diff --git a/game/engines/default/engine/Actor.lua b/game/engines/default/engine/Actor.lua index 3d0a5d2ac656428508583a27d980cc32331130c3..ff82df52d4f57d35cc91256a00ba786bb1f7d048 100644 --- a/game/engines/default/engine/Actor.lua +++ b/game/engines/default/engine/Actor.lua @@ -252,8 +252,9 @@ end function _M:knockback(srcx, srcy, dist, recursive) print("[KNOCKBACK] from", srcx, srcx, "over", dist) - local l = line.new(srcx, srcy, self.x, self.y, true) - local lx, ly = l(true) + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end + local l = core.fov.line(srcx, srcy, self.x, self.y, block_actor, true) + local lx, ly, is_corner_blocked = l:step(block_actor, true) local ox, oy = lx, ly dist = dist - 1 @@ -266,10 +267,10 @@ function _M:knockback(srcx, srcy, dist, recursive) end end - while game.level.map:isBound(lx, ly) and not game.level.map:checkAllEntities(lx, ly, "block_move", self) and dist > 0 do + while game.level.map:isBound(lx, ly) and not is_corner_blocked and not game.level.map:checkAllEntities(lx, ly, "block_move", self) and dist > 0 do dist = dist - 1 ox, oy = lx, ly - lx, ly = l(true) + lx, ly, is_corner_blocked = l:step(block_actor, true) print("[KNOCKBACK] try", lx, ly, dist, "::", game.level.map:checkAllEntities(lx, ly, "block_move", self)) if recursive then @@ -293,8 +294,9 @@ end function _M:pull(srcx, srcy, dist, recursive) print("[PULL] from", self.x, self.x, "towards", srcx, srcy, "over", dist) - local l = line.new(self.x, self.y, srcx, srcy) - local lx, ly = l() + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end + local l = core.fov.line(self.x, self.y, srcx, srcy, block_actor) + local lx, ly, is_corner_blocked = l:step(block_actor) local ox, oy = lx, ly dist = dist - 1 @@ -308,10 +310,10 @@ function _M:pull(srcx, srcy, dist, recursive) end end - while game.level.map:isBound(lx, ly) and not game.level.map:checkAllEntities(lx, ly, "block_move", self) and dist > 0 do + while game.level.map:isBound(lx, ly) and not is_corner_blocked and not game.level.map:checkAllEntities(lx, ly, "block_move", self) and dist > 0 do dist = dist - 1 ox, oy = lx, ly - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_actor) print("[PULL] try", lx, ly, dist, "::", game.level.map:checkAllEntities(lx, ly, "block_move", self)) if recursive then @@ -370,19 +372,45 @@ function _M:canSee(actor, def, def_pct) return true, 100 end +--- Create a line to target based on field of vision +function _M:lineFOV(tx, ty, extra_block, block, sx, sy) + sx = sx or self.x + sy = sy or self.y + local act = game.level.map(tx, ty, Map.ACTOR) + local sees_target = (self.sight and core.fov.distance(sx, sy, tx, ty) <= self.sight or not self.sight) and + (game.level.map.lites(tx, ty) or act and self:canSee(act)) + + block = block or function(_, x, y) + if sees_target then + return game.level.map:checkAllEntities(x, y, "block_sight") or + game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") and not game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "pass_projectile") or + type(extra_block) == "function" and extra_block(self, x, y) + elseif (self.sight and core.fov.distance(sx, sy, x, y) <= self.sight or not self.sight) and game.level.map.lites(x, y) then + return game.level.map:checkEntity(x, y, Map.TERRAIN, "block_sight") or + game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") and not game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "pass_projectile") or + type(extra_block) == "function" and extra_block(self, x, y) + else + return true + end + end + + return core.fov.line(sx, sy, tx, ty, block) +end + --- Does the actor have LOS to the target function _M:hasLOS(x, y, what) if not x or not y then return false, self.x, self.y end what = what or "block_sight" - local l = line.new(self.x, self.y, x, y) - local lx, ly = l() - while lx and ly do + local l = core.fov.line(self.x, self.y, x, y, what) + local lx, ly, is_corner_blocked = l:step(what) + + while lx and ly and not is_corner_blocked do if game.level.map:checkAllEntities(lx, ly, what) then break end - lx, ly = l() + lx, ly, is_corner_blocked = l:step(what) end -- Ok if we are at the end reset lx and ly for the next code - if not lx and not ly then lx, ly = x, y end + if not lx and not ly and not is_corner_blocked then lx, ly = x, y end if lx == x and ly == y then return true, lx, ly end return false, lx, ly @@ -394,6 +422,7 @@ end -- @param radius how close we should be (defaults to 1) function _M:isNear(x, y, radius) radius = radius or 1 - if math.floor(core.fov.distance(self.x, self.y, x, y)) > radius then return false end + if core.fov.distance(self.x, self.y, x, y) > radius then return false end return true end + diff --git a/game/engines/default/engine/Map.lua b/game/engines/default/engine/Map.lua index a2847eaf6d263af3ca1495bcdaf8ac78f57c0199..2c174c25db13365add3b388496f379e6881e832c 100644 --- a/game/engines/default/engine/Map.lua +++ b/game/engines/default/engine/Map.lua @@ -827,7 +827,7 @@ function _M:addEffect(src, x, y, duration, damtype, dam, radius, dir, angle, ove -- Handle any angle if type(dir) == "table" then - grids = core.fov.beam_any_angle_grids(x, y, radius, dir.angle, angle, true) + grids = core.fov.beam_any_angle_grids(x, y, radius, dir.delta_x, dir.delta_y, angle, true) -- Handle balls elseif dir == 5 then grids = core.fov.circle_grids(x, y, radius, true) @@ -904,7 +904,7 @@ function _M:processEffects() table.insert(todel, i) elseif e.update_fct then if e:update_fct() then - if type(dir) == "table" then e.grids = core.fov.beam_any_angle_gridse(e.x, e.y, e.radius, e.dir.angle, e.angle, true) + if type(dir) == "table" then e.grids = core.fov.beam_any_angle_grids(e.x, e.y, e.radius, e.dir.delta_x, e.dir.delta_y, e.angle, true) elseif e.dir == 5 then e.grids = core.fov.circle_grids(e.x, e.y, e.radius, true) else e.grids = core.fov.beam_grids(e.x, e.y, e.radius, e.dir, e.angle, true) end if e.particles then diff --git a/game/engines/default/engine/Projectile.lua b/game/engines/default/engine/Projectile.lua index a6e9c3fab2a3c0a9eb5d4464bcfd1e82715a54b7..79a20e6377bae4f2c77e4bb0624509dce0f4c3ba 100644 --- a/game/engines/default/engine/Projectile.lua +++ b/game/engines/default/engine/Projectile.lua @@ -37,6 +37,21 @@ function _M:init(t, no_default) Entity.init(self, t, no_default) end +function _M:save() + if self.project and self.project.def and self.project.def.typ and self.project.def.typ.line_function then + self.project.def.typ.line_function_save = { self.project.def.typ.line_function:export() } + self.project.def.typ.line_function = nil + end + return class.save(self, {}) +end + +function _M:loaded() + if self.project and self.project.def and self.project.def.typ and self.project.def.typ.line_function_save then + self.project.def.typ.line_function = core.fov.line_import(unpack(self.project.def.typ.line_function_save)) + self.project.def.typ.line_function_save = nil + end +end + --- Moves a projectile on the map -- *WARNING*: changing x and y properties manually is *WRONG* and will blow up in your face. Use this method. Always. -- @param map the map to move onto diff --git a/game/engines/default/engine/Target.lua b/game/engines/default/engine/Target.lua index bc3de39a6aa07d48db4e578765b7c9b419a60f00..f7213e77d6ffb6caf0d82cbb7dedeaa6cbc16c63 100644 --- a/game/engines/default/engine/Target.lua +++ b/game/engines/default/engine/Target.lua @@ -82,9 +82,23 @@ function _M:display(dispx, dispy) if not self.active then return end local s = self.sb - local l = line.new(self.source_actor.x, self.source_actor.y, self.target.x, self.target.y) - 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 l + if self.target_type.source_actor.lineFOV then + l = self.target_type.source_actor:lineFOV(self.target.x, self.target.y) + else + l = core.fov.line(self.source_actor.x, self.source_actor.y, self.target.x, self.target.y) + end + local block_corner = function(_, bx, by) + if self.target_type.block_path then + local b, h, hr = self.target_type:block_path(bx, by, true) + return b and h and not hr + else + return false + end + end + + local lx, ly, is_corner_blocked = l:step(block_corner) + local stop_x, stop_y = self.source_actor.x, self.source_actor.y local stop_radius_x, stop_radius_y = self.source_actor.x, self.source_actor.y local stopped = false @@ -94,6 +108,11 @@ function _M:display(dispx, dispy) block, hit, hit_radius = self.target_type:block_path(lx, ly, true) end + if is_corner_blocked then + block = true + stopped = true + end + -- Update coordinates and set color if hit and not stopped then stop_x, stop_y = lx, ly @@ -119,7 +138,7 @@ function _M:display(dispx, dispy) stopped = true end - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_corner) 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) @@ -148,14 +167,15 @@ function _M:display(dispx, dispy) end, nil) elseif 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)) + --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, game.level.map.w, game.level.map.h, self.target_type.cone, - dir_angle, + self.target.x - self.source_actor.x, + self.target.y - self.source_actor.y, self.target_type.cone_angle, function(_, px, py) if self.target_type.block_radius and self.target_type:block_radius(px, py, true) then return true end @@ -208,10 +228,10 @@ function _M:getType(t) if 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 math.floor(dist - typ.range + 0.5) > 0 then return true, false, false end + 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 math.floor(dist - typ.range + 0.5) > 0 then return true, false, false end + 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 @@ -359,3 +379,4 @@ function _M:pointAtRange(srcx, srcy, destx, desty, dist) return lx, ly end end + diff --git a/game/engines/default/engine/ai/talented.lua b/game/engines/default/engine/ai/talented.lua index c9b9f6813591907e0748a6e5ac2f641ae9300636..d10945d3eb8de9d13bb01aa2bae7e173c51bca10 100644 --- a/game/engines/default/engine/ai/talented.lua +++ b/game/engines/default/engine/ai/talented.lua @@ -23,7 +23,7 @@ newAI("dumb_talented", function(self) -- Find available talents local avail = {} - local target_dist = math.floor(core.fov.distance(self.x, self.y, self.ai_target.actor.x, self.ai_target.actor.y)) + local target_dist = core.fov.distance(self.x, self.y, self.ai_target.actor.x, self.ai_target.actor.y) for tid, _ in pairs(self.talents) do local t = self:getTalentFromId(tid) -- print(self.name, self.uid, "dumb ai talents can try use", t.name, tid, "::", t.mode, not self:isTalentCoolingDown(t), target_dist <= self:getTalentRange(t), self:preUseTalent(t, true), self:canProject({type="bolt"}, self.ai_target.actor.x, self.ai_target.actor.y)) @@ -65,3 +65,4 @@ newAI("dumb_talented_simple", function(self) return true end end) + diff --git a/game/engines/default/engine/interface/ActorProject.lua b/game/engines/default/engine/interface/ActorProject.lua index 21e51a46ae1848f841f30fd973320fe491821e76..bf01ff868c95112edbf663881bcade972a88e741 100644 --- a/game/engines/default/engine/interface/ActorProject.lua +++ b/game/engines/default/engine/interface/ActorProject.lua @@ -59,19 +59,40 @@ function _M:project(t, x, y, damtype, dam, particles) local srcx, srcy = t.x or self.x, t.y or self.y -- Stop at range or on block - local lx, ly = x, y + local lx, ly, is_corner_blocked = x, y local stop_x, stop_y = srcx, srcy local stop_radius_x, stop_radius_y = srcx, srcy - local l = line.new(srcx, srcy, x, y) - lx, ly = l() - local initial_dir = lx and coord_to_dir[lx - srcx][ly - srcy] or 5 + local l + if typ.source_actor.lineFOV then + l = typ.source_actor:lineFOV(x, y, nil, nil, srcx, srcy) + else + l = core.fov.line(srcx, srcy, x, y) + end + local block_corner = function(_, bx, by) + if self.target_type.block_path then + local b, h, hr = self.target_type:block_path(bx, by, true) + return b and h and not hr + else + return false + end + end + + local lx, ly, is_corner_blocked = l:step(block_corner) while lx and ly do local block, hit, hit_radius = false, true, true if typ.block_path then block, hit, hit_radius = typ:block_path(lx, ly) end + if is_corner_blocked then + block = true + hit_radius = false + end if hit then - stop_x, stop_y = lx, ly + if is_corner_blocked then + stop_x, stop_y = stop_radius_x, stop_radius_y + else + stop_x, stop_y = lx, ly + end -- Deal damage: beam if typ.line then addGrid(lx, ly) end -- WHAT DOES THIS DO AGAIN? @@ -85,7 +106,7 @@ function _M:project(t, x, y, damtype, dam, particles) end if block then break end - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_corner) end if typ.ball and typ.ball > 0 then @@ -105,14 +126,15 @@ function _M:project(t, x, y, damtype, dam, particles) nil) addGrid(stop_x, stop_y) elseif typ.cone and typ.cone > 0 then - local dir_angle = math.deg(math.atan2(y - self.y, x - self.x)) + --local dir_angle = math.deg(math.atan2(y - self.y, x - self.x)) core.fov.calc_beam_any_angle( stop_radius_x, stop_radius_y, game.level.map.w, game.level.map.h, typ.cone, - dir_angle, + x - self.x, + y - self.y, typ.cone_angle, function(_, px, py) if typ.block_radius and typ:block_radius(px, py) then return true end @@ -179,25 +201,47 @@ function _M:canProject(t, x, y) typ.start_y = self.y -- Stop at range or on block - local lx, ly = x, y + local lx, ly, is_corner_blocked = x, y local stop_x, stop_y = self.x, self.y local stop_radius_x, stop_radius_y = self.x, self.y - local l = line.new(self.x, self.y, x, y) - lx, ly = l() + local l + if typ.source_actor.lineFOV then + l = typ.source_actor:lineFOV(x, y) + else + l = core.fov.line(self.x, self.y, x, y) + end + local block_corner = function(_, bx, by) + if self.target_type.block_path then + local b, h, hr = self.target_type:block_path(bx, by, true) + return b and h and not hr + else + return false + end + end + + local lx, ly, is_corner_blocked = l:step(block_corner) while lx and ly do local block, hit, hit_radius = false, true, true if typ.block_path then block, hit, hit_radius = typ:block_path(lx, ly) end + if is_corner_blocked then + block = true + hit_radius = false + end if hit then - stop_x, stop_y = lx, ly + if is_corner_blocked then + stop_x, stop_y = stop_radius_x, stop_radius_y + else + stop_x, stop_y = lx, ly + end end if hit_radius then stop_radius_x, stop_radius_y = lx, ly end if block then break end - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_corner) end -- Check for minimum range @@ -230,6 +274,11 @@ function _M:projectile(t, x, y, damtype, dam, particles) typ.source_actor = self typ.start_x = self.x typ.start_y = self.y + if self.lineFOV then + typ.line_function = self:lineFOV(x, y) + else + typ.line_function = core.fov.line(self.x, self.y, x, y) + end local proj = require(self.projectile_class):makeProject(self, t.display, {x=x, y=y, start_x = t.x or self.x, start_y = t.y or self.y, damtype=damtype, tg=t, typ=typ, dam=dam, particles=particles}) game.zone:addEntity(game.level, proj, "projectile", t.x or self.x, t.y or self.y) @@ -247,20 +296,26 @@ end -- @return act should we call projectDoAct (usually only for beam) -- @return stop is this the last (blocking) tile? function _M:projectDoMove(typ, tgtx, tgty, x, y, srcx, srcy) - -- Stop at range or on block - local l = line.new(srcx, srcy, tgtx, tgty) - local lx, ly = srcx, srcy - -- Look for our current position - while lx and ly and not (lx == x and ly == y) do lx, ly = l() end - -- Now get the next position - if lx and ly then lx, ly = l() end + local block_corner = function(_, bx, by) + if self.target_type.block_path then + local b, h, hr = self.target_type:block_path(bx, by, true) + return b and h and not hr + else + return false + end + end + local lx, ly, is_corner_blocked = typ.line_function:step(block_corner) if lx and ly then local block, hit, hit_radius = false, true, true if typ.block_path then block, hit, hit_radius = typ:block_path(lx, ly) end - + if is_corner_blocked then + block = true + hit = false + hit_radius = false + end if block then if hit then return lx, ly, false, true @@ -329,15 +384,16 @@ function _M:projectDoStop(typ, tg, damtype, dam, particles, lx, ly, tmp, rx, ry) 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 - local dir_angle = math.deg(math.atan2(ly - typ.source_actor.y, lx - typ.source_actor.x)) + --local initial_dir = lx and util.getDir(lx, ly, x, y) or 5 + --local dir_angle = math.deg(math.atan2(ly - typ.source_actor.y, lx - typ.source_actor.x)) core.fov.calc_beam_any_angle( rx, ry, game.level.map.w, game.level.map.h, typ.cone, - dir_angle, + lx - typ.source_actor.x, + ly - typ.source_actor.y, typ.cone_angle, function(_, px, py) if typ.block_radius and typ:block_radius(px, py) then return true end diff --git a/game/engines/default/engine/interface/PlayerMouse.lua b/game/engines/default/engine/interface/PlayerMouse.lua index 5f994bac297a6eac1c2e3c7bba8573894b91d459..a2e9c5806b3f0d014094150a0bb36640cbbe78ea 100644 --- a/game/engines/default/engine/interface/PlayerMouse.lua +++ b/game/engines/default/engine/interface/PlayerMouse.lua @@ -49,7 +49,7 @@ function _M:mouseMove(tmx, tmy, spotHostiles, astar_check, force_move) -- Expand logic for clarity local test_actor = game.level.map(tmx, tmy, Map.ACTOR) local test_hostile = spotHostiles and spotHostiles(self) - local test_adjacent = math.floor(core.fov.distance(self.x, self.y, tmx, tmy)) == 1 + local test_adjacent = core.fov.distance(self.x, self.y, tmx, tmy) == 1 if ((config.settings.mouse_move or force_move) and (test_hostile or test_adjacent)) or (not config.settings.mouse_move and test_adjacent and test_actor) then local l = line.new(self.x, self.y, tmx, tmy) local nx, ny = l() @@ -143,3 +143,4 @@ function _M:mouseHandleDefault(key, allow_move, button, mx, my, xrel, yrel, even if not xrel and not yrel then moving_around = false end end + diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua index 6b23ef0f8d0a39e5e70079536aaf921eec0fba19..468fa6423586f328989f899c18daa014a62e6f97 100644 --- a/game/engines/default/engine/utils.lua +++ b/game/engines/default/engine/utils.lua @@ -1046,11 +1046,11 @@ function core.fov.beam_grids(x, y, radius, dir, angle, block) return grids end -function core.fov.beam_any_angle_grids(x, y, radius, dir_angle, angle, block) +function core.fov.beam_any_angle_grids(x, y, radius, delta_x, delta_y, angle, block) if not x or not y then return {} end if radius == 0 then return {[x]={[y]=true}} end local grids = {} - core.fov.calc_beam_any_angle(x, y, game.level.map.w, game.level.map.h, radius, dir_angle, angle, function(_, lx, ly) + core.fov.calc_beam_any_angle(x, y, game.level.map.w, game.level.map.h, radius, delta_x, delta_y, angle, function(_, lx, ly) if block and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true end end, function(_, lx, ly) @@ -1065,6 +1065,45 @@ function core.fov.beam_any_angle_grids(x, y, radius, dir_angle, angle, block) return grids end +function core.fov.line(sx, sy, tx, ty, block, start_at_end) + local what = type(block) == "string" and block or "block_sight" + block = type(block) == "function" and block or + block == false and function(_, x, y) return end or + function(_, x, y) + return game.level.map:checkAllEntities(x, y, what) + end + return core.fov.line_base(sx, sy, tx, ty, game.level.map.w, game.level.map.h, block) +end + +tmps = core.fov.line_base(0, 0, 0, 0, 0, 0, function(_, x, y) end) +getmetatable(tmps).__index.step = function(l, block_corner, dont_stop_at_end) + block_corner = type(block_corner) == "function" and block_corner or + block_corner == false and function(_, x, y) return end or + type(block_corner) == "string" and function(_, x, y) return game.level.map:checkAllEntities(x, y, what) end or + function(_, x, y) return game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") and + not game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "pass_projectile") end + return l:step_base(dont_stop_at_end, game.level.map.w, game.level.map.h, block_corner) +end + +--- Sets the permissiveness of FoV based on the shape of blocked terrain +-- @param val can be a number between 0 and 1 (least permissive to most permissive) or the name of a shape: square, diamond, octagon, firstpeek. +-- val = 0.0 is equivalent to "square", and val = 1.0 is equivalent to "diamond" +-- "firstpeek" is the least permissive setting that allows @ to see r below: +-- @## +-- ..r +function core.fov.set_permissiveness(val) + val = type(val) == "string" and (string.lower(val) == "square" and 0.0 or + string.lower(val) == "diamond" and 0.5 or + string.lower(val) == "octagon" and 1 - math.sqrt(0.5) or --0.29289321881345247560 or + string.lower(val) == "firstpeek" and 0.167) or + type(tonumber(val)) == "number" and 0.5*tonumber(val) + + if type(val) ~= "number" then return end + val = util.bound(val, 0.0, 0.5) + core.fov.set_permissiveness_base(val) + return val +end + --- Finds free grids around coords in a radius. -- This will return a random grid, the closest possible to the epicenter -- @param sx the epicenter coordinates @@ -1087,7 +1126,7 @@ function util.findFreeGrid(sx, sy, radius, block, what) if game.level.map:checkEntity(x, y, game.level.map.TERRAIN, "block_move") then ok = false end -- print("findFreeGrid", x, y, "from", sx,sy,"=>", ok) if ok then - gs[#gs+1] = {x, y, math.floor(core.fov.distance(sx, sy, x, y)), rng.range(1, 1000)} + gs[#gs+1] = {x, y, core.fov.distance(sx, sy, x, y), rng.range(1, 1000)} end end end @@ -1200,3 +1239,4 @@ function require_first(...) end return nil end + diff --git a/game/engines/default/modules/boot/data/talents.lua b/game/engines/default/modules/boot/data/talents.lua index 8477f2e2772cdc3ac3f9070a01442ee50e600ed4..fca600766aff5a367174f8a91fa3b2e54b591188 100644 --- a/game/engines/default/modules/boot/data/talents.lua +++ b/game/engines/default/modules/boot/data/talents.lua @@ -30,7 +30,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end target:knockback(self.x, self.y, 2 + self:getDex()) return true @@ -194,3 +194,4 @@ newTalent{ return "swooosh" end, } + diff --git a/game/modules/example/data/talents.lua b/game/modules/example/data/talents.lua index 1ea53df8fe0e0c46c928b97346267a663faea14f..e5e96b1975ff8056beb75c7a98cba0c35abda368 100644 --- a/game/modules/example/data/talents.lua +++ b/game/modules/example/data/talents.lua @@ -30,7 +30,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end target:knockback(self.x, self.y, 2 + self:getDex()) return true @@ -58,3 +58,4 @@ newTalent{ return "Zshhhhhhhhh!" end, } + diff --git a/game/modules/example_realtime/data/talents.lua b/game/modules/example_realtime/data/talents.lua index 1ea53df8fe0e0c46c928b97346267a663faea14f..e5e96b1975ff8056beb75c7a98cba0c35abda368 100644 --- a/game/modules/example_realtime/data/talents.lua +++ b/game/modules/example_realtime/data/talents.lua @@ -30,7 +30,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end target:knockback(self.x, self.y, 2 + self:getDex()) return true @@ -58,3 +58,4 @@ newTalent{ return "Zshhhhhhhhh!" end, } + diff --git a/game/modules/tome/ai/escort.lua b/game/modules/tome/ai/escort.lua index b032e9d10ee0cf8c9aaf56590a03a7660e2c270f..96adbdf9a56610c23ee649d8823f9ed511af1f21 100644 --- a/game/modules/tome/ai/escort.lua +++ b/game/modules/tome/ai/escort.lua @@ -59,7 +59,7 @@ newAI("move_escort", function(self) local tx, ty = self.escort_target.x, self.escort_target.y local a = Astar.new(game.level.map, self) local path = self.escort_path - if path and path[1] and math.floor(core.fov.distance(self.x, self.y, path[1].x, path[1].y)) > 1 then self.escort_path = nil path = nil end + if path and path[1] and core.fov.distance(self.x, self.y, path[1].x, path[1].y) > 1 then self.escort_path = nil path = nil end if not path or #path == 0 then path = a:calc(self.x, self.y, tx, ty) end if not path then return self:runAI("move_simple") @@ -73,3 +73,4 @@ newAI("move_escort", function(self) end end end) + diff --git a/game/modules/tome/ai/heal.lua b/game/modules/tome/ai/heal.lua index 743711be2aa9d2bce98d49130a456cf32bee652d..a4ba928ade8f39d7dc7a07094de8c66800b262cf 100644 --- a/game/modules/tome/ai/heal.lua +++ b/game/modules/tome/ai/heal.lua @@ -43,7 +43,7 @@ end) newAI("dumb_heal", function(self) -- Find available talents local avail = {} - local target_dist = math.floor(core.fov.distance(self.x, self.y, self.ai_target.actor.x, self.ai_target.actor.y)) + local target_dist = core.fov.distance(self.x, self.y, self.ai_target.actor.x, self.ai_target.actor.y) for tid, _ in pairs(self.talents) do local t = self:getTalentFromId(tid) -- print(self.name, self.uid, "dumb ai talents can try use", t.name, tid, "::", t.mode, not self:isTalentCoolingDown(t), target_dist <= self:getTalentRange(t), self:preUseTalent(t, true), self:canProject({type="bolt"}, self.ai_target.actor.x, self.ai_target.actor.y)) @@ -75,3 +75,4 @@ newAI("dumb_heal_simple", function(self) return true end end) + diff --git a/game/modules/tome/ai/party.lua b/game/modules/tome/ai/party.lua index b335c8734ed408f8ea5dd252ccf3590a004cb928..497efd165ebae2d74746feb9ef5daecb3693472a 100644 --- a/game/modules/tome/ai/party.lua +++ b/game/modules/tome/ai/party.lua @@ -22,7 +22,7 @@ newAI("party_member", function(self) -- Stay close to the leash anchor if anchor and self.ai_state.tactic_leash then - local leash_dist = math.floor(core.fov.distance(self.x, self.y, anchor.x, anchor.y)) + local leash_dist = core.fov.distance(self.x, self.y, anchor.x, anchor.y) if self.ai_state.tactic_leash < leash_dist then -- print("[PARTY AI] leashing to anchor", self.name) return self:runAI("move_anchor") @@ -49,4 +49,5 @@ newAI("move_anchor", function(self) local tx, ty = anchor.x, anchor.y return self:moveDirection(tx, ty) end -end) \ No newline at end of file +end) + diff --git a/game/modules/tome/ai/shadow.lua b/game/modules/tome/ai/shadow.lua index ae340f6fd72cea9f80b4385fbcfedb4b50683a97..b7465549a3ce37495321de10a53ef39cb4958e45 100644 --- a/game/modules/tome/ai/shadow.lua +++ b/game/modules/tome/ai/shadow.lua @@ -59,7 +59,7 @@ local function shadowChooseActorTarget(self) end local function shadowMoveToActorTarget(self) - local range = math.floor(core.fov.distance(self.x, self.y, self.ai_target.actor.x, self.ai_target.actor.y)) + local range = core.fov.distance(self.x, self.y, self.ai_target.actor.x, self.ai_target.actor.y) if range <= 1 and self.ai_state.close_attack_spell_chance and rng.percent(self.ai_state.close_attack_spell_chance) then -- chance for close spell @@ -236,7 +236,7 @@ newAI("shadow", function(self) self:attackTarget(target, nil, 1, true) return true end - if not newX and math.floor(core.fov.distance(x, y, defendant.x, defendant.y)) <= 1 and self:canMove(x, y, false) then + if not newX and core.fov.distance(x, y, defendant.x, defendant.y) <= 1 and self:canMove(x, y, false) then newX, newY = x, y end end @@ -313,3 +313,4 @@ newAI("shadow", function(self) --game.logPlayer(self.summoner, "#PINK#%s -> failed to make a move.", self.name:capitalize()) return true end) + diff --git a/game/modules/tome/ai/tactical.lua b/game/modules/tome/ai/tactical.lua index 3e2b1ce3cd4ddb3ecd1bcfed440bee474b6d4fdc..a354c1617267a0605c1c5b45d47598ddc50699be 100644 --- a/game/modules/tome/ai/tactical.lua +++ b/game/modules/tome/ai/tactical.lua @@ -22,16 +22,16 @@ -- Internal functions local checkLOS = function(sx, sy, tx, ty) what = what or "block_sight" - local l = line.new(sx, sy, tx, ty) - local lx, ly = l() - while lx and ly do + local l = core.fov.line(sx, sy, tx, ty, what) + local lx, ly, is_corner_blocked = l:step(what) + while lx and ly and not is_corner_blocked do if game.level.map:checkAllEntities(lx, ly, what) then break end - lx, ly = l() + lx, ly, is_corner_blocked = l:step(what) end -- Ok if we are at the end reset lx and ly for the next code - if not lx and not ly then lx, ly = x, y end - + if not lx and not ly and not is_corner_blocked then lx, ly = x, y end + if lx == x and ly == y then return true, lx, ly end return false, lx, ly end @@ -65,7 +65,7 @@ newAI("use_tactical", function(self) print("============================== TACTICAL AI", self.name) local avail = {} local ok = false - local target_dist = self.ai_target.actor and math.floor(core.fov.distance(self.x, self.y, self.ai_target.actor.x, self.ai_target.actor.y)) + local target_dist = self.ai_target.actor and core.fov.distance(self.x, self.y, self.ai_target.actor.x, self.ai_target.actor.y) local hate = self.ai_target.actor and (self:reactionToward(self.ai_target.actor) < 0) local has_los = self.ai_target.actor and self:hasLOS(self.ai_target.actor.x, self.ai_target.actor.y) local self_compassion = (self.ai_state.self_compassion == false and 0) or self.ai_state.self_compassion or 5 @@ -352,7 +352,7 @@ newAI("tactical", function(self) -- Keep your distance local special_move = false if self.ai_tactic.safe_range and self.ai_target.actor and self:hasLOS(self.ai_target.actor.x, self.ai_target.actor.y) then - local target_dist = math.floor(core.fov.distance(self.x, self.y, self.ai_target.actor.x, self.ai_target.actor.y)) + local target_dist = core.fov.distance(self.x, self.y, self.ai_target.actor.x, self.ai_target.actor.y) if self.ai_tactic.safe_range == target_dist then special_move = "none" elseif self.ai_tactic.safe_range > target_dist then @@ -385,4 +385,5 @@ newAI("flee_dmap_keep_los", function(self) if can_flee then return self:move(fx, fy) end -end) \ No newline at end of file +end) + diff --git a/game/modules/tome/ai/worldnpcs.lua b/game/modules/tome/ai/worldnpcs.lua index 8d8002e982ddae8273cf4e5c7068df83d06bd5f9..1b92fb5c4ac9ba49a3b70b8e3c30ed9e943e3aae 100644 --- a/game/modules/tome/ai/worldnpcs.lua +++ b/game/modules/tome/ai/worldnpcs.lua @@ -67,9 +67,9 @@ newAI("move_world_patrol", function(self) else local path = self.ai_state.route_path -- print("Using route", self.ai_state.route_path) - if not path or not path[1] or (path[1] and math.floor(core.fov.distance(self.x, self.y, path[1].x, path[1].y)) > 1) then + if not path or not path[1] or (path[1] and core.fov.distance(self.x, self.y, path[1].x, path[1].y)) > 1 then self.ai_state.route_path = nil self.ai_state.route = nil --- print("Nulling!", path, path and path[1], path and path[1] and math.floor(core.fov.distance(self.x, self.y, path[1].x, path[1].y))) +-- print("Nulling!", path, path and path[1], path and path[1] and core.fov.distance(self.x, self.y, path[1].x, path[1].y)) return true else local ret = self:move(path[1].x, path[1].y) @@ -114,3 +114,4 @@ newAI("move_world_hostile", function(self) end return true end) + diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua index 2047b840ab2e98e5f5471a289fe3425a87f14d66..f75e01582bf25aa83d396f3025e428dff57c3bcf 100644 --- a/game/modules/tome/class/Actor.lua +++ b/game/modules/tome/class/Actor.lua @@ -104,8 +104,8 @@ function _M:init(t, no_default) self.spell_cooldown_reduction = 0 self.unused_stats = self.unused_stats or 3 - self.unused_talents = self.unused_talents or 2 - self.unused_generics = self.unused_generics or 1 + self.unused_talents = self.unused_talents or 2 + self.unused_generics = self.unused_generics or 1 self.unused_talents_types = self.unused_talents_types or 0 t.healing_factor = t.healing_factor or 1 @@ -1589,7 +1589,7 @@ function _M:levelup() if self.level % 5 == 0 then self.unused_talents = self.unused_talents + 1 end if self.level % 5 == 0 then self.unused_generics = self.unused_generics - 1 end -- At levels 10, 20 and 30 we gain a new talent type - if self.level == 10 or self.level == 20 or self.level == 30 then + if self.level == 10 or self.level == 20 or self.level == 30 then self.unused_talents_types = self.unused_talents_types + 1 end elseif type(self.no_points_on_levelup) == "function" then @@ -2717,7 +2717,7 @@ function _M:canSeeNoCache(actor, def, def_pct) -- check cursed pity talent if actor:knowTalent(self.T_PITY) then local t = actor:getTalentFromId(self.T_PITY) - if math.floor(core.fov.distance(self.x, self.y, actor.x, actor.y)) >= actor:getTalentRange(t) then + if core.fov.distance(self.x, self.y, actor.x, actor.y) >= actor:getTalentRange(t) then return false, 50 - actor:getTalentLevel(self.T_PITY) * 5 end end @@ -2768,7 +2768,7 @@ function _M:hasLOS(x, y, what) if not x or not y then return false, self.x, self.y end what = what or "block_sight" - local lx, ly + local lx, ly, is_corner_blocked if what == "block_sight" then local darkVisionRange if self:knowTalent(self.T_DARK_VISION) then @@ -2776,10 +2776,10 @@ function _M:hasLOS(x, y, what) darkVisionRange = self:getTalentRange(t) end - local l = line.new(self.x, self.y, x, y) + local l = core.fov.line(self.x, self.y, x, y, "block_sight") local inCreepingDark, lastX, lastY = false - lx, ly = l() - while lx and ly do + lx, ly, is_corner_blocked = l:step("block_sight") + while lx and ly and not is_corner_blocked do if game.level.map:checkAllEntities(lx, ly, "block_sight") then if darkVisionRange and game.level.map:checkAllEntities(lx, ly, "creepingDark") then inCreepingDark = true @@ -2793,20 +2793,20 @@ function _M:hasLOS(x, y, what) end lastX, lastY = lx, ly - lx, ly = l() + lx, ly, is_corner_blocked = l:step("block_sight") end else - local l = line.new(self.x, self.y, x, y) - lx, ly = l() - while lx and ly do + local l = core.fov.line(self.x, self.y, x, y, what) + lx, ly, is_corner_blocked = l:step(what) + while lx and ly and not is_corner_blocked do if game.level.map:checkAllEntities(lx, ly, what) then break end - lx, ly = l() + lx, ly, is_corner_blocked = l:step(what) end end -- Ok if we are at the end reset lx and ly for the next code - if not lx and not ly then lx, ly = x, y end + if not lx and not ly and not is_corner_blocked then lx, ly = x, y end if lx == x and ly == y then return true, lx, ly end return false, lx, ly @@ -2981,3 +2981,4 @@ function _M:addedToLevel(level, x, y) self:check("on_added_to_level", level, x, y) end + diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua index d095cd11cab09ca6882fbe7e8cfd3e82c6fe5c59..7e8005e3e2501750718b94d168631128f4ccca53 100644 --- a/game/modules/tome/class/Game.lua +++ b/game/modules/tome/class/Game.lua @@ -1494,7 +1494,7 @@ function _M:mouseLeftClick(mx, my) local t = p:getTalentFromId(p.auto_shoot_talent) if not t then return end - local target_dist = math.floor(core.fov.distance(p.x, p.y, a.x, a.y)) + local target_dist = core.fov.distance(p.x, p.y, a.x, a.y) if p:enoughEnergy() and p:reactionToward(a) < 0 and not p:isTalentCoolingDown(t) and p:preUseTalent(t, true, true) and target_dist <= p:getTalentRange(t) and p:canProject({type="hit"}, a.x, a.y) then p:useTalent(t.id, nil, nil, nil, a) @@ -1512,7 +1512,7 @@ function _M:mouseMiddleClick(mx, my) local t = p:getTalentFromId(p.auto_shoot_midclick_talent) if not t then return end - local target_dist = math.floor(core.fov.distance(p.x, p.y, a.x, a.y)) + local target_dist = core.fov.distance(p.x, p.y, a.x, a.y) if p:enoughEnergy() and p:reactionToward(a) < 0 and not p:isTalentCoolingDown(t) and p:preUseTalent(t, true, true) and target_dist <= p:getTalentRange(t) and p:canProject({type="hit"}, a.x, a.y) then p:useTalent(t.id, nil, nil, nil, a) @@ -1858,3 +1858,4 @@ function _M:mouseIcon(bx, by) self:tooltipDisplayAtMap(self.w, self.h, "Show message/chat log (#{bold}##GOLD#ctrl+m#LAST##{normal}#)") end end + diff --git a/game/modules/tome/class/NPC.lua b/game/modules/tome/class/NPC.lua index ea27ce4e93d657b9c399c3bb077238c7fbfaf57a..6728a71b6dbe300b936fbe70c1177b24cef984e2 100644 --- a/game/modules/tome/class/NPC.lua +++ b/game/modules/tome/class/NPC.lua @@ -68,6 +68,47 @@ function _M:doFOV() end end +--- Create a line to target based on field of vision +function _M:lineFOV(tx, ty, extra_block, block, sx, sy) + sx = sx or self.x + sy = sy or self.y + local act = game.level.map(tx, ty, engine.Map.ACTOR) + local sees_target = core.fov.distance(sx, sy, tx, ty) <= self.sight and game.level.map.lites(tx, ty) or + act and self:canSee(act) and core.fov.distance(sx, sy, tx, ty) <= math.max(self.sight, (self.heightened_senses or 0) + (self.infravision or 0)) + + local darkVisionRange + if self:knowTalent(self.T_DARK_VISION) then + local t = self:getTalentFromId(self.T_DARK_VISION) + darkVisionRange = self:getTalentRange(t) + end + local inCreepingDark = false + + block = block or function(_, x, y) + if darkVisionRange then + if game.level.map:checkAllEntities(x, y, "creepingDark") then + inCreepingDark = true + end + if inCreepingDark and core.fov.distance(sx, sy, x, y) > darkVisionRange then + return true + end + end + + if sees_target then + return game.level.map:checkAllEntities(x, y, "block_sight") or + game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") and not game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "pass_projectile") or + type(extra_block) == "function" and extra_block(self, x, y) + elseif core.fov.distance(sx, sy, x, y) <= self.sight and game.level.map.lites(x, y) then + return game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_sight") or + game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") and not game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "pass_projectile") or + type(extra_block) == "function" and extra_block(self, x, y) + else + return true + end + end + + return core.fov.line(sx, sy, tx, ty, block) +end + --- Give target to others function _M:seen_by(who) if self.ai_target.actor then return end @@ -263,3 +304,4 @@ function _M:addedToLevel(level, x, y) return mod.class.Actor.addedToLevel(self, level, x, y) end + diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua index 2d2a89ad2055ad2b72513dc491f236902f15fa5b..4fd450625ebf65f4e764fe17b0007e3a7bd221da 100644 --- a/game/modules/tome/class/Player.lua +++ b/game/modules/tome/class/Player.lua @@ -119,7 +119,7 @@ function _M:onEnterLevel(zone, level) for eff_id, p in pairs(self.tmp) do if self.tempeffect_def[eff_id].cancel_on_level_change then effs[#effs+1] = eff_id - if type(self.tempeffect_def[eff_id].cancel_on_level_change) == "function" then self.tempeffect_def[eff_id].cancel_on_level_change(self, p) end + if type(self.tempeffect_def[eff_id].cancel_on_level_change) == "function" then self.tempeffect_def[eff_id].cancel_on_level_change(self, p) end end end for i, eff_id in ipairs(effs) do self:removeEffect(eff_id) end @@ -389,6 +389,46 @@ function _M:doFOV() self:playerFOV() end +--- Create a line to target based on field of vision +function _M:lineFOV(tx, ty, extra_block, block, sx, sy) + sx = sx or self.x + sy = sy or self.y + local act = game.level.map(x, y, Map.ACTOR) + local sees_target = game.level.map.seens(tx, ty) + + local darkVisionRange + if self:knowTalent(self.T_DARK_VISION) then + local t = self:getTalentFromId(self.T_DARK_VISION) + darkVisionRange = self:getTalentRange(t) + end + local inCreepingDark = false + + block = block or function(_, x, y) + if darkVisionRange then + if game.level.map:checkAllEntities(x, y, "creepingDark") then + inCreepingDark = true + end + if inCreepingDark and core.fov.distance(sx, sy, x, y) > darkVisionRange then + return true + end + end + + if sees_target then + return game.level.map:checkAllEntities(x, y, "block_sight") or + game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") and not game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "pass_projectile") or + type(extra_block) == "function" and extra_block(self, x, y) + elseif core.fov.distance(sx, sy, x, y) <= self.sight and (game.level.map.remembers(x, y) or game.level.map.seens(x, y)) then + return game.level.map:checkEntity(x, y, Map.TERRAIN, "block_sight") or + game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") and not game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "pass_projectile") or + type(extra_block) == "function" and extra_block(self, x, y) + else + return true + end + end + + return core.fov.line(sx, sy, tx, ty, block) +end + --- Called before taking a hit, overload mod.class.Actor:onTakeHit() to stop resting and running function _M:onTakeHit(value, src) self:runStop("taken damage") @@ -401,7 +441,7 @@ function _M:onTakeHit(value, src) -- Hit direction warning if src.x and src.y and (self.x ~= src.x or self.y ~= src.y) then - local range = math.floor(core.fov.distance(src.x, src.y, self.x, self.y)) + local range = core.fov.distance(src.x, src.y, self.x, self.y) if range > 1 then local angle = math.atan2(src.y - self.y, src.x - self.x) game.level.map:particleEmitter(self.x, self.y, 1, "hit_warning", {angle=math.deg(angle)}) @@ -726,7 +766,7 @@ function _M:playerWear() local inven = self:getInven(self.INVEN_INVEN) local titleupdator = self:getEncumberTitleUpdator("Wield/wear object") local d d = self:showInventory(titleupdator(), inven, function(o) - return o:wornInven() and self:getInven(o:wornInven()) and true or false + return o:wornInven() and self:getInven(o:wornInven()) and true or false end, function(o, item) self:doWear(inven, item, o) d:updateTitle(titleupdator()) @@ -968,3 +1008,4 @@ function _M:on_quest_status(quest, status, sub) game.logPlayer(game.player, "#LIGHT_RED#Quest '%s' is failed! #WHITE#(Press 'j' to see the quest log)", quest.name) end end + diff --git a/game/modules/tome/class/interface/Combat.lua b/game/modules/tome/class/interface/Combat.lua index 146261f0abb05af7a00d9ebe974e26d213f11785..1364a8184b047c3e7f8d905751e158639abf84e0 100644 --- a/game/modules/tome/class/interface/Combat.lua +++ b/game/modules/tome/class/interface/Combat.lua @@ -157,7 +157,7 @@ function _M:attackTarget(target, damtype, mult, noenergy) -- Mount attack ? local mount = self:hasMount() - if mount and mount.mount.attack_with_rider and math.floor(core.fov.distance(self.x, self.y, target.x, target.y)) <= 1 then + if mount and mount.mount.attack_with_rider and core.fov.distance(self.x, self.y, target.x, target.y) <= 1 then mount.mount.actor:attackTarget(target, nil, nil, nil) end @@ -383,7 +383,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam) -- Conduit (Psi) if hitted and not target.dead and self:knowTalent(self.T_CONDUIT) and self:isTalentActive(self.T_CONDUIT) and self.use_psi_combat then - local t = self:getTalentFromId(self.T_CONDUIT) + local t = self:getTalentFromId(self.T_CONDUIT) t.do_combat(self, t, target) end @@ -481,7 +481,7 @@ local weapon_talents = { mace = Talents.T_WEAPONS_MASTERY, knife = Talents.T_KNIFE_MASTERY, whip = Talents.T_EXOTIC_WEAPONS_MASTERY, - trident= Talents.T_EXOTIC_WEAPONS_MASTERY, + trident = Talents.T_EXOTIC_WEAPONS_MASTERY, bow = Talents.T_BOW_MASTERY, sling = Talents.T_SLING_MASTERY, staff = Talents.T_STAFF_MASTERY, @@ -642,7 +642,7 @@ function _M:combatDamage(weapon) end if self.use_psi_combat then if self:knowTalent(self.T_GREATER_TELEKINETIC_GRASP) then - local g = self:getTalentFromId(self.T_GREATER_TELEKINETIC_GRASP) + local g = self:getTalentFromId(self.T_GREATER_TELEKINETIC_GRASP) totstat = totstat * g.stat_sub(self, g) else totstat = totstat * 0.6 @@ -1053,7 +1053,7 @@ function _M:buildCombo() local power = 1 -- Combo String bonuses if self:knowTalent(self.T_COMBO_STRING) then - local t= self:getTalentFromId(self.T_COMBO_STRING) + local t = self:getTalentFromId(self.T_COMBO_STRING) if rng.percent(t.getChance(self, t)) then power = 2 end @@ -1061,7 +1061,7 @@ function _M:buildCombo() end if self:knowTalent(self.T_RELENTLESS_STRIKES) then - local t= self:getTalentFromId(self.T_RELENTLESS_STRIKES) + local t = self:getTalentFromId(self.T_RELENTLESS_STRIKES) self:incStamina(t.getStamina(self, t)) end @@ -1143,3 +1143,4 @@ function _M:startGrapple(target) return false end end + diff --git a/game/modules/tome/data/talents/celestial/combat.lua b/game/modules/tome/data/talents/celestial/combat.lua index d15b88c02909eb394a601a5b9759fa4003921b0f..31e5fb0f32bab5d25b94707877b9a6054d118266 100644 --- a/game/modules/tome/data/talents/celestial/combat.lua +++ b/game/modules/tome/data/talents/celestial/combat.lua @@ -129,7 +129,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end self:attackTarget(target, DamageType.LIGHT, t.getDamage(self, t), true) return true end, @@ -139,3 +139,4 @@ newTalent{ format(100 * damage) end, } + diff --git a/game/modules/tome/data/talents/celestial/guardian.lua b/game/modules/tome/data/talents/celestial/guardian.lua index a1870bbd102ea137d93f2e57044c670e9b727c28..77152c0642b0109b78680d9bd32b0ce3dd3b5692 100644 --- a/game/modules/tome/data/talents/celestial/guardian.lua +++ b/game/modules/tome/data/talents/celestial/guardian.lua @@ -77,7 +77,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end -- First attack with weapon self:attackTarget(target, nil, t.getWeaponDamage(self, t), true) diff --git a/game/modules/tome/data/talents/chronomancy/spacetime-folding.lua b/game/modules/tome/data/talents/chronomancy/spacetime-folding.lua index 3a065f004653dff2de2efd23fe307d0394af063e..1cc31c224b088198c61139fcb7739bc107e9ac53 100644 --- a/game/modules/tome/data/talents/chronomancy/spacetime-folding.lua +++ b/game/modules/tome/data/talents/chronomancy/spacetime-folding.lua @@ -62,7 +62,7 @@ newTalent{ local tx, ty, target = self:getTarget(tg) if not tx or not ty then return nil end tx, ty = checkBackfire(self, tx, ty, t.paradox) - if math.floor(core.fov.distance(self.x, self.y, tx, ty)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, tx, ty) > self:getTalentRange(t) then return nil end if not self:canBe("teleport") or game.level.map.attrs(tx, ty, "no_teleport") or game.level.map.attrs(self.x, self.y, "no_teleport") then game.logSeen(self, "The spell fizzles!") return true @@ -74,19 +74,19 @@ newTalent{ if not target then return nil end end end - + -- checks for spacetime mastery hit bonus local power = self:combatSpellpower() if self:knowTalent(self.T_SPACETIME_MASTERY) then power = self:combatSpellpower() * 1 + (self:getTalentLevel(self.T_SPACETIME_MASTERY)/10) end - + if target:canBe("teleport") then local hit = self:checkHit(power, target:combatSpellResist() + (target:attr("continuum_destabilization") or 0)) if not hit then game.logSeen(target, "The spell fizzles!") return true - else + else self:project(tg, tx, ty, DamageType.CONFUSION, { dur = t.getConfuseDuration(self, t), dam = t.getConfuseEfficency(self, t), @@ -168,12 +168,12 @@ newTalent{ if not x or not y then return nil end x, y = checkBackfire(self, x, y, t.paradox) local _ _, x, y = self:canProject(tg, x, y) - + if not self:canBe("teleport") or game.level.map.attrs(x, y, "no_teleport") then game.logSeen(self, "The spell fizzles!") return true end - + if self:hasLOS(x, y) and not game.level.map:checkEntity(x, y, Map.TERRAIN, "block_move") then local dam = self:spellCrit(t.getDamage(self, t)) self:project(tg, x, y, DamageType.TEMPORALSTUN, dam) @@ -187,7 +187,7 @@ newTalent{ game.logSeen(self, "You can't move there.") return nil end - + return true end, info = function(self, t) @@ -235,3 +235,4 @@ newTalent{ format (damage*100) end, }]=] + diff --git a/game/modules/tome/data/talents/corruptions/scourge.lua b/game/modules/tome/data/talents/corruptions/scourge.lua index 2b309cdc584fc85d207aea158fd4969f53f4038b..05154b40d72e3e82865011d6d28d8d1c87d284b8 100644 --- a/game/modules/tome/data/talents/corruptions/scourge.lua +++ b/game/modules/tome/data/talents/corruptions/scourge.lua @@ -37,7 +37,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local speed1, hit1 = self:attackTargetWith(target, weapon.combat, nil, self:combatTalentWeaponDamage(t, 0.8, 1.6)) local speed2, hit2 = self:attackTargetWith(target, offweapon.combat, nil, self:getOffHandMult(self:combatTalentWeaponDamage(t, 0.8, 1.6))) @@ -119,7 +119,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local speed1, hit1 = self:attackTargetWith(target, weapon.combat, DamageType.ACID, self:combatTalentWeaponDamage(t, 0.8, 1.6)) local speed2, hit2 = self:attackTargetWith(target, offweapon.combat, DamageType.ACID, self:getOffHandMult(self:combatTalentWeaponDamage(t, 0.8, 1.6))) @@ -161,7 +161,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local speed1, hit1 = self:attackTargetWith(target, weapon.combat, DamageType.DARKNESS, self:combatTalentWeaponDamage(t, 0.6, 1.4)) @@ -181,3 +181,4 @@ newTalent{ format(100 * self:combatTalentWeaponDamage(t, 0.6, 1.4), 100 * self:combatTalentWeaponDamage(t, 0.6, 1.4)) end, } + diff --git a/game/modules/tome/data/talents/cunning/ambush.lua b/game/modules/tome/data/talents/cunning/ambush.lua index 5816214bc0742fa3e14e22738f5260fc8acacb70..cf2779cf6c2f4f306b7a6ccceec239fc5eab6720 100644 --- a/game/modules/tome/data/talents/cunning/ambush.lua +++ b/game/modules/tome/data/talents/cunning/ambush.lua @@ -35,7 +35,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end if target:checkHit(self:combatAttackDex(), target:combatPhysicalResist(), 0, 95, 5) and target:canBe("disarm") then target:setEffect(target.EFF_DISARMED, t.getDuration(self, t), {}) @@ -78,7 +78,7 @@ newTalent{ target:move(sx, sy, true) - if math.floor(core.fov.distance(self.x, self.y, sx, sy)) <= 1 then + if core.fov.distance(self.x, self.y, sx, sy) <= 1 then if target:checkHit(self:combatAttackDex(), target:combatMentalResist(), 0, 95, 5) and target:canBe("silence") then target:setEffect(target.EFF_SILENCED, t.getDuration(self, t), {}) else @@ -219,3 +219,4 @@ newTalent{ format(duration, res, 100 * damage) end, } + diff --git a/game/modules/tome/data/talents/cunning/dirty.lua b/game/modules/tome/data/talents/cunning/dirty.lua index 81371b9b30549cc54feeb10863866d0d07bb3d17..acad29bfdd2336059e91da9ab5fa02067194eaf4 100644 --- a/game/modules/tome/data/talents/cunning/dirty.lua +++ b/game/modules/tome/data/talents/cunning/dirty.lua @@ -35,7 +35,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hitted = self:attackTarget(target, nil, t.getDamage(self, t), true) if hitted then @@ -85,7 +85,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local tx, ty, sx, sy = target.x, target.y, self.x, self.y local hitted = self:attackTarget(target, nil, 0, true) @@ -129,7 +129,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hitted = self:attackTarget(target, nil, t.getDamage(self, t), true) if hitted then @@ -159,3 +159,4 @@ newTalent{ format(100 * damage, duration, attackpen, damagepen) end, } + diff --git a/game/modules/tome/data/talents/cunning/lethality.lua b/game/modules/tome/data/talents/cunning/lethality.lua index 4c99f44ef891d671573a7f3eb09923527d62704c..d4a882a81daa3d1fd698e840e5e95fe34e5b1a96 100644 --- a/game/modules/tome/data/talents/cunning/lethality.lua +++ b/game/modules/tome/data/talents/cunning/lethality.lua @@ -56,7 +56,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hitted = self:attackTarget(target, nil, t.getDamage(self, t), true) if hitted then @@ -115,7 +115,7 @@ newTalent{ local tids = {} for tid, _ in pairs(self.talents_cd) do local tt = self:getTalentFromId(tid) - if tt.type[2] <= t.getMaxLevel(self, t) and (tt.type[1]:find("^cunning/") or tt.type[1]:find("^technique/")) then + if tt.type[2] <= t.getMaxLevel(self, t) and (tt.type[1]:find("^cunning/") or tt.type[1]:find("^technique/")) then tids[#tids+1] = tid end end @@ -134,3 +134,4 @@ newTalent{ format(talentcount, maxlevel) end, } + diff --git a/game/modules/tome/data/talents/cunning/poisons.lua b/game/modules/tome/data/talents/cunning/poisons.lua index ae5ea3b8bc6deae867f2de819fd5b3ebadce1423..043472e38bf4094a594d1a67b756d52fa48a1381 100644 --- a/game/modules/tome/data/talents/cunning/poisons.lua +++ b/game/modules/tome/data/talents/cunning/poisons.lua @@ -89,7 +89,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local nb = 0 for eff_id, p in pairs(target.tmp) do @@ -130,7 +130,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local mod = (100 + self:combatTalentStatDamage(t, "cun", 40, 250)) / 100 for eff_id, p in pairs(target.tmp) do @@ -380,3 +380,4 @@ newTalent{ format(damDesc(self, DamageType.NATURE, t.getDOT(self, t)), t.getDuration(self, t), t.getEffect(self, t)) end, } + diff --git a/game/modules/tome/data/talents/cunning/shadow-magic.lua b/game/modules/tome/data/talents/cunning/shadow-magic.lua index 38c705ed800e1496e5246a31060a45d28a7c2c24..dd52df29a5de907a8dbbba838e54d86fb4f1deba 100644 --- a/game/modules/tome/data/talents/cunning/shadow-magic.lua +++ b/game/modules/tome/data/talents/cunning/shadow-magic.lua @@ -103,14 +103,14 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end if not game.level.map.seens(x, y) then return nil end local tx, ty = util.findFreeGrid(x, y, 20, true, {[engine.Map.ACTOR]=true}) self:move(tx, ty, true) -- Attack ? - if math.floor(core.fov.distance(self.x, self.y, x, y)) == 1 then + if core.fov.distance(self.x, self.y, x, y) == 1 then self:attackTarget(target, DamageType.DARKNESS, t.getDamage(self, t), true) if target:canBe("stun") then target:setEffect(target.EFF_DAZED, t.getDuration(self, t), {}) @@ -128,3 +128,4 @@ newTalent{ format(duration, t.getDamage(self, t) * 100) end, } + diff --git a/game/modules/tome/data/talents/cursed/dark-figure.lua b/game/modules/tome/data/talents/cursed/dark-figure.lua index 37ac541722660ba4c8cb37c6818d74b02ddc5fa1..185e6783a1fa4798dc03c1b50f0be8fdab545b65 100644 --- a/game/modules/tome/data/talents/cursed/dark-figure.lua +++ b/game/modules/tome/data/talents/cursed/dark-figure.lua @@ -38,7 +38,7 @@ newTalent{ local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then game.logPlayer(self, "You are too far to from the target!") return nil end @@ -136,4 +136,3 @@ newTalent{ end, } - diff --git a/game/modules/tome/data/talents/cursed/darkness.lua b/game/modules/tome/data/talents/cursed/darkness.lua index 7aa69f70202b87f2d4765659f2843c4ebf7c85c0..f7ee2c2facade305827cb7de193b7697b85bd140 100644 --- a/game/modules/tome/data/talents/cursed/darkness.lua +++ b/game/modules/tome/data/talents/cursed/darkness.lua @@ -342,17 +342,6 @@ newTalent{ getDamageIncrease = function(self, t) return combatTalentDamage(self, t, 0, 40) end, - hasLOS = function(x1, y1, x2, y2) - local l = line.new(x1, y1, x2, y2) - local lx, ly = l() - while lx and ly do - local entity = game.level.map:checkAllEntities(lx, ly, "block_sight") - if entity and not game.level.map:checkAllEntities(lx, ly, "creepingDark") then return false end - - lx, ly = l() - end - return true - end, info = function(self, t) local damageIncrease = t.getDamageIncrease(self, t) return ([[Your eyes penetrate the darkness to find anyone that may be hiding there. You can also see through your clouds of creeping dark and gain the advantage of doing %d%% more damage to anyone enveloped by it.]]):format(damageIncrease) @@ -418,7 +407,7 @@ newTalent{ points = 5, random_ego = "attack", cooldown = 10, - hate = 1.2, + hate = 1.2, range = 6, tactical = { ATTACK = 2, DISABLE = 2 }, direct_hit = true, @@ -451,3 +440,4 @@ newTalent{ The damage will increase with the Magic stat.]]):format(pinDuration, damage) end, } + diff --git a/game/modules/tome/data/talents/cursed/endless-hunt.lua b/game/modules/tome/data/talents/cursed/endless-hunt.lua index 93be59e19830c1a82869c5e4f15246a3108bacc8..fcded949b698c1a4eee49f6445e8cac93f10b908 100644 --- a/game/modules/tome/data/talents/cursed/endless-hunt.lua +++ b/game/modules/tome/data/talents/cursed/endless-hunt.lua @@ -53,7 +53,7 @@ newTalent{ local target = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(target) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end -- attempt domination if checkWillFailure(self, target, 50, 90, 1) then @@ -104,7 +104,7 @@ newTalent{ local tg = {type="hit", pass_terrain = true, range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end local start = rng.range(0, 8) for i = start, start + 8 do @@ -145,7 +145,7 @@ newTalent{ local x, y, target = self:getTarget(tg) if not x or not y or not target or target == self then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then game.logPlayer(self, "You are too far to begin stalking that prey!") return nil end @@ -196,3 +196,4 @@ newTalent{ -- return ([[Focusing your hate you blast your target for %d blight damage. Damage increases with willpower.]]):format(damage) -- end, --} + diff --git a/game/modules/tome/data/talents/cursed/force-of-will.lua b/game/modules/tome/data/talents/cursed/force-of-will.lua index 094b10fd67eafed1882933ac5728199e46a6fe2b..8976595e4bcb7502a48cea49681ef8ffc60e97b1 100644 --- a/game/modules/tome/data/talents/cursed/force-of-will.lua +++ b/game/modules/tome/data/talents/cursed/force-of-will.lua @@ -42,15 +42,16 @@ local function forceHit(self, target, sourceX, sourceY, damage, knockback, knock sourceY = sourceY + dir_to_coord[newDirection][2] end - local lineFunction = line.new(sourceX, sourceY, target.x, target.y, true) + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", target) end + local lineFunction = core.fov.line(sourceX, sourceY, target.x, target.y, block_actor, true) local finalX, finalY = target.x, target.y local knockbackCount = 0 local blocked = false while knockback > 0 do blocked = true - local x, y = lineFunction(true) + local x, y, is_corner_blocked = lineFunction:step(block_actor, true) - if not game.level.map:isBound(x, y) or game.level.map:checkAllEntities(x, y, "block_move", target) then + if not game.level.map:isBound(x, y) or is_corner_blocked or game.level.map:checkAllEntities(x, y, "block_move", target) then -- blocked local nextTarget = game.level.map(x, y, Map.ACTOR) if nextTarget then @@ -121,7 +122,7 @@ newTalent{ local x, y, target = self:getTarget(tg) if not x or not y or not target or core.fov.distance(self.x, self.y, x, y) > range then return nil end - --local distance = math.max(1, math.floor(core.fov.distance(self.x, self.y, x, y))) + --local distance = math.max(1, core.fov.distance(self.x, self.y, x, y)) local power = 1 --(1 - ((distance - 1) / range)) local damage = t.getDamage(self, t) * power local knockback = t.getKnockback(self, t) @@ -245,7 +246,7 @@ newTalent{ -- your will ignores friendly targets (except for knockback hits) local target = game.level.map(x, y, Map.ACTOR) if target and self:reactionToward(target) < 0 then - local distance = math.floor(core.fov.distance(blastX, blastY, x, y)) + local distance = core.fov.distance(blastX, blastY, x, y) local power = (1 - (distance / radius)) local localDamage = damage * power local dazeDuration = t.getDazeDuration(self, t) @@ -355,3 +356,4 @@ newTalent{ end end, } + diff --git a/game/modules/tome/data/talents/cursed/shadows.lua b/game/modules/tome/data/talents/cursed/shadows.lua index d575a7e735d450ded1abca9b634d05aa9a5419b8..76219086515fe84a4829ae8647212b0dbd5d8d48 100644 --- a/game/modules/tome/data/talents/cursed/shadows.lua +++ b/game/modules/tome/data/talents/cursed/shadows.lua @@ -57,7 +57,7 @@ newTalent{ local tg = {type="hit", pass_terrain = true, range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end local start = rng.range(0, 8) for i = start, start + 8 do @@ -560,3 +560,4 @@ newTalent{ return ([[Instill hate in your shadows, strengthening their attacks. They gain %d%% extra accuracy and %d%% extra damage. The fury of their attacks gives them the ability to try to Dominate their foes, increasing all damage taken by that foe for 4 turns. (%d%% chance at range 1)]]):format(combatAtk, incDamage, dominateChance) end, } + diff --git a/game/modules/tome/data/talents/cursed/slaughter.lua b/game/modules/tome/data/talents/cursed/slaughter.lua index fb823f6b00dc3fb0303460386d9a8d797f07d9a6..49893170eaf7014151f6cc841be84c5299f67f21 100644 --- a/game/modules/tome/data/talents/cursed/slaughter.lua +++ b/game/modules/tome/data/talents/cursed/slaughter.lua @@ -40,7 +40,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local multiplier = 1 + (0.17 + .23 * self:getTalentLevel(t)) * getHateMultiplier(self, 0, 1) local hit = self:attackTarget(target, nil, multiplier, true) @@ -121,13 +121,14 @@ newTalent{ local targeting = {type="bolt", range=self:getTalentRange(t), nolock=true} local targetX, targetY, actualTarget = self:getTarget(targeting) if not targetX or not targetY then return nil end - if math.floor(core.fov.distance(self.x, self.y, targetX, targetY)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, targetX, targetY) > self:getTalentRange(t) then return nil end - local lineFunction = line.new(self.x, self.y, targetX, targetY) - local nextX, nextY = lineFunction() + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", target) end + local lineFunction = core.fov.line(self.x, self.y, targetX, targetY, block_actor) + local nextX, nextY, is_corner_blocked = lineFunction:step(block_actor) local currentX, currentY = self.x, self.y - while nextX and nextY do + while nextX and nextY and not is_corner_blocked do local blockingTarget = game.level.map(nextX, nextY, Map.ACTOR) if blockingTarget and self:reactionToward(blockingTarget) < 0 then -- attempt a knockback @@ -147,7 +148,7 @@ newTalent{ for i = start, start + 8 do local x = nextX + (i % 3) - 1 local y = nextY + math.floor((i % 9) / 3) - 1 - if math.floor(core.fov.distance(currentY, currentX, x, y)) > 1 + if core.fov.distance(currentY, currentX, x, y) > 1 and game.level.map:isBound(x, y) and not game.level.map:checkAllEntities(x, y, "block_move", self) then blockingTarget:move(x, y, true) @@ -169,7 +170,7 @@ newTalent{ -- allow the move currentX, currentY = nextX, nextY - nextX, nextY = lineFunction() + nextX, nextY, is_corner_blocked = lineFunction:step(block_actor) -- attack adjacent targets for i = 0, 8 do @@ -247,3 +248,4 @@ newTalent{ Requires a one- or two-handed axe or a cursed weapon.]]):format(chance, multiplier * 50, multiplier * 100) end, } + diff --git a/game/modules/tome/data/talents/cursed/strife.lua b/game/modules/tome/data/talents/cursed/strife.lua index 6f32f5bdd4a97440c47e42fc55d3c756c7e6d4dd..bdda4f84318e51187d52093e0e81c3115c8c24b7 100644 --- a/game/modules/tome/data/talents/cursed/strife.lua +++ b/game/modules/tome/data/talents/cursed/strife.lua @@ -49,12 +49,12 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local damagePercent = t.getDamagePercent(self, t) local poisonDamage = t.getPoisonDamage(self, t) local duration = t.getDuration(self, t) - + local hit = self:attackTarget(target, nil, damagePercent / 100, true) if hit and target:canBe("poison") then target:setEffect(target.EFF_POISONED, duration, {src=self, power=poisonDamage / duration}) @@ -93,18 +93,18 @@ newTalent{ game.logPlayer(self, "You cannot use %s without an axe or a cursed weapon!", t.name) return nil end - + local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local damagePercent = t.getDamagePercent(self, t) local distance = t.getDistance(self, t) - + local hit = self:attackTarget(target, nil, damagePercent / 100, true) self:knockback(target.x, target.y, distance) - + return true end, info = function(self, t) @@ -136,20 +136,20 @@ newTalent{ game.logPlayer(self, "You cannot use %s without an axe or a cursed weapon!", t.name) return nil end - + local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local damagePercent = t.getDamagePercent(self, t) local duration = t.getDuration(self, t) - + local hit = self:attackTarget(target, nil, damagePercent / 100, true) if hit and target:canBe("stun") then target:setEffect(target.EFF_STUNNED, duration, {}) end - + return true end, info = function(self, t) @@ -182,35 +182,23 @@ newTalent{ getConfuseEfficiency = function(self, t) return 50 + self:getTalentLevelRaw(t) * 10 end, - hasLOS = function(startX, startY, x, y) - local l = line.new(startX, startY, x, y) - local lx, ly = l() - while lx and ly do - if game.level.map:checkAllEntities(lx, ly, "block_sight") then break end - - lx, ly = l() - end - if not lx and not ly then return true end - if lx == x and ly == y then return true end - return false - end, action = function(self, t) if not self:hasAxeWeapon() and not self:hasCursedWeapon() then game.logPlayer(self, "You cannot use %s without an axe or a cursed weapon!", t.name) return nil end - + local damagePercent = t.getDamagePercent(self, t) local attackCount = t.getAttackCount(self, t) local confuseDuration = t.getConfuseDuration(self, t) local confuseEfficiency = t.getConfuseEfficiency(self, t) - + local minDistance = 1 local maxDistance = 4 local startX, startY = self.x, self.y local positions = {} local targets = {} - + -- find all positions and targets in range for x = startX - maxDistance, startX + maxDistance do for y = startY - maxDistance, startY + maxDistance do @@ -219,26 +207,26 @@ newTalent{ and core.fov.distance(startX, startY, x, y) >= minDistance and self:hasLOS(x, y) then if self:canMove(x, y) then positions[#positions + 1] = {x, y} end - + local target = game.level.map(x, y, Map.ACTOR) if target and target ~= self and self:reactionToward(target) < 0 then targets[#targets + 1] = target end end end end - + -- perform confusion for i = 1, #targets do self:project({type="hit",x=targets[i].x,y=targets[i].y}, targets[i].x, targets[i].y, DamageType.CONFUSION, { dur = confuseDuration, dam = confuseEfficiency }) end - + -- perform attacks for i = 1, attackCount do if #targets == 0 then break end - + local target = rng.tableRemove(targets) local hit = self:attackTarget(target, nil, damagePercent / 100, true) end - + -- perform movements if #positions > 0 then for i = 1, 8 do @@ -250,11 +238,11 @@ newTalent{ end end end - + game.level.map:particleEmitter(currentX, currentY, 1, "teleport_in") local position = positions[rng.range(1, #positions)] self:move(position[1], position[2], true) - + return true end, info = function(self, t) @@ -262,8 +250,9 @@ newTalent{ local attackCount = t.getAttackCount(self, t) local confuseDuration = t.getConfuseDuration(self, t) local confuseEfficiency = t.getConfuseEfficiency(self, t) - + return ([[With unnatural speed you assail all foes in sight within a range of 4 with wild swings from your axe. You will attack up to %d different targets for %d%% damage. When the assualt finally ends all foes in range will be confused for %d turns and you will find yourself in a nearby location. Requires a one- or two-handed axe or a cursed weapon.]]):format(attackCount, damagePercent, confuseDuration) end, } + diff --git a/game/modules/tome/data/talents/gifts/call.lua b/game/modules/tome/data/talents/gifts/call.lua index c580789a6a4523107196511be72e1020e9a0babe..9a777233a8d1e73c30fb41931968bbfb36d75d6d 100644 --- a/game/modules/tome/data/talents/gifts/call.lua +++ b/game/modules/tome/data/talents/gifts/call.lua @@ -76,7 +76,7 @@ newTalent{ short_name = "NATURE_TOUCH", local tg = {default_target=self, type="hit", nowarning=true, range=self:getTalentRange(t), first_target="friend"} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end if not target.undead then target:heal(20 + self:combatTalentStatDamage(t, "wil", 30, 500)) end @@ -149,3 +149,4 @@ newTalent{ format(math.ceil(self:getTalentLevel(t) + 2), self:getTalentLevelRaw(t)) end, } + diff --git a/game/modules/tome/data/talents/gifts/cold-drake.lua b/game/modules/tome/data/talents/gifts/cold-drake.lua index 65aad04cf91b32aefa313d5453641e6ffc492d88..50cf5d1ea7597eeb0b9666cca4e20da6f7c07f40 100644 --- a/game/modules/tome/data/talents/gifts/cold-drake.lua +++ b/game/modules/tome/data/talents/gifts/cold-drake.lua @@ -34,7 +34,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end self:attackTarget(target, DamageType.COLD, 1.4 + self:getTalentLevel(t) / 8, true) return true end, @@ -151,3 +151,4 @@ newTalent{ The damage will increase with the Strength stat]]):format(self:getTalentRadius(t), damDesc(self, DamageType.COLD, self:combatTalentStatDamage(t, "str", 30, 430))) end, } + diff --git a/game/modules/tome/data/talents/gifts/sand-drake.lua b/game/modules/tome/data/talents/gifts/sand-drake.lua index 8a2a312220e288ddb14fc348c005c59532ab38a3..d45db8bf6b4a49b5bf17c8add37f0537a1085ced 100644 --- a/game/modules/tome/data/talents/gifts/sand-drake.lua +++ b/game/modules/tome/data/talents/gifts/sand-drake.lua @@ -33,7 +33,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hit = self:attackTarget(target, DamageType.NATURE, self:combatTalentWeaponDamage(t, 1, 1.5), true) if not hit then return true end @@ -87,7 +87,7 @@ newTalent{ local radius = self:getTalentRadius(t) local dam = t.getDamage(self, t) return ([[You slam your foot onto the ground, shaking the area around you in a radius of %d. - Creatures caught by the quake will be damaged for %d and knocked back up to 4 titles away. + Creatures caught by the quake will be damaged for %d and knocked back up to 4 titles away. The terrain will also be moved around within the quake's radius. The damage will increase with the Strength stat.]]):format(radius, dam) end, @@ -150,3 +150,4 @@ newTalent{ The damage will increase with the Strength stat]]):format(self:getTalentRadius(t), damDesc(self, DamageType.PHYSICAL, damage), duration) end, } + diff --git a/game/modules/tome/data/talents/gifts/slime.lua b/game/modules/tome/data/talents/gifts/slime.lua index f8c9ad0e9638270f4588f187fa6d731aa1566be6..f4b675cbe79908e8f4eeb3a4ccaac5a7edc05e3b 100644 --- a/game/modules/tome/data/talents/gifts/slime.lua +++ b/game/modules/tome/data/talents/gifts/slime.lua @@ -33,7 +33,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end self.combat_apr = self.combat_apr + 1000 self:attackTarget(target, DamageType.POISON, 1.5 + self:getTalentLevel(t) / 4, true) self.combat_apr = self.combat_apr - 1000 @@ -149,3 +149,4 @@ newTalent{ The process is quite a strain on your body and all your talents will be put on cooldown for %d turns.]]):format(range, radius, duration) end, } + diff --git a/game/modules/tome/data/talents/misc/horrors.lua b/game/modules/tome/data/talents/misc/horrors.lua index bf85746a1ec558080fc744e0c888c5ff10011f2a..b7fd0b139aa7de2999d5a117c6f541d911a9bfb0 100644 --- a/game/modules/tome/data/talents/misc/horrors.lua +++ b/game/modules/tome/data/talents/misc/horrors.lua @@ -41,7 +41,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 1, 1.7), true) if hit then @@ -71,20 +71,20 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end - local l = line.new(self.x, self.y, x, y) - local lx, ly = l() - local tx, ty = lx, ly - lx, ly = l() + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end + local l = self:lineFOV(x, y, block_actor) + local lx, ly, is_corner_blocked = l:step(block_actor) + local tx, ty, _ = lx, ly while lx and ly do - if game.level.map:checkEntity(lx, ly, Map.TERRAIN, "block_move", self) then break end + if is_corner_blocked or block_actor(_, lx, ly) then break end tx, ty = lx, ly - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_actor) end -- Find space - if game.level.map:checkEntity(tx, ty, Map.TERRAIN, "block_move", self) then return nil end + if block_actor(_, tx, ty) then return nil end local fx, fy = util.findFreeGrid(tx, ty, 5, true, {[Map.ACTOR]=true}) if not fx then return @@ -136,7 +136,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.5, 1), true) if target:checkHit(self:combatAttackStr(), target:combatPhysicalResist(), 0, 95, 8 - self:getTalentLevel(t) / 2) and target:canBe("cut") then @@ -364,7 +364,7 @@ newTalent{ if not target then return nil end if target:checkHit(self:combatMindpower(), target:combatMentalResist(), 0, 95, 15) and target:canBe("fear") then - target:setEffect(target.EFF_VOID_ECHOES, 6, {src= self, power=t.getDamage(self, t)}) + target:setEffect(target.EFF_VOID_ECHOES, 6, {src=self, power=t.getDamage(self, t)}) else game.logSeen(target, "%s resists the void!", target.name:capitalize()) end @@ -511,3 +511,4 @@ newTalent{ return ([[A terrible rotting disease that removes a beneficial physical effect and deals acid and blight damage each turn. If not cleared after a full five turn duration it will inflict extra damage and spawn a carrion worm mass.]]) end, } + diff --git a/game/modules/tome/data/talents/misc/inscriptions.lua b/game/modules/tome/data/talents/misc/inscriptions.lua index a738ef27813e580ef11bc88b2ec513285197541c..e86b23805a1a654d1fad2bde0198412402861b13 100644 --- a/game/modules/tome/data/talents/misc/inscriptions.lua +++ b/game/modules/tome/data/talents/misc/inscriptions.lua @@ -280,7 +280,7 @@ newInscription{ local data = self:getInscriptionData(t.short_name) local damage = t.getDamage(self, t) return ([[Causes thick vines to spring from the ground and entangle all targets within %d squares for %d turns, pinning them in place and dealing %0.2f physical damage and %0.2f nature damage each turn.]]): - format(self:getTalentRadius(t), data.dur, damDesc(self, DamageType.PHYSICAL, damage)/3, damDesc(self, DamageType.Nature, 2*damage)/3) + format(self:getTalentRadius(t), data.dur, damDesc(self, DamageType.PHYSICAL, damage)/3, damDesc(self, DamageType.NATURE, 2*damage)/3) end, short_info = function(self, t) local data = self:getInscriptionData(t.short_name) @@ -586,7 +586,7 @@ newInscription{ return data.range end, target = function(self, t) - return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false, talent=t} + return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false, talent=t} end, action = function(self, t) local data = self:getInscriptionData(t.short_name) @@ -874,3 +874,4 @@ newInscription{ return ([[Range %d telepathy for %d turns]]):format(self:getTalentRange(t), data.dur) end, } + diff --git a/game/modules/tome/data/talents/misc/npcs.lua b/game/modules/tome/data/talents/misc/npcs.lua index 9724983766efacb78e38730c4cc730d7c0ed0e2c..da8f82400f99d6e22d265e10d9e0cbf4d8086a75 100644 --- a/game/modules/tome/data/talents/misc/npcs.lua +++ b/game/modules/tome/data/talents/misc/npcs.lua @@ -40,7 +40,7 @@ newTalent{ requires_target = true, tactical = { ATTACK = 3 }, action = function(self, t) - if not self.can_multiply or self.can_multiply <= 0 then print("no more multiply") return nil end + if not self.can_multiply or self.can_multiply <= 0 then print("no more multiply") return nil end -- Find space local x, y = util.findFreeGrid(self.x, self.y, 1, true, {[Map.ACTOR]=true}) @@ -82,7 +82,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end self.combat_apr = self.combat_apr + 1000 self:attackTarget(target, DamageType.POISON, 2 + self:getTalentLevel(t), true) self.combat_apr = self.combat_apr - 1000 @@ -107,7 +107,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end self.combat_apr = self.combat_apr + 1000 self:attackTarget(target, DamageType.ACID, self:combatTalentWeaponDamage(t, 1, 1.8), true) self.combat_apr = self.combat_apr - 1000 @@ -132,7 +132,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end self.combat_apr = self.combat_apr + 1000 self:attackTarget(target, DamageType.BLIND, self:combatTalentWeaponDamage(t, 0.8, 1.4), true) self.combat_apr = self.combat_apr - 1000 @@ -157,7 +157,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end self.combat_apr = self.combat_apr + 1000 self:attackTarget(target, DamageType.POISON, 2 + self:getTalentLevel(t), true) self.combat_apr = self.combat_apr - 1000 @@ -181,7 +181,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.5, 1), true) -- Try to stun ! @@ -213,7 +213,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.5, 1), true) if hit and target:checkHit(self:combatAttackStr(), target:combatPhysicalResist(), 0, 95, 5 - self:getTalentLevel(t) / 2) and target:canBe("disarm") then @@ -242,7 +242,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.5, 1), true) -- Try to stun ! @@ -274,7 +274,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 1.5, 2), true) -- Try to knockback ! @@ -307,7 +307,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end self:attackTarget(target, DamageType.POISON, 2 + self:getTalentLevel(t), true) return true end, @@ -389,7 +389,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.5, 1), true) -- Try to rot ! @@ -420,7 +420,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.5, 1), true) -- Try to rot ! @@ -451,7 +451,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.5, 1), true) -- Try to rot ! @@ -647,7 +647,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.8, 1.4), true) -- Try to stun ! @@ -773,16 +773,16 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end - local l = line.new(self.x, self.y, x, y) - local lx, ly = l() - local tx, ty = self.x, self.y - lx, ly = l() + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end + local l = self:lineFOV(x, y, block_actor) + local lx, ly, is_corner_blocked = l:step(block_actor) + local tx, ty = lx, ly while lx and ly do - if game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end + if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end tx, ty = lx, ly - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_actor) end local ox, oy = self.x, self.y @@ -793,7 +793,7 @@ newTalent{ end -- Attack ? - if math.floor(core.fov.distance(self.x, self.y, x, y)) == 1 and target:canBe("pin") then + if core.fov.distance(self.x, self.y, x, y) == 1 and target:canBe("pin") then target:setEffect(target.EFF_PINNED, 5, {}) end @@ -1038,7 +1038,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local speed, hit = self:attackTargetWith(target, weapon.combat, nil, self:combatTalentWeaponDamage(t, 1, 1.4)) -- Try to stun ! @@ -1226,7 +1226,7 @@ newTalent{ local tg = {type="bolt", range=1} local x, y, target = self:getTarget(tg) if not x or not y then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end self:project(tg, x, y, DamageType.LIGHT, math.floor(self:combatSpellpower(0.25) * self:getTalentLevel(t)), {type="light"}) game.level.map:particleEmitter(self.x, self.y, 1, "ball_fire", {radius = 1, r = 1, g = 0, b = 0}) self:die(self) @@ -1251,7 +1251,7 @@ newTalent{ local tg = {type="bolt", range=1} local x, y, target = self:getTarget(tg) if not x or not y then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end self:project(tg, x, y, DamageType.COLD, self.will_o_wisp_dam or 1) game.level.map:particleEmitter(self.x, self.y, 1, "ball_ice", {radius = 1, r = 1, g = 0, b = 0}) self:die(self) @@ -1448,10 +1448,10 @@ newTalent{ end end end) - + game.level.map:particleEmitter(self.x, self.y, tg.radius, "ball_light", {radius=tg.radius}) game:playSoundNear(self, "talents/arcane") - + return true end, info = function(self, t) @@ -1565,3 +1565,4 @@ newTalent{ return ([[Invoke a slimy crawler.]]) end, } + diff --git a/game/modules/tome/data/talents/psionic/augmented-mobility.lua b/game/modules/tome/data/talents/psionic/augmented-mobility.lua index 8b2d58dff6e4f70188d7833c23b8e886f9d47083..0e0ee645dc2912a38eb51024fa5fc95b0f9f48eb 100644 --- a/game/modules/tome/data/talents/psionic/augmented-mobility.lua +++ b/game/modules/tome/data/talents/psionic/augmented-mobility.lua @@ -186,15 +186,17 @@ newTalent{ local _ _, x, y = self:canProject(tg, x, y) game.level.map:particleEmitter(self.x, self.y, tg.radius, "flamebeam", {tx=x-self.x, ty=y-self.y}) game:playSoundNear(self, "talents/lightning") - local l = line.new(self.x, self.y, x, y) - local lx, ly = l() - local tx, ty = self.x, self.y - lx, ly = l() + + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, engine.Map.TERRAIN, "block_move", self) end + local l = self:lineFOV(x, y, block_actor) + local lx, ly, is_corner_blocked = l:step(block_actor) + local tx, ty, _ = lx, ly while lx and ly do - if game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move", self) then break end + if is_corner_blocked or block_actor(_, lx, ly) then break end tx, ty = lx, ly - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_actor) end + --self:move(tx, ty, true) local fx, fy = util.findFreeGrid(tx, ty, 5, true, {[Map.ACTOR]=true}) if not fx then @@ -212,3 +214,4 @@ newTalent{ format(range, 2*dam/3, dam) end, } + diff --git a/game/modules/tome/data/talents/psionic/projection.lua b/game/modules/tome/data/talents/psionic/projection.lua index 0a51a29bd4c7edce7ffccd284aa766c37164c10c..a6041032f3b3e3745126628404fb30a81332a9c0 100644 --- a/game/modules/tome/data/talents/psionic/projection.lua +++ b/game/modules/tome/data/talents/psionic/projection.lua @@ -166,8 +166,8 @@ newTalent{ local x, y = self:getTarget(tg) if not x or not y then return nil end local actor = game.level.map(x, y, Map.ACTOR) - --if math.floor(core.fov.distance(self.x, self.y, x, y)) == 1 and not actor then return true end - if math.floor(core.fov.distance(self.x, self.y, x, y)) == 0 then return true end + --if core.fov.distance(self.x, self.y, x, y) == 1 and not actor then return true end + if core.fov.distance(self.x, self.y, x, y) == 0 then return true end self:project(tg, x, y, DamageType.BATTER, self:spellCrit(rng.avg(0.8*dam, dam))) local _ _, x, y = self:canProject(tg, x, y) game.level.map:particleEmitter(self.x, self.y, tg.radius, "matter_beam", {tx=x-self.x, ty=y-self.y}) @@ -283,8 +283,8 @@ newTalent{ local x, y = self:getTarget(tg) if not x or not y then return nil end local actor = game.level.map(x, y, Map.ACTOR) - --if math.floor(core.fov.distance(self.x, self.y, x, y)) == 1 and not actor then return true end - if math.floor(core.fov.distance(self.x, self.y, x, y)) == 0 then return true end + --if core.fov.distance(self.x, self.y, x, y) == 1 and not actor then return true end + if core.fov.distance(self.x, self.y, x, y) == 0 then return true end self:project(tg, x, y, DamageType.FIREBURN, self:spellCrit(rng.avg(0.8*dam, dam))) game.level.map:particleEmitter(self.x, self.y, tg.radius, "breath_fire", {radius=tg.radius, tx=x-self.x, ty=y-self.y}) game:playSoundNear(self, "talents/fire") @@ -404,7 +404,7 @@ newTalent{ local tg = {type="bolt", nolock=true, range=self:getTalentRange(t), talent=t} local fx, fy = self:getTarget(tg) if not fx or not fy then return nil end - if math.floor(core.fov.distance(self.x, self.y, fx, fy)) == 0 then return true end + if core.fov.distance(self.x, self.y, fx, fy) == 0 then return true end local nb = t.getNumSpikeTargets(self, t) local affected = {} @@ -507,3 +507,4 @@ newTalent{ end, } + diff --git a/game/modules/tome/data/talents/psionic/psi-fighting.lua b/game/modules/tome/data/talents/psionic/psi-fighting.lua index 3ae2272d9625b25f56a8fa201f11ee3370c9745f..3d7880f8e47977641e3b3c3836d97bde28adcd7b 100644 --- a/game/modules/tome/data/talents/psionic/psi-fighting.lua +++ b/game/modules/tome/data/talents/psionic/psi-fighting.lua @@ -47,7 +47,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end self.use_psi_combat = true self:attackTargetWith(target, weapon.combat, nil, self:combatTalentWeaponDamage(t, 1.8, 3)) self.use_psi_combat = false @@ -172,3 +172,4 @@ newTalent{ format(dur, targets) end, } + diff --git a/game/modules/tome/data/talents/spells/arcane-shield.lua b/game/modules/tome/data/talents/spells/arcane-shield.lua index 410cf2edd2e18772dbc0274cf11afc313897bfea..6ddc1f2ff733ca0d38fdca37a9eca7d06da74626 100644 --- a/game/modules/tome/data/talents/spells/arcane-shield.lua +++ b/game/modules/tome/data/talents/spells/arcane-shield.lua @@ -35,7 +35,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end -- First attack with both weapon & shield (since we have the Stoneshield talent) local hit = self:attackTarget(target, DamageType.ARCANE, self:combatTalentWeaponDamage(t, 0.6, (100 + self:combatTalentSpellDamage(t, 50, 300)) / 100), true) @@ -103,7 +103,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end -- First attack with both weapon & shield (since we have the Stoneshield talent) local hit1 = self:attackTarget(target, DamageType.NATURE, self:combatTalentWeaponDamage(t, 0.6, 1.6), true) @@ -159,3 +159,4 @@ newTalent{ :format(100 * self:combatTalentWeaponDamage(t, 1.3, 2.6), self:getTalentRadius(t)) end, } + diff --git a/game/modules/tome/data/talents/spells/fire.lua b/game/modules/tome/data/talents/spells/fire.lua index 691bb8a1e46f769278407db768cb09b6f003e002..9853f190b30d95b8492ec0ed7f0098f28689334a 100644 --- a/game/modules/tome/data/talents/spells/fire.lua +++ b/game/modules/tome/data/talents/spells/fire.lua @@ -100,15 +100,11 @@ newTalent{ self:project(tg, x, y, DamageType.FLAMESHOCK, {dur=t.getStunDuration(self, t), dam=self:spellCrit(t.getDamage(self, t))}) if self:attr("burning_wake") then - local l = line.new(self.x, self.y, x, y) - local lx, ly = l() - local dir = lx and coord_to_dir[lx - self.x][ly - self.y] or 6 - game.level.map:addEffect(self, self.x, self.y, 4, DamageType.INFERNO, self:attr("burning_wake"), tg.radius, - {angle=math.deg(math.atan2(y - self.y, x - self.x))}, 55, + {delta_x=x-self.x, delta_y=y-self.y}, 55, {type="inferno"}, nil, self:spellFriendlyFire() ) @@ -221,3 +217,4 @@ newTalent{ format(damDesc(self, DamageType.FIRE, damage), radius, duration) end, } + diff --git a/game/modules/tome/data/talents/spells/golem.lua b/game/modules/tome/data/talents/spells/golem.lua index ffafee10aaf98cc1335204a900b22758a96d3c90..8d462f14f90d2c7ac5fbce5320f8edd13542a3ce 100644 --- a/game/modules/tome/data/talents/spells/golem.lua +++ b/game/modules/tome/data/talents/spells/golem.lua @@ -44,30 +44,31 @@ newTalent{ local x, y, target = self:getTarget(tg) game.target.source_actor = olds if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end if self.ai_target then self.ai_target.target = target end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then - local l = line.new(self.x, self.y, x, y) - local lx, ly = l() - if game.level.map:checkAllEntities(lx, ly, "block_move", self) then + if core.fov.distance(self.x, self.y, x, y) > 1 then + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end + local l = self:lineFOV(x, y, block_actor) + local lx, ly, is_corner_blocked = l:step(block_actor) + if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then game.logPlayer(self, "You are too close to build up momentum!") return end - local tx, ty = self.x, self.y - lx, ly = l() + local tx, ty = lx, ly + lx, ly, is_corner_blocked = l:step(block_actor) while lx and ly do - if game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end + if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end tx, ty = lx, ly - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_actor) end self:move(tx, ty, true) end -- Attack ? - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return true end + if core.fov.distance(self.x, self.y, x, y) > 1 then return true end local hit = self:attackTarget(target, nil, t.getDamage(self, t), true) -- Try to knockback ! @@ -152,30 +153,31 @@ newTalent{ local x, y, target = self:getTarget(tg) game.target.source_actor = olds if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end if self.ai_target then self.ai_target.target = target end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then - local l = line.new(self.x, self.y, x, y) - local lx, ly = l() - if game.level.map:checkAllEntities(lx, ly, "block_move", self) then + if core.fov.distance(self.x, self.y, x, y) > 1 then + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end + local l = self:lineFOV(x, y, block_actor) + local lx, ly, is_corner_blocked = l:step(block_actor) + if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then game.logPlayer(self, "You are too close to build up momentum!") return end - local tx, ty = self.x, self.y - lx, ly = l() + local tx, ty = lx, ly + lx, ly, is_corner_blocked = l:step(block_actor) while lx and ly do - if game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end + if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end tx, ty = lx, ly - lx, ly = l() - end + lx, ly, is_corner_blocked = l:step(block_actor) + end self:move(tx, ty, true) end -- Attack ? - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return true end + if core.fov.distance(self.x, self.y, x, y) > 1 then return true end local hit = self:attackTarget(target, nil, t.getDamage(self, t), true) -- Try to knockback ! @@ -225,22 +227,23 @@ newTalent{ local x, y, target = self:getTarget(tg) game.target.source_actor = olds if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then - local l = line.new(self.x, self.y, x, y) - local lx, ly = l() - if game.level.map:checkAllEntities(lx, ly, "block_move", self) then + if core.fov.distance(self.x, self.y, x, y) > 1 then + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end + local l = self:lineFOV(x, y, block_actor) + local lx, ly, is_corner_blocked = l:step(block_actor) + if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then game.logPlayer(self, "You are too close to build up momentum!") return end - local tx, ty = self.x, self.y - lx, ly = l() + local tx, ty = lx, ly + lx, ly, is_corner_blocked = l:step(block_actor) while lx and ly do - if game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end + if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end tx, ty = lx, ly - lx, ly = l() - end + lx, ly, is_corner_blocked = l:step(block_actor) + end self:move(tx, ty, true) end @@ -296,15 +299,12 @@ newTalent{ local x, y = self:getTarget(tg) if not x or not y then return nil end - -- Always project the beam as far as possible - local current_angle = math.atan2((y - self.y), (x - self.x)) + math.pi - local target_x = self.x - math.floor(0.5 + (tg.range * math.cos(current_angle))) - local target_y = self.y - math.floor(0.5 + (tg.range * math.sin(current_angle))) - local l = line.new(self.x, self.y, target_x, target_y) - local lx, ly = l() - target_x, target_y = lx, ly + -- We will always project the beam as far as possible + local l = self:lineFOV(x, y) + local lx, ly, is_corner_blocked = l:step(nil, true) + local target_x, target_y = lx, ly -- Check for terrain and friendly actors - while lx and ly do + while lx and ly and not is_corner_blocked and core.fov.distance(self.x, self.y, lx, ly) <= tg.range do local actor = game.level.map(lx, ly, engine.Map.ACTOR) if actor and (self:reactionToward(actor) >= 0) then break @@ -313,7 +313,7 @@ newTalent{ break end target_x, target_y = lx, ly - lx, ly = l() + lx, ly = l:step(nil, true) end x, y = target_x, target_y @@ -490,3 +490,4 @@ newTalent{ This spell is only usable when the golem's master is dead.]]):format(rad, damDesc(self, DamageType.FIRE, 50 + 10 * self.level)) end, } + diff --git a/game/modules/tome/data/talents/spells/staff-combat.lua b/game/modules/tome/data/talents/spells/staff-combat.lua index 55099fa278f4e3ccced4d31a8a95da04f2593da7..e0a052da8bac8822dba90ed18856fe1702cbe07a 100644 --- a/game/modules/tome/data/talents/spells/staff-combat.lua +++ b/game/modules/tome/data/talents/spells/staff-combat.lua @@ -39,10 +39,10 @@ newTalent{ local block_from_range = false if typ.range and typ.start_x then local dist = core.fov.distance(typ.start_x, typ.start_y, lx, ly) - if math.floor(dist - typ.range + 0.5) > 0 then block_from_range = true end + if dist > typ.range then block_from_range = true 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 math.floor(dist - typ.range + 0.5) > 0 then block_from_range = true end + if dist > typ.range then block_from_range = true end end if a then return block_from_range or (self and self:reactionToward(a) < 0), hit, hit_radius else return block, hit, hit_radius end @@ -175,7 +175,7 @@ newTalent{ local tg = self:getTalentTarget(t) local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local speed, hit = self:attackTargetWith(target, weapon.combat, nil, t.getDamage(self, t)) -- Try to stun ! @@ -197,3 +197,4 @@ newTalent{ format(100 * damage, dazedur) end, } + diff --git a/game/modules/tome/data/talents/techniques/2hweapon.lua b/game/modules/tome/data/talents/techniques/2hweapon.lua index 4014bb5c427ab4c1baea3626892ea5e503dee823..ca630d05d5e0718def3e4b80e7c648ca7246a3d1 100644 --- a/game/modules/tome/data/talents/techniques/2hweapon.lua +++ b/game/modules/tome/data/talents/techniques/2hweapon.lua @@ -167,7 +167,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local inc = self.stamina / 2 if self:getTalentLevel(t) >= 4 then @@ -226,7 +226,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local speed, hit = self:attackTargetWith(target, weapon.combat, nil, self:combatTalentWeaponDamage(t, 1, 1.5)) -- Try to stun ! @@ -269,7 +269,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local speed, hit = self:attackTargetWith(target, weapon.combat, nil, self:combatTalentWeaponDamage(t, 1, 1.5)) -- Try to stun ! @@ -315,7 +315,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local speed, hit = self:attackTargetWith(target, weapon.combat, nil, self:combatTalentWeaponDamage(t, 1, 1.5)) -- Try to stun ! @@ -376,3 +376,4 @@ newTalent{ Each turn the bonus decreases by 2.]]):format(2 * self:getTalentLevel(t)) end, } + diff --git a/game/modules/tome/data/talents/techniques/battle-tactics.lua b/game/modules/tome/data/talents/techniques/battle-tactics.lua index f87eebc5603680b5955e6a428c6dd4c687a9079e..78b2796be2684b7656a19886b24743112c383821 100644 --- a/game/modules/tome/data/talents/techniques/battle-tactics.lua +++ b/game/modules/tome/data/talents/techniques/battle-tactics.lua @@ -64,7 +64,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 1, 1.7), true) if hit then @@ -127,3 +127,4 @@ newTalent{ format(drain) end, } + diff --git a/game/modules/tome/data/talents/techniques/combat-techniques.lua b/game/modules/tome/data/talents/techniques/combat-techniques.lua index 902042b0d1af143b3ac8e892d41fc589bea3d92b..a96554f21d5c03079eb5fd88ebf9b98fd2f498a2 100644 --- a/game/modules/tome/data/talents/techniques/combat-techniques.lua +++ b/game/modules/tome/data/talents/techniques/combat-techniques.lua @@ -67,20 +67,21 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end - local l = line.new(self.x, self.y, x, y) - local lx, ly = l() - if game.level.map:checkAllEntities(lx, ly, "block_move", self) then + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end + local l = self:lineFOV(x, y, block_actor) + local lx, ly, is_corner_blocked = l:step(block_actor) + if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then game.logPlayer(self, "You are too close to build up momentum!") return end local tx, ty = lx, ly - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_actor) while lx and ly do - if game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end + if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end tx, ty = lx, ly - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_actor) end local ox, oy = self.x, self.y @@ -91,7 +92,7 @@ newTalent{ end -- Attack ? - if math.floor(core.fov.distance(self.x, self.y, x, y)) == 1 then + if core.fov.distance(self.x, self.y, x, y) == 1 then if self:attackTarget(target, nil, 1.2, true) and target:canBe("stun") then -- Daze, no save target:setEffect(target.EFF_DAZED, 3, {}) @@ -209,3 +210,4 @@ newTalent{ return ([[You revel in the death of your foes, regaining %d stamina with each death.]]):format(self:getTalentLevel(t) * 2) end, } + diff --git a/game/modules/tome/data/talents/techniques/dualweapon.lua b/game/modules/tome/data/talents/techniques/dualweapon.lua index c876393700f566c7a072c7011bd1dcc1237f82ae..0aa630e70f1990dbdf7432e1f6b4f4b9d46b912b 100644 --- a/game/modules/tome/data/talents/techniques/dualweapon.lua +++ b/game/modules/tome/data/talents/techniques/dualweapon.lua @@ -128,7 +128,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end -- First attack with offhand local speed, hit = self:attackTargetWith(target, offweapon.combat, nil, self:getOffHandMult(self:combatTalentWeaponDamage(t, 0.7, 1.5))) @@ -177,7 +177,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.4, 1.0), true) self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.4, 1.0), true) self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.4, 1.0), true) @@ -210,7 +210,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local dir = util.getDir(x, y, self.x, self.y) if dir == 5 then return nil end @@ -279,3 +279,4 @@ newTalent{ return ([[Spin around, damaging all targets around you with both weapons for %d%%.]]):format(100 * self:combatTalentWeaponDamage(t, 1.2, 1.9)) end, } + diff --git a/game/modules/tome/data/talents/techniques/field-control.lua b/game/modules/tome/data/talents/techniques/field-control.lua index 9cc78c46605234eb44fe72e8766bfa9e10d8450f..171b1550f7feb38f795ecb2b95904237f97a2acb 100644 --- a/game/modules/tome/data/talents/techniques/field-control.lua +++ b/game/modules/tome/data/talents/techniques/field-control.lua @@ -81,7 +81,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end -- Try to knockback ! local can = function(target) @@ -128,3 +128,4 @@ newTalent{ format(math.min(90, 15 + self:getDex(10, true) * self:getTalentLevel(t))) end, } + diff --git a/game/modules/tome/data/talents/techniques/finishing-moves.lua b/game/modules/tome/data/talents/techniques/finishing-moves.lua index e10d58b9d8df1359ce97322185da4ba290bd40bc..0c64bfd95e7a37df7303f96da0b8140f3cb53495 100644 --- a/game/modules/tome/data/talents/techniques/finishing-moves.lua +++ b/game/modules/tome/data/talents/techniques/finishing-moves.lua @@ -35,17 +35,17 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end - + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end + -- breaks active grapples if the target is not grappled if target:isGrappled(self) then grappled = true else self:breakGrapples() end - + local hit = self:attackTarget(target, nil, t.getDamage(self, t), true) - + if hit then if target:checkHit(self:combatAttackStr(), target:combatPhysicalResist(), 0, 95, 5 - self:getTalentLevel(t) / 2) and target:canBe("stun") then target:setEffect(target.EFF_STUNNED, t.getDuration(self, t), {}) @@ -53,9 +53,9 @@ newTalent{ game.logSeen(target, "%s resists the stun!", target.name:capitalize()) end end - + self:clearCombo() - + return true end, info = function(self, t) @@ -84,24 +84,24 @@ newTalent{ --on_pre_use = function(self, t, silent) if not self:hasEffect(self.EFF_COMBO) then if not silent then game.logPlayer(self, "You must have a combo going to use this ability.") end return false end return true end, getDamage = function(self, t) return self:combatTalentWeaponDamage(t, 0.1, 0.8) + getStrikingStyle(self, dam) end, getAreaDamage = function(self, t) return self:combatTalentStatDamage(t, "str", 10, 300) * (1 + getStrikingStyle(self, dam)) end, - radius = function(self, t) + radius = function(self, t) return 1 + math.floor(self:getTalentLevel(t) / 4) end, action = function(self, t) local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end - + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end + -- breaks active grapples if the target is not grappled if target:isGrappled(self) then grappled = true else self:breakGrapples() end - + local hit = self:attackTarget(target, nil, t.getDamage(self, t), true) - + if hit then local tg = {type="ball", range=1, radius=self:getTalentRadius(t), selffire=false, talent=t} local damage = t.getAreaDamage(self, t) * (0.25 + (self:getCombo(combo) /5)) @@ -109,9 +109,9 @@ newTalent{ game.level.map:particleEmitter(x, y, tg.radius, "ball_earth", {radius=tg.radius}) game:playSoundNear(self, "talents/breath") end - + self:clearCombo() - + return true end, info = function(self, t) @@ -145,17 +145,17 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end - + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end + -- breaks active grapples if the target is not grappled if target:isGrappled(self) then grappled = true else self:breakGrapples() end - + local hit = self:attackTarget(target, nil, t.getDamage(self, t), true) - + if hit then -- try to daze if target:checkHit(self:combatAttackStr(), target:combatPhysicalResist(), 0, 95, 5 - self:getTalentLevel(t) / 2) and target:canBe("stun") then @@ -163,13 +163,13 @@ newTalent{ else game.logSeen(target, "%s resists the body shot!", target.name:capitalize()) end - + target:incStamina(- t.getDrain(self, t)) - + end - + self:clearCombo() - + return true end, info = function(self, t) @@ -203,19 +203,19 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end - + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end + -- breaks active grapples if the target is not grappled if target:isGrappled(self) then grappled = true else self:breakGrapples() end - + local damage = t.getDamage(self, t) + (t.getBonusDamage(self, t) or 0) - + local hit = self:attackTarget(target, nil, damage, true) - + -- Try to insta-kill if hit then if target:checkHit(self:combatAttackStr(), target:combatPhysicalResist(), 0, 95, 5 - self:getTalentLevel(t) / 2) and target:canBe("instakill") and target.life > 0 and target.life < target.max_life * 0.2 then @@ -226,14 +226,14 @@ newTalent{ game.logSeen(target, "%s resists the death blow!", target.name:capitalize()) end end - + -- restore stamina if target.dead then self:incStamina(t.getStamina(self, t)) end - + self:clearCombo() - + return true end, info = function(self, t) @@ -245,4 +245,5 @@ newTalent{ Using this talent removes your combo points.]]) :format(damage, stamina, staminamax) end, -} \ No newline at end of file +} + diff --git a/game/modules/tome/data/talents/techniques/grappling.lua b/game/modules/tome/data/talents/techniques/grappling.lua index b783a2fb183a2d2357e40f6cbe7f3564fb9be85d..72dcf074bd65e8b82985c1ca88883f4c4e660cd2 100644 --- a/game/modules/tome/data/talents/techniques/grappling.lua +++ b/game/modules/tome/data/talents/techniques/grappling.lua @@ -78,7 +78,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local grappled = false @@ -147,7 +147,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local grappled = false @@ -162,7 +162,7 @@ newTalent{ if self:grappleSizeCheck(target) then return true end - + -- start the grapple; this will automatically hit and reapply the grapple if we're already grappling the target local hit = self:startGrapple (target) -- deal damage and maim if appropriate @@ -224,18 +224,19 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end local grappled = false -- do the rush - local l = line.new(self.x, self.y, x, y) - local tx, ty = self.x, self.y - lx, ly = l() + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end + local l = self:lineFOV(x, y, block_actor) + local lx, ly, is_corner_blocked = l:step(block_actor) + local tx, ty = lx, ly while lx and ly do - if game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end + if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end tx, ty = lx, ly - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_actor) end local ox, oy = self.x, self.y @@ -252,12 +253,12 @@ newTalent{ self:breakGrapples() end - if math.floor(core.fov.distance(self.x, self.y, x, y)) == 1 then + if core.fov.distance(self.x, self.y, x, y) == 1 then -- end the talent without effect if the target is to big if self:grappleSizeCheck(target) then return true end - + -- start the grapple; this will automatically hit and reapply the grapple if we're already grappling the target local hit = self:startGrapple (target) -- takedown or slam as appropriate @@ -285,9 +286,10 @@ newTalent{ info = function(self, t) local duration = t.getDuration(self, t) local takedown = t.getTakeDown(self, t) - local slam = t.getSlam(self, t) + local slam = t.getSlam(self, t) return ([[Rushes forward and attempts to take the target to the ground, starting a grapple, inflicting %0.2f physical damage, and dazing the target for %d turns. If you're already grappling the target you'll instead slam them into the ground for %0.2f physical damage and potentially stun them for %d turns. The grapple effects and duration will be based off your grapple talent effect if you have it and the damage will scale with the strength stat.]]) :format(damDesc(self, DamageType.PHYSICAL, (takedown)), duration, damDesc(self, DamageType.PHYSICAL, (slam)), duration) end, } + diff --git a/game/modules/tome/data/talents/techniques/kick-boxing.lua b/game/modules/tome/data/talents/techniques/kick-boxing.lua index b204d591587064677689158b745d8fbb4cc67ee6..8763b48d0832e54a3b494dd1a1efb9e4e6ee995d 100644 --- a/game/modules/tome/data/talents/techniques/kick-boxing.lua +++ b/game/modules/tome/data/talents/techniques/kick-boxing.lua @@ -32,11 +32,11 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end - + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end + local hit = target:checkHit(self:combatAttack(), target:combatDefense(), 0, 95, 5 - self:getTalentLevel(t) / 2) -- local hit = self:attackTarget(target, nil, nil, true) - + -- Try to knockback ! if hit then local can = function(target) @@ -47,23 +47,23 @@ newTalent{ self:project(target, target.x, target.y, DamageType.PHYSICAL, t.getDamage(self, t), nil, target) game.logSeen(target, "%s resists the knockback!", target.name:capitalize()) end - + end - + if can(target) then target:knockback(self.x, self.y, t.getPush(self, t), can) end - + -- move the attacker back and build combo point self:knockback(target.x, target.y, 1) self:buildCombo() else game.logSeen(target, "%s misses %s.", self.name:capitalize(), target.name:capitalize()) end - + return true end, info = function(self, t) local damage = t.getDamage(self, t) - local push =t.getPush(self, t) + local push = t.getPush(self, t) return ([[A push kick that knocks the target back %d tiles, moves you back 1 tile, and inflicts %0.2f physical damage. If another creature is in the way that creature will be affected too. Targets knocked into other targets may take extra damage. The damage will scale with the Strength stat. Builds one combo point.]]) @@ -87,20 +87,20 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end - + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end + -- extra damage vs. grappled targets if target:isGrappled(self) then hit = self:attackTarget(target, nil, t.getDamageTwo(self, t), true) else hit = self:attackTarget(target, nil, t.getDamage(self, t), true) end - + -- combo point if hit then self:buildCombo() end - + return true end, info = function(self, t) @@ -128,19 +128,20 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end - + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end + -- bonus damage for charging - local charge = math.floor((core.fov.distance(self.x, self.y, x, y)) -1) / 10 - + local charge = (core.fov.distance(self.x, self.y, x, y) - 1) / 10 + -- do the rush - local l = line.new(self.x, self.y, x, y) - local tx, ty = self.x, self.y - lx, ly = l() + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end + local l = self:lineFOV(x, y, block_actor) + local lx, ly, is_corner_blocked = l:step(block_actor) + local tx, ty = lx, ly while lx and ly do - if game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end + if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end tx, ty = lx, ly - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_actor) end local ox, oy = self.x, self.y @@ -149,9 +150,9 @@ newTalent{ self:resetMoveAnim() self:setMoveAnim(ox, oy, 8, 5) end - + -- do the backhand - if math.floor(core.fov.distance(self.x, self.y, x, y)) == 1 then + if core.fov.distance(self.x, self.y, x, y) == 1 then -- get left and right side local dir = util.getDir(x, y, self.x, self.y) local lx, ly = util.coordAddDir(self.x, self.y, dir_sides[dir].left) @@ -163,7 +164,7 @@ newTalent{ if hit then self:buildCombo() end - + --left hit if lt then hit2 = self:attackTarget(lt, nil, t.getDamage(self, t) + charge, true) @@ -181,10 +182,10 @@ newTalent{ end end end - + -- remove grappls self:breakGrapples() - + return true end, info = function(self, t) @@ -234,4 +235,5 @@ newTalent{ The resist penetration will scale with the Dexterity stat.]]): format(resistpen, combo, drain) end, -} \ No newline at end of file +} + diff --git a/game/modules/tome/data/talents/techniques/magical-combat.lua b/game/modules/tome/data/talents/techniques/magical-combat.lua index d5d14c720fe622b7bdf05e0b1073fde54d63180a..2d7ae9aac1cd76b0a7a0d9694d060ca8b473bf0d 100644 --- a/game/modules/tome/data/talents/techniques/magical-combat.lua +++ b/game/modules/tome/data/talents/techniques/magical-combat.lua @@ -37,15 +37,11 @@ newTalent{ if self:knowTalent(self.T_EARTHEN_MISSILES) and mana > self:getTalentFromId(self.T_EARTHEN_MISSILES).mana * fatigue then spells[#spells+1] = self.T_EARTHEN_MISSILES end local tid = rng.table(spells) if tid then - -- Extending beam target, assumes a maximum range of 10 - local current_angle = math.atan2((target.y - self.y), (target.x - self.x)) + math.pi - target_x = self.x - math.floor(0.5 + (10 * math.cos(current_angle))) - target_y = self.y - math.floor(0.5 + (10 * math.sin(current_angle))) - local l = line.new(self.x, self.y, target_x, target_y) - local lx, ly = l() - target_x, target_y = lx, ly + local l = self:lineFOV(target.x, target.y) + local lx, ly, is_corner_blocked = l:step(nil, true) + local target_x, target_y = lx, ly -- Check for terrain and friendly actors - while lx and ly do + while lx and ly and not is_corner_blocked and core.fov.distance(self.x, self.y, lx, ly) <= 10 do local actor = game.level.map(lx, ly, engine.Map.ACTOR) if actor and (self:reactionToward(actor) >= 0) then break @@ -54,7 +50,7 @@ newTalent{ break end target_x, target_y = lx, ly - lx, ly = l() + lx, ly = l:step(nil, true) end print("[ARCANE COMBAT] autocast ",self:getTalentFromId(tid).name) local old_cd = self:isTalentCoolingDown(self:getTalentFromId(tid)) @@ -130,3 +126,4 @@ newTalent{ format(self:combatSpellpower() * self:getTalentLevel(Talents.T_ARCANE_DESTRUCTION) / 9) end, } + diff --git a/game/modules/tome/data/talents/techniques/mobility.lua b/game/modules/tome/data/talents/techniques/mobility.lua index 5069ad22fbaeaa463a2778ac68383e8dd7f2820e..4420a7a76b711c745dcd85c1c66418d4387c05df 100644 --- a/game/modules/tome/data/talents/techniques/mobility.lua +++ b/game/modules/tome/data/talents/techniques/mobility.lua @@ -34,7 +34,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hitted = self:attackTarget(target, nil, t.getDamage(self, t), true) if hitted then @@ -111,3 +111,4 @@ newTalent{ format(self:getTalentLevelRaw(t) * 2, self:getTalentLevelRaw(t)) end, } + diff --git a/game/modules/tome/data/talents/techniques/pugilism.lua b/game/modules/tome/data/talents/techniques/pugilism.lua index d373e42392eda7b61d86b178052e881ab64036ae..72c4a59c34c5d4259d8ccdd25f0b6124cf120803 100644 --- a/game/modules/tome/data/talents/techniques/pugilism.lua +++ b/game/modules/tome/data/talents/techniques/pugilism.lua @@ -85,7 +85,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end -- force stance change if target and not self:isTalentActive(self.T_STRIKING_STANCE) then @@ -170,20 +170,21 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end -- bonus damage for charging - local charge = math.floor((core.fov.distance(self.x, self.y, x, y)) -1) / 10 + local charge = (core.fov.distance(self.x, self.y, x, y) - 1) / 10 local damage = t.getDamage(self, t) + charge -- do the rush - local l = line.new(self.x, self.y, x, y) - local tx, ty = self.x, self.y - lx, ly = l() + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end + local l = self:lineFOV(x, y, block_actor) + local lx, ly, is_corner_blocked = l:step(block_actor) + local tx, ty = lx, ly while lx and ly do - if game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end + if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end tx, ty = lx, ly - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_actor) end local ox, oy = self.x, self.y @@ -198,7 +199,7 @@ newTalent{ local hit3 = false -- do the backhand - if math.floor(core.fov.distance(self.x, self.y, x, y)) == 1 then + if core.fov.distance(self.x, self.y, x, y) == 1 then -- get left and right side local dir = util.getDir(x, y, self.x, self.y) local lx, ly = util.coordAddDir(self.x, self.y, dir_sides[dir].left) @@ -268,7 +269,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end -- breaks active grapples if the target is not grappled if target:isGrappled(self) then @@ -315,3 +316,4 @@ newTalent{ :format(damage) end, } + diff --git a/game/modules/tome/data/talents/techniques/thuggery.lua b/game/modules/tome/data/talents/techniques/thuggery.lua index bedf7705c6460d645d867a99ed30ec2d8e30b94c..6af638a828c659bb325b1c100cf335dca12bdf9a 100644 --- a/game/modules/tome/data/talents/techniques/thuggery.lua +++ b/game/modules/tome/data/talents/techniques/thuggery.lua @@ -48,7 +48,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local dam = t.getDamage(self, t) @@ -148,3 +148,4 @@ newTalent{ format(t.getCrit(self, t), t.getPen(self, t), t.getDrain(self, t)) end, } + diff --git a/game/modules/tome/data/talents/techniques/unarmed-discipline.lua b/game/modules/tome/data/talents/techniques/unarmed-discipline.lua index 2bf8153d4f35d5e406cbe8c76a5d07b5e4eed5a3..3a30676fc6f168ef7be1ff7164bda6cf679512cd 100644 --- a/game/modules/tome/data/talents/techniques/unarmed-discipline.lua +++ b/game/modules/tome/data/talents/techniques/unarmed-discipline.lua @@ -39,7 +39,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hit = target:checkHit(self:combatAttack(), target:combatDefense(), 0, 95, 5 - self:getTalentLevel(t) / 2) -- local hit = self:attackTarget(target, nil, nil, true) @@ -72,7 +72,7 @@ newTalent{ end, info = function(self, t) local damage = t.getDamage(self, t) - local push =t.getPush(self, t) + local push = t.getPush(self, t) return ([[A push kick that knocks the target back %d tiles, moves you back 1 tile, and inflicts %0.2f physical damage. If another creature is in the way that creature will be affected too. Targets knocked into other targets may take extra damage. This is considered a strike for the purposes of stance damage bonuses, will earn one combo point, and will break any grapples you're maintaining. The damage will scale with the strength, dexterity, and cunning stats.]]) @@ -185,3 +185,4 @@ newTalent{ :format(damDesc(self, DamageType.PHYSICAL, (damage))) end, } + diff --git a/game/modules/tome/data/talents/techniques/weaponshield.lua b/game/modules/tome/data/talents/techniques/weaponshield.lua index a3fe57286e9c20902694c5dc981ca62b718641dd..f6d7a3702761782b571449a58d56de1523eca83d 100644 --- a/game/modules/tome/data/talents/techniques/weaponshield.lua +++ b/game/modules/tome/data/talents/techniques/weaponshield.lua @@ -42,7 +42,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end self:attackTargetWith(target, shield.special_combat, nil, self:combatTalentWeaponDamage(t, 1, 1.7, self:getTalentLevel(self.T_SHIELD_EXPERTISE))) local speed, hit = self:attackTargetWith(target, shield.special_combat, nil, self:combatTalentWeaponDamage(t, 1.2, 2.1, self:getTalentLevel(self.T_SHIELD_EXPERTISE))) @@ -98,7 +98,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end -- First attack with weapon self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.8, 1.3), true) @@ -146,7 +146,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end -- First attack with shield local speed, hit = self:attackTargetWith(target, shield.special_combat, nil, self:combatTalentWeaponDamage(t, 1, 1.5, self:getTalentLevel(self.T_SHIELD_EXPERTISE))) @@ -309,3 +309,4 @@ newTalent{ (10 + self:getCon() * 0.25) * self:getTalentLevel(t)) end, } + diff --git a/game/modules/tome/data/talents/undeads/ghoul.lua b/game/modules/tome/data/talents/undeads/ghoul.lua index a15eaf78ead457b1764f9630ad6d1b0230df4a2b..59d2ed26ca910f60ed0e5fb8221ea6a73708773f 100644 --- a/game/modules/tome/data/talents/undeads/ghoul.lua +++ b/game/modules/tome/data/talents/undeads/ghoul.lua @@ -54,20 +54,20 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end - local l = line.new(self.x, self.y, x, y) - local lx, ly = l() - local tx, ty = lx, ly - lx, ly = l() + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end + local l = self:lineFOV(x, y, block_actor) + local lx, ly, is_corner_blocked = l:step(block_actor) + local tx, ty, _ = lx, ly while lx and ly do - if game.level.map:checkEntity(lx, ly, Map.TERRAIN, "block_move", self) then break end + if is_corner_blocked or block_actor(_, lx, ly) then break end tx, ty = lx, ly - lx, ly = l() + lx, ly, is_corner_blocked = l:step(block_actor) end -- Find space - if game.level.map:checkEntity(tx, ty, Map.TERRAIN, "block_move", self) then return nil end + if block_actor(_, tx, ty) then return nil end local fx, fy = util.findFreeGrid(tx, ty, 5, true, {[Map.ACTOR]=true}) if not fx then return @@ -127,7 +127,7 @@ newTalent{ local tg = {type="hit", range=self:getTalentRange(t)} local x, y, target = self:getTarget(tg) if not x or not y or not target then return nil end - if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end local hitted = self:attackTarget(target, nil, 0.2 + self:getTalentLevel(t) / 12, true) if hitted then @@ -145,3 +145,4 @@ newTalent{ format(100 * (0.2 + self:getTalentLevel(t) / 12), 3 + math.ceil(self:getTalentLevel(t))) end, } + diff --git a/game/modules/tome/data/timed_effects.lua b/game/modules/tome/data/timed_effects.lua index e8a6ac34e1e85f4253f22cd57a1f70059487253e..63ed51af59957f5c4df914fb33686471aea06a8d 100644 --- a/game/modules/tome/data/timed_effects.lua +++ b/game/modules/tome/data/timed_effects.lua @@ -526,7 +526,7 @@ newEffect{ eff.tmpid = self:addTemporaryValue("never_move", 1) end, on_timeout = function(self, eff) - if math.floor(core.fov.distance(self.x, self.y, eff.src.x, eff.src.y)) > 1 or eff.src.dead or not game.level:hasEntity(eff.src) then + if core.fov.distance(self.x, self.y, eff.src.x, eff.src.y) > 1 or eff.src.dead or not game.level:hasEntity(eff.src) then return true end self:suffocate(eff.power, eff.src, (" was constricted to death by %s."):format(eff.src.unique and eff.src.name or eff.src.name:a_an())) @@ -2718,7 +2718,7 @@ newEffect{ long_desc = function(self, eff) return ("Increases the effectiveness of all healing the target receives by %d%%."):format(eff.power * 100) end, type = "magical", status = "beneficial", - parameters = { power= 0.1 }, + parameters = { power = 0.1 }, activate = function(self, eff) eff.tmpid = self:addTemporaryValue("healing_factor", eff.power) end, @@ -3322,12 +3322,12 @@ newEffect{ on_lose = function(self, err) return "#Target# has regained its natural age.", "-Turn Back the Clock" end, activate = function(self, eff) eff.stat = self:addTemporaryValue("inc_stats", { - [Stats.STAT_STR] =-eff.power, - [Stats.STAT_DEX] =-eff.power, - [Stats.STAT_CON] =-eff.power, - [Stats.STAT_MAG] =-eff.power, - [Stats.STAT_WIL] =-eff.power, - [Stats.STAT_CUN] =-eff.power, + [Stats.STAT_STR] = -eff.power, + [Stats.STAT_DEX] = -eff.power, + [Stats.STAT_CON] = -eff.power, + [Stats.STAT_MAG] = -eff.power, + [Stats.STAT_WIL] = -eff.power, + [Stats.STAT_CUN] = -eff.power, }) -- Make sure the target doesn't have more life then it should if self.life > self.max_life then @@ -3775,7 +3775,7 @@ newEffect{ on_lose = function(self, err) return "#Target# has released the hold.", "-Grappling" end, on_timeout = function(self, eff) local p = eff.trgt:hasEffect(eff.trgt.EFF_GRAPPLED) - if not p or p.src ~= self or math.floor(core.fov.distance(self.x, self.y, eff.trgt.x, eff.trgt.y)) > 1 or eff.trgt.dead or not game.level:hasEntity(eff.trgt) then + if not p or p.src ~= self or core.fov.distance(self.x, self.y, eff.trgt.x, eff.trgt.y) > 1 or eff.trgt.dead or not game.level:hasEntity(eff.trgt) then self:removeEffect(self.EFF_GRAPPLING) end end, @@ -3804,7 +3804,7 @@ newEffect{ eff.atk = self:addTemporaryValue("combat_atk", -eff.power) end, on_timeout = function(self, eff) - if math.floor(core.fov.distance(self.x, self.y, eff.src.x, eff.src.y)) > 1 or eff.src.dead or not game.level:hasEntity(eff.src) then + if core.fov.distance(self.x, self.y, eff.src.x, eff.src.y) > 1 or eff.src.dead or not game.level:hasEntity(eff.src) then self:removeEffect(self.EFF_GRAPPLED) end end, @@ -3994,8 +3994,8 @@ newEffect{ end, activate = function(self, eff) eff.cur_inc = eff.inc - eff.tmpid= self:addTemporaryValue("resists", { - [DamageType.PHYSICAL]= eff.inc, + eff.tmpid = self:addTemporaryValue("resists", { + [DamageType.PHYSICAL] = eff.inc, }) end, deactivate = function(self, eff) @@ -4024,7 +4024,7 @@ newEffect{ end, on_timeout = function(self, eff) local severed = false - if math.floor(core.fov.distance(self.x, self.y, eff.src.x, eff.src.y)) >= eff.free or eff.src.dead or not game.level:hasEntity(eff.src) then severed = true end + if core.fov.distance(self.x, self.y, eff.src.x, eff.src.y) >= eff.free or eff.src.dead or not game.level:hasEntity(eff.src) then severed = true end if rng.percent(eff.free_chance) then severed = true end if severed then @@ -4325,7 +4325,7 @@ newEffect{ activate = function(self, eff) eff.cur_power = eff.power eff.rstid = self:addTemporaryValue("resists", { all = eff.power}) - eff.dmgid = self:addTemporaryValue("inc_damage", {all= -10}) + eff.dmgid = self:addTemporaryValue("inc_damage", {all = -10}) end, deactivate = function(self, eff) self:removeTemporaryValue("resists", eff.rstid) @@ -4496,7 +4496,7 @@ newEffect{ on_lose = function(self, err) return "#Target# is free from the abyss.", "-Abyssal Shroud" end, activate = function(self, eff) eff.liteid = self:addTemporaryValue("lite", -eff.lite) - eff.darkid = self:addTemporaryValue("resists", { [DamageType.DARKNESS]= - eff.power }) + eff.darkid = self:addTemporaryValue("resists", { [DamageType.DARKNESS] = -eff.power }) end, deactivate = function(self, eff) self:removeTemporaryValue("lite", eff.liteid) @@ -4615,3 +4615,4 @@ newEffect{ self:removeTemporaryValue("die_at", eff.dieatid) end, } + diff --git a/game/modules/tome/data/zones/crypt-kryl-feijan/zone.lua b/game/modules/tome/data/zones/crypt-kryl-feijan/zone.lua index fd200fb18b505585d66b4cdd42da7a569786e1db..e70e4e461205cb88ed17bf849635c4dc8e2873a6 100644 --- a/game/modules/tome/data/zones/crypt-kryl-feijan/zone.lua +++ b/game/modules/tome/data/zones/crypt-kryl-feijan/zone.lua @@ -102,7 +102,7 @@ return { local melinda for uid, e in pairs(game.level.entities) do if e.define_as and e.define_as == "MELINDA" then melinda = e end end - if melinda and not melinda.dead and math.floor(core.fov.distance(game.player.x, game.player.y, melinda.x, melinda.y)) > 1 then + if melinda and not melinda.dead and core.fov.distance(game.player.x, game.player.y, melinda.x, melinda.y) > 1 then require("engine.ui.Dialog"):simplePopup("Crypt", "You can not abandon Melinda here!") return nil, nil, true end @@ -141,3 +141,4 @@ return { }, }, } + diff --git a/src/core_lua.c b/src/core_lua.c index 038043de918cf4d70c8d939c45dcf214dc4140e1..9348c5a476e69902d8025dbe231b901b0cb52d73 100644 --- a/src/core_lua.c +++ b/src/core_lua.c @@ -2565,7 +2565,7 @@ static int lua_line_init(lua_State *L) bool start_at_end = lua_toboolean(L, 5); line_data *data = (line_data*)lua_newuserdata(L, sizeof(line_data)); - auxiliar_setclass(L, "line{core}", -1); + auxiliar_setclass(L, "core{line}", -1); data->origx=xFrom; data->origy=yFrom; @@ -2604,7 +2604,7 @@ static int lua_line_init(lua_State *L) static int lua_line_step(lua_State *L) { - line_data *data = (line_data*)auxiliar_checkclass(L, "line{core}", 1); + line_data *data = (line_data*)auxiliar_checkclass(L, "core{line}", 1); bool dont_stop_at_end = lua_toboolean(L, 2); if ( data->stepx*data->deltax > data->stepy*data->deltay ) { @@ -2631,7 +2631,7 @@ static int lua_line_step(lua_State *L) static int lua_free_line(lua_State *L) { - (void)auxiliar_checkclass(L, "line{core}", 1); + (void)auxiliar_checkclass(L, "core{line}", 1); lua_pushnumber(L, 1); return 1; } @@ -2725,7 +2725,7 @@ static const struct luaL_reg zliblib[] = int luaopen_core(lua_State *L) { - auxiliar_newclass(L, "line{core}", line_reg); + auxiliar_newclass(L, "core{line}", line_reg); auxiliar_newclass(L, "gl{texture}", sdl_texture_reg); auxiliar_newclass(L, "gl{fbo}", gl_fbo_reg); auxiliar_newclass(L, "gl{quadratic}", gl_quadratic_reg); @@ -2747,3 +2747,4 @@ int luaopen_core(lua_State *L) lua_settop(L, 0); return 1; } + diff --git a/src/fov.c b/src/fov.c index 9e0508444fc75e91292ebba1364df602a3a77f8f..82115d49cabf7ead410dd2d9fd1a7613c511cf56 100644 --- a/src/fov.c +++ b/src/fov.c @@ -41,6 +41,9 @@ * FOV * ****************************************************************** ******************************************************************/ + +static const char FOV_PERMISSIVE_KEY = 'k'; + struct lua_fovcache { bool *cache; @@ -58,12 +61,33 @@ struct lua_fov struct lua_fovcache *cache; }; +static int lua_fov_set_permissiveness(lua_State *L) +{ + float val = luaL_checknumber(L, 1); + if (val < 0.0f) val = 0.0f; + else if (val > 0.5f) val = 0.5f; + lua_pushlightuserdata(L, (void *)&FOV_PERMISSIVE_KEY); // push address as guaranteed unique key + lua_pushnumber(L, val); + lua_settable(L, LUA_REGISTRYINDEX); + return 0; +} + +static int lua_fov_get_permissiveness(lua_State *L) +{ + lua_pushlightuserdata(L, (void *)&FOV_PERMISSIVE_KEY); // push address as guaranteed unique key + lua_gettable(L, LUA_REGISTRYINDEX); /* retrieve value */ + if (lua_isnil(L, -1)) { + lua_pop(L, 1); // remove nil + lua_pushlightuserdata(L, (void *)&FOV_PERMISSIVE_KEY); // push address as guaranteed unique key + lua_pushnumber(L, 0.0f); + lua_settable(L, LUA_REGISTRYINDEX); + } +} static void map_seen(void *m, int x, int y, int dx, int dy, int radius, void *src) { struct lua_fov *fov = (struct lua_fov *)m; - radius--; if (x < 0 || y < 0 || x >= fov->w || y >= fov->h) return; - if (dx*dx + dy*dy <= radius*radius + 1) + if (dx*dx + dy*dy <= radius*radius + radius) // <-- use shape of FoV. Also, is this check really necessary? TODO: verify and delete if unnecessary { // circular view - can be changed if you like lua_rawgeti(fov->L, LUA_REGISTRYINDEX, fov->apply_ref); @@ -129,7 +153,10 @@ static int lua_fov_calc_circle(lua_State *L) fov_settings_init(&(fov.fov_settings)); fov_settings_set_opacity_test_function(&(fov.fov_settings), map_opaque); fov_settings_set_apply_lighting_function(&(fov.fov_settings), map_seen); - fov_circle(&(fov.fov_settings), &fov, NULL, x, y, radius+1); + lua_fov_get_permissiveness(L); + fov.fov_settings.permissiveness = luaL_checknumber(L, -1); + + fov_circle(&(fov.fov_settings), &fov, NULL, x, y, radius); map_seen(&fov, x, y, 0, 0, radius, NULL); fov_settings_free(&(fov.fov_settings)); @@ -184,7 +211,10 @@ static int lua_fov_calc_beam(lua_State *L) fov_settings_init(&(fov.fov_settings)); fov_settings_set_opacity_test_function(&(fov.fov_settings), map_opaque); fov_settings_set_apply_lighting_function(&(fov.fov_settings), map_seen); - fov_beam(&(fov.fov_settings), &fov, NULL, x, y, radius+1, dir, angle); + lua_fov_get_permissiveness(L); + fov.fov_settings.permissiveness = luaL_checknumber(L, -1); + + fov_beam(&(fov.fov_settings), &fov, NULL, x, y, radius, dir, angle); map_seen(&fov, x, y, 0, 0, radius, NULL); fov_settings_free(&(fov.fov_settings)); @@ -202,10 +232,11 @@ static int lua_fov_calc_beam_any_angle(lua_State *L) int w = luaL_checknumber(L, 3); int h = luaL_checknumber(L, 4); int radius = luaL_checknumber(L, 5); - float dir_angle = luaL_checknumber(L, 6); - float beam_angle = luaL_checknumber(L, 7); + float dx = luaL_checknumber(L, 6); + float dy = luaL_checknumber(L, 7); + float beam_angle = luaL_checknumber(L, 8); struct lua_fov fov; - if (lua_isuserdata(L, 10)) + if (lua_isuserdata(L, 11)) { fov.cache = (struct lua_fovcache*)auxiliar_checkclass(L, "fov{cache}", 10); fov.cache_ref = luaL_ref(L, LUA_REGISTRYINDEX); @@ -222,18 +253,21 @@ static int lua_fov_calc_beam_any_angle(lua_State *L) fov.cache = NULL; fov.w = w; fov.h = h; - + fov_settings_init(&(fov.fov_settings)); fov_settings_set_opacity_test_function(&(fov.fov_settings), map_opaque); fov_settings_set_apply_lighting_function(&(fov.fov_settings), map_seen); - fov_beam_any_angle(&(fov.fov_settings), &fov, NULL, x, y, radius+1, dir_angle, beam_angle); + lua_fov_get_permissiveness(L); + fov.fov_settings.permissiveness = luaL_checknumber(L, -1); + + fov_beam_any_angle(&(fov.fov_settings), &fov, NULL, x, y, radius, dx, dy, beam_angle); map_seen(&fov, x, y, 0, 0, radius, NULL); fov_settings_free(&(fov.fov_settings)); - + luaL_unref(L, LUA_REGISTRYINDEX, fov.apply_ref); luaL_unref(L, LUA_REGISTRYINDEX, fov.opaque_ref); if (fov.cache_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, fov.cache_ref); - + return 0; } @@ -244,7 +278,8 @@ static int lua_distance(lua_State *L) double x2 = luaL_checknumber(L, 3); double y2 = luaL_checknumber(L, 4); - lua_pushnumber(L, sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2))); + // TODO: switch/case based on FoV shape. Use rounded circle for now + lua_pushnumber(L, (int)(sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)) + 0.5f)); return 1; } @@ -322,10 +357,9 @@ static void map_default_seen(void *m, int x, int y, int dx, int dy, int radius, { default_fov *def = (default_fov*)src; struct lua_fov *fov = (struct lua_fov *)m; - radius--; float sqdist = dx*dx + dy*dy; float dist = sqrtf(sqdist); - if (sqdist <= radius*radius + 1) + if (dx*dx + dy*dy <= radius*radius + radius) // <-- use FoV shape. Also, is this check really necessary? TODO: verify and delete if unnecessary { // Distance Map if (def->do_dmap) @@ -461,7 +495,10 @@ static int lua_fov_calc_default_fov(lua_State *L) fov_settings_init(&(fov.fov_settings)); fov_settings_set_opacity_test_function(&(fov.fov_settings), map_default_opaque); fov_settings_set_apply_lighting_function(&(fov.fov_settings), map_default_seen); - fov_circle(&(fov.fov_settings), &fov, &def, x, y, radius+1); + lua_fov_get_permissiveness(L); + fov.fov_settings.permissiveness = luaL_checknumber(L, -1); + + fov_circle(&(fov.fov_settings), &fov, &def, x, y, radius); map_default_seen(&fov, x, y, 0, 0, radius, &def); fov_settings_free(&(fov.fov_settings)); @@ -470,6 +507,202 @@ static int lua_fov_calc_default_fov(lua_State *L) return 0; } +/**************************************************************** + ** FOV line + ****************************************************************/ + +static int lua_fov_line_init(lua_State *L) +{ + int source_x = luaL_checknumber(L, 1); + int source_y = luaL_checknumber(L, 2); + int dest_x = luaL_checknumber(L, 3); + int dest_y = luaL_checknumber(L, 4); + int w = luaL_checknumber(L, 5); + int h = luaL_checknumber(L, 6); + struct lua_fov fov; + fov.cache_ref = LUA_NOREF; + fov.cache = NULL; +/* if (lua_isuserdata(L, 9)) + { + fov.cache = (struct lua_fovcache*)auxiliar_checkclass(L, "fov{cache}", 8); + fov.cache_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + else + { + lua_pop(L, 1); + fov.cache_ref = LUA_NOREF; + fov.cache = NULL; + } +*/ + fov.L = L; + fov.opaque_ref = luaL_ref(L, LUA_REGISTRYINDEX); + bool start_at_end = lua_toboolean(L, 8); + fov.w = w; + fov.h = h; + fov_settings_init(&(fov.fov_settings)); + fov_settings_set_opacity_test_function(&(fov.fov_settings), map_opaque); + lua_fov_get_permissiveness(L); + fov.fov_settings.permissiveness = luaL_checknumber(L, -1); + + fov_line_data *line = (fov_line_data*)lua_newuserdata(L, sizeof(fov_line_data)); + + fov_create_los_line(&(fov.fov_settings), &fov, NULL, line, source_x, source_y, dest_x, dest_y, start_at_end); + fov_settings_free(&(fov.fov_settings)); + luaL_unref(L, LUA_REGISTRYINDEX, fov.opaque_ref); +// if (fov.cache_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, fov.cache_ref); + + auxiliar_setclass(L, "core{fovline}", -1); + return 1; +} + +static int lua_fov_line_step(lua_State *L) +{ + fov_line_data *line = (fov_line_data*)auxiliar_checkclass(L, "core{fovline}", 1); + bool dont_stop_at_end = lua_toboolean(L, 2); + if (!dont_stop_at_end && line->dest_t == line->t) return 0; + + // If there is a tie, then choose the tile closer to a cardinal direction. + // If we weren't careful, this would be the most likely place to have floating precision + // errors that would be inconsistent with FoV. Therefore, let's be extra cautious! + line->t += 1; + float fx = (float)line->t * line->step_x - copysign(GRID_EPSILON, line->step_x); + float fy = (float)line->t * line->step_y - copysign(GRID_EPSILON, line->step_y); + int x = (fx < 0.0f) ? -(int)(0.5f - fx) : (int)(0.5f + fx); + int y = (fy < 0.0f) ? -(int)(0.5f - fy) : (int)(0.5f + fy); + fx = (float)(line->t - 1) * line->step_x - copysign(GRID_EPSILON, line->step_x); + fy = (float)(line->t - 1) * line->step_y - copysign(GRID_EPSILON, line->step_y); + int x_prev = (fx < 0.0f) ? -(int)(0.5f - fx) : (int)(0.5f + fx); + int y_prev = (fy < 0.0f) ? -(int)(0.5f - fy) : (int)(0.5f + fy); + + // check if line is blocked by a corner of an adjacent tile + bool is_corner_blocked = false; + if (x != x_prev && y != y_prev) { + // Note: we can maybe store lua_fov struct in the line struct so we don't need to do this every time, but we need to verify projectiles can save and load correctly + int w = luaL_checknumber(L, 3); + int h = luaL_checknumber(L, 4); + struct lua_fov fov; + fov.cache_ref = LUA_NOREF; + fov.cache = NULL; + fov.L = L; + fov.w = w; + fov.h = h; + fov.opaque_ref = luaL_ref(L, LUA_REGISTRYINDEX); + fov_settings_init(&(fov.fov_settings)); + fov_settings_set_opacity_test_function(&(fov.fov_settings), map_opaque); + lua_fov_get_permissiveness(L); + fov.fov_settings.permissiveness = luaL_checknumber(L, -1); + + if (fabs(line->step_x) > fabs(line->step_y)) { + float dy = (line->step_y < 0.0f) ? 1.0f : 0.0f; + dy += (float)y - fy - 0.5f; + float val = fx - (float)x_prev + dy*line->step_x/line->step_y; + if (fabs(val) < 0.5f - fov.fov_settings.permissiveness - 2.5f*SLOPE_EPSILON && fov.fov_settings.opaque(&fov, line->source_x + x_prev, line->source_y + y)) { + is_corner_blocked = true; + } + } + else { + float dx = (line->step_x < 0.0f) ? 1.0f : 0.0f; + dx += (float)x - fx - 0.5f; + float val = fy - (float)y_prev + dx*line->step_y/line->step_x; + if (fabs(val) < 0.5f - fov.fov_settings.permissiveness - 2.5f*SLOPE_EPSILON && fov.fov_settings.opaque(&fov, line->source_x + x, line->source_y + y_prev)) { + is_corner_blocked = true; + } + } + luaL_unref(L, LUA_REGISTRYINDEX, fov.opaque_ref); + } + lua_pushnumber(L, line->source_x + x); + lua_pushnumber(L, line->source_y + y); + lua_pushboolean(L, is_corner_blocked); + return 3; +} + +// The next three functions aren't used anywhere and can probably be deleted +static int lua_fov_line_blocked_xy(lua_State *L) +{ + fov_line_data *line = (fov_line_data*)auxiliar_checkclass(L, "core{fovline}", 1); + bool dont_stop_at_end = lua_toboolean(L, 2); + + if (!line->is_blocked) return 0; + float val = (float)line->block_t * line->step_x - copysign(GRID_EPSILON, line->step_x); + int x = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + val = (float)line->block_t * line->step_y - copysign(GRID_EPSILON, line->step_y); + int y = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + + lua_pushnumber(L, line->source_x + x); + lua_pushnumber(L, line->source_y + y); + return 2; +} + +static int lua_fov_line_last_open_xy(lua_State *L) +{ + fov_line_data *line = (fov_line_data*)auxiliar_checkclass(L, "core{fovline}", 1); + bool dont_stop_at_end = lua_toboolean(L, 2); + int x, y; + + if (line->is_blocked) { + float val = (float)(line->block_t - 1) * line->step_x - copysign(GRID_EPSILON, line->step_x); + x = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + val = (float)(line->block_t - 1) * line->step_y - copysign(GRID_EPSILON, line->step_y); + y = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + } + else { + x = line->dest_x; + y = line->dest_y; + } + lua_pushnumber(L, x); + lua_pushnumber(L, y); + return 2; +} + +static int lua_fov_line_is_blocked(lua_State *L) +{ + fov_line_data *line = (fov_line_data*)auxiliar_checkclass(L, "core{fovline}", 1); + lua_pushboolean(L, line->is_blocked); + return 1; +} + +// export data so we may save it in lua +static int lua_fov_line_export(lua_State *L) +{ + fov_line_data *line = (fov_line_data*)auxiliar_checkclass(L, "core{fovline}", 1); + lua_pushnumber(L, line->source_x); + lua_pushnumber(L, line->source_y); + lua_pushnumber(L, line->dest_x); + lua_pushnumber(L, line->dest_y); + lua_pushnumber(L, line->t); + lua_pushnumber(L, line->block_t); + lua_pushnumber(L, line->dest_t); + lua_pushnumber(L, line->step_x); + lua_pushnumber(L, line->step_y); + lua_pushboolean(L, line->is_blocked); + return 10; +} + +// load previously exported data (or create a specific line of your choice) +static int lua_fov_line_import(lua_State *L) +{ + fov_line_data *line = (fov_line_data*)lua_newuserdata(L, sizeof(fov_line_data)); + line->source_x = luaL_checknumber(L, 1); + line->source_y = luaL_checknumber(L, 2); + line->dest_x = luaL_checknumber(L, 3); + line->dest_y = luaL_checknumber(L, 4); + line->t = luaL_checknumber(L, 5); + line->block_t = luaL_checknumber(L, 6); + line->dest_t = luaL_checknumber(L, 7); + line->step_x = luaL_checknumber(L, 8); + line->step_y = luaL_checknumber(L, 9); + line->is_blocked = lua_toboolean(L, 10); + + auxiliar_setclass(L, "core{fovline}", -1); + return 1; +} + +static int lua_free_fov_line(lua_State *L) +{ + (void)auxiliar_checkclass(L, "core{fovline}", 1); + lua_pushnumber(L, 1); + return 1; +} static const struct luaL_reg fovlib[] = { @@ -479,6 +712,9 @@ static const struct luaL_reg fovlib[] = {"calc_circle", lua_fov_calc_circle}, {"calc_beam", lua_fov_calc_beam}, {"calc_beam_any_angle", lua_fov_calc_beam_any_angle}, + {"line_base", lua_fov_line_init}, + {"line_import", lua_fov_line_import}, + {"set_permissiveness_base", lua_fov_set_permissiveness}, {NULL, NULL}, }; @@ -489,10 +725,24 @@ static const struct luaL_reg fovcache_reg[] = {NULL, NULL}, }; +static const struct luaL_reg fovline_reg[] = +{ + {"__gc", lua_free_fov_line}, + {"__call", lua_fov_line_step}, + {"step_base", lua_fov_line_step}, + {"is_blocked", lua_fov_line_is_blocked}, + {"blocked_xy", lua_fov_line_blocked_xy}, + {"last_open_xy", lua_fov_line_last_open_xy}, + {"export", lua_fov_line_export}, + {NULL, NULL}, +}; + int luaopen_fov(lua_State *L) { auxiliar_newclass(L, "fov{cache}", fovcache_reg); + auxiliar_newclass(L, "core{fovline}", fovline_reg); luaL_openlib(L, "core.fov", fovlib, 0); lua_pop(L, 1); return 1; } + diff --git a/src/fov/fov.c b/src/fov/fov.c index 3500c195255eaa779bbfb9396cb98f8aa8eae4b5..04cbc79888039576f0f6773c84d24ff22d462265 100644 --- a/src/fov/fov.c +++ b/src/fov/fov.c @@ -13,6 +13,10 @@ #include <assert.h> #include "fov.h" +/* radians/degrees conversions */ +#define DtoR 1.74532925199432957692e-02 +#define RtoD 57.2957795130823208768 + /* +---++---++---++---+ | || || || | @@ -85,6 +89,7 @@ void fov_settings_init(fov_settings_type *settings) { settings->apply = NULL; settings->heights = NULL; settings->numheights = 0; + settings->permissiveness = 0.0f; } void fov_settings_set_shape(fov_settings_type *settings, @@ -203,9 +208,13 @@ static float fov_slope(float dx, float dy) { int x, y, dy, dy0, dy1; \ unsigned h; \ int prev_blocked = -1; \ - float end_slope_next; \ + float start_slope_next, end_slope_next; \ fov_settings_type *settings = data->settings; \ \ + if (start_slope - end_slope > 5.0f*SLOPE_EPSILON) { \ + return; \ + } \ + \ if (dx == 0) { \ fov_octant_##nx##ny##nf(data, dx+1, start_slope, end_slope, apply_edge, apply_diag); \ return; \ @@ -213,26 +222,12 @@ static float fov_slope(float dx, float dy) { return; \ } \ \ - dy0 = (int)(0.5f + ((float)dx)*start_slope); \ - dy1 = (int)(0.5f + ((float)dx)*end_slope); \ + dy0 = (int)(0.5f + (float)dx*start_slope + GRID_EPSILON); \ + dy1 = (int)(0.5f + (float)dx*end_slope - GRID_EPSILON); \ \ rx = data->source_##rx signx dx; \ \ - if (!apply_diag && dy1 == dx) { \ - /* We do diagonal lines on every second octant, so they don't get done twice. */ \ - --dy1; \ - \ - /* But, we still need to check if we can see past it if the slopes are similar */ \ - if (dy1 < dy0) { \ - ry = data->source_##ry signy dy0; \ - if (settings->opaque(data->map, x, y)) { \ - return; \ - } \ - prev_blocked = 0; \ - } \ - } \ - \ - /* we also need to check if the previous spot is blocked */ \ + /* we need to check if the previous spot is blocked */ \ if (dy0 > 0) { \ ry = data->source_##ry signy (dy0-1); \ if (settings->opaque(data->map, x, y)) { \ @@ -271,26 +266,47 @@ static float fov_slope(float dx, float dy) { ry = data->source_##ry signy dy; \ \ if (settings->opaque(data->map, x, y)) { \ - if (settings->opaque_apply == FOV_OPAQUE_APPLY && (apply_edge || dy > 0)) { \ - settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source); \ + if (settings->opaque_apply == FOV_OPAQUE_APPLY && (apply_edge || dy > 0) && (apply_diag || dy != dx)) { \ + settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source); \ } \ if (prev_blocked == 0) { \ - end_slope_next = fov_slope((float)dx + 0.5f, (float)dy - 0.5f); \ - fov_octant_##nx##ny##nf(data, dx+1, start_slope, end_slope_next, apply_edge, apply_diag); \ + end_slope_next = fov_slope((float)dx + 0.5f - settings->permissiveness + SLOPE_EPSILON, (float)dy - 0.5f) - GRID_EPSILON; \ + if (end_slope_next > end_slope) { \ + end_slope_next = end_slope; \ + } \ + if (dy != dy0) { \ + fov_octant_##nx##ny##nf(data, dx+1, start_slope, end_slope_next, apply_edge, apply_diag); \ + } \ } \ prev_blocked = 1; \ } else { \ - if (apply_edge || dy > 0) { \ - settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source); \ - } \ if (prev_blocked == 1) { \ - start_slope = fov_slope((float)dx - 0.5f, (float)dy - 0.5f); \ + start_slope_next = fov_slope((float)dx - 0.5f + settings->permissiveness - SLOPE_EPSILON, (float)dy - 0.5f) + GRID_EPSILON; \ + if (start_slope_next > start_slope) { \ + start_slope = start_slope_next; \ + if (start_slope - end_slope > 5.0f*SLOPE_EPSILON) { \ + return; \ + } \ + } \ + } \ + if ((apply_edge || dy > 0) && (apply_diag || dy != dx)) { \ + settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source); \ } \ prev_blocked = 0; \ } \ } \ \ if (prev_blocked == 0) { \ + /* We need to check if the next spot is blocked and change end_slope accordingly */ \ + if (dx != dy1) { \ + ry = data->source_##ry signy (dy1+1); \ + if (settings->opaque(data->map, x, y)) { \ + end_slope_next = fov_slope((float)dx + 0.5f - settings->permissiveness + SLOPE_EPSILON, (float)dy1 + 0.5f) - GRID_EPSILON; \ + if (end_slope_next < end_slope) { \ + end_slope = end_slope_next; \ + } \ + } \ + } \ fov_octant_##nx##ny##nf(data, dx+1, start_slope, end_slope, apply_edge, apply_diag); \ } \ } @@ -375,12 +391,12 @@ static float betweenf(float x, float a, float b) { fov_octant_##p3(&data, 1, start_slope, 1.0f, true, false); \ fov_octant_##p4(&data, 1, start_slope, 1.0f, true, false); \ } \ - if (a - 2.0f > FLT_EPSILON) { /* a > 2.0f */ \ + if (a - 2.0f > 2.0f * FLT_EPSILON) { /* a > 2.0f */ \ end_slope = betweenf(a - 2.0f, 0.0f, 1.0f); \ fov_octant_##p5(&data, 1, 0.0f, end_slope, false, true); \ fov_octant_##p6(&data, 1, 0.0f, end_slope, false, true); \ } \ - if (a - 3.0f > FLT_EPSILON) { /* a > 3.0f */ \ + if (a - 3.0f > 3.0 * FLT_EPSILON) { /* a > 3.0f */ \ start_slope = betweenf(4.0f - a, 0.0f, 1.0f); \ fov_octant_##p7(&data, 1, start_slope, 1.0f, true, false); \ fov_octant_##p8(&data, 1, start_slope, 1.0f, false, false); \ @@ -397,12 +413,12 @@ static float betweenf(float x, float a, float b) { fov_octant_##p3(&data, 1, 0.0f, end_slope, false, true); \ fov_octant_##p4(&data, 1, 0.0f, end_slope, false, true); \ } \ - if (a - 2.0f > FLT_EPSILON) { /* a > 2.0f */ \ + if (a - 2.0f > 2.0f * FLT_EPSILON) { /* a > 2.0f */ \ start_slope = betweenf(3.0f - a, 0.0f, 1.0f); \ fov_octant_##p5(&data, 1, start_slope, 1.0f, true, false); \ fov_octant_##p6(&data, 1, start_slope, 1.0f, true, false); \ } \ - if (a - 3.0f > FLT_EPSILON) { /* a > 3.0f */ \ + if (a - 3.0f > 3.0f * FLT_EPSILON) { /* a > 3.0f */ \ end_slope = betweenf(a - 3.0f, 0.0f, 1.0f); \ fov_octant_##p7(&data, 1, 0.0f, end_slope, false, true); \ fov_octant_##p8(&data, 1, 0.0f, end_slope, false, false); \ @@ -447,106 +463,112 @@ void fov_beam(fov_settings_type *settings, void *map, void *source, } #define BEAM_ANY_DIRECTION(offset, p1, p2, p3, p4, p5, p6, p7, p8) \ -angle_begin -= offset; \ -angle_end -= offset; \ -start_slope = angle_begin; \ -end_slope = betweenf(angle_end, 0.0f, 1.0f); \ -fov_octant_##p1(&data, 1, start_slope, end_slope, true, true); \ -\ -if (angle_end - 1.0 > FLT_EPSILON) { \ -start_slope = betweenf(2.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p2(&data, 1, start_slope, 1.0f, true, false); \ -\ -if (angle_end - 2.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 2.0f, 0.0f, 1.0f); \ -fov_octant_##p3(&data, 1, 0.0f, end_slope, false, true); \ -\ -if (angle_end - 3.0 > FLT_EPSILON) { \ -start_slope = betweenf(4.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p4(&data, 1, start_slope, 1.0f, true, false); \ -\ -if (angle_end - 4.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 4.0f, 0.0f, 1.0f); \ -fov_octant_##p5(&data, 1, 0.0f, end_slope, false, true); \ -\ -if (angle_end - 5.0 > FLT_EPSILON) { \ -start_slope = betweenf(6.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p6(&data, 1, start_slope, 1.0f, true, false); \ -\ -if (angle_end - 6.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 6.0f, 0.0f, 1.0f); \ -fov_octant_##p7(&data, 1, 0.0f, end_slope, false, true); \ -\ -if (angle_end - 7.0 > FLT_EPSILON) { \ -start_slope = betweenf(8.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p8(&data, 1, start_slope, 1.0f, false, false); \ -}}}}}}} + angle_begin -= offset; \ + angle_end -= offset; \ + start_slope = angle_begin; \ + end_slope = betweenf(angle_end, 0.0f, 1.0f); \ + fov_octant_##p1(&data, 1, start_slope, end_slope, true, true); \ + \ + if (angle_end - 1.0 > FLT_EPSILON) { \ + start_slope = betweenf(2.0f - angle_end, 0.0f, 1.0f); \ + fov_octant_##p2(&data, 1, start_slope, 1.0f, true, false); \ + \ + if (angle_end - 2.0 > 2.0f * FLT_EPSILON) { \ + end_slope = betweenf(angle_end - 2.0f, 0.0f, 1.0f); \ + fov_octant_##p3(&data, 1, 0.0f, end_slope, false, true); \ + \ + if (angle_end - 3.0 > 3.0f * FLT_EPSILON) { \ + start_slope = betweenf(4.0f - angle_end, 0.0f, 1.0f); \ + fov_octant_##p4(&data, 1, start_slope, 1.0f, true, false); \ + \ + if (angle_end - 4.0 > 4.0f * FLT_EPSILON) { \ + end_slope = betweenf(angle_end - 4.0f, 0.0f, 1.0f); \ + fov_octant_##p5(&data, 1, 0.0f, end_slope, false, true); \ + \ + if (angle_end - 5.0 > 5.0f * FLT_EPSILON) { \ + start_slope = betweenf(6.0f - angle_end, 0.0f, 1.0f); \ + fov_octant_##p6(&data, 1, start_slope, 1.0f, true, false); \ + \ + if (angle_end - 6.0 > 6.0f * FLT_EPSILON) { \ + end_slope = betweenf(angle_end - 6.0f, 0.0f, 1.0f); \ + fov_octant_##p7(&data, 1, 0.0f, end_slope, false, true); \ + \ + if (angle_end - 7.0 > 7.0f * FLT_EPSILON) { \ + start_slope = betweenf(8.0f - angle_end, 0.0f, 1.0f); \ + fov_octant_##p8(&data, 1, start_slope, 1.0f, false, false); \ + }}}}}}} #define BEAM_ANY_DIRECTION_DIAG(offset, p1, p2, p3, p4, p5, p6, p7, p8) \ -angle_begin -= offset; \ -angle_end -= offset; \ -start_slope = betweenf(1.0 - angle_end, 0.0f, 1.0f); \ -end_slope = 1.0 - angle_begin; \ -fov_octant_##p1(&data, 1, start_slope, end_slope, true, true); \ -\ -if (angle_end - 1.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 1.0f, 0.0f, 1.0f); \ -fov_octant_##p2(&data, 1, 0.0f, end_slope, false, true); \ -\ -if (angle_end - 2.0 > FLT_EPSILON) { \ -start_slope = betweenf(3.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p3(&data, 1, start_slope, 1.0f, true, false); \ -\ -if (angle_end - 3.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 3.0f, 0.0f, 1.0f); \ -fov_octant_##p4(&data, 1, 0.0f, end_slope, false, true); \ -\ -if (angle_end - 4.0 > FLT_EPSILON) { \ -start_slope = betweenf(5.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p5(&data, 1, start_slope, 1.0f, true, false); \ -\ -if (angle_end - 5.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 5.0f, 0.0f, 1.0f); \ -fov_octant_##p6(&data, 1, 0.0f, end_slope, false, true); \ -\ -if (angle_end - 6.0 > FLT_EPSILON) { \ -start_slope = betweenf(7.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p7(&data, 1, start_slope, 1.0f, true, false); \ -\ -if (angle_end - 7.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 7.0f, 0.0f, 1.0f); \ -fov_octant_##p8(&data, 1, 0.0f, end_slope, false, false); \ + angle_begin -= offset; \ + angle_end -= offset; \ + start_slope = betweenf(1.0 - angle_end, 0.0f, 1.0f); \ + end_slope = 1.0 - angle_begin; \ + fov_octant_##p1(&data, 1, start_slope, end_slope, true, true); \ + \ + if (angle_end - 1.0 > FLT_EPSILON) { \ + end_slope = betweenf(angle_end - 1.0f, 0.0f, 1.0f); \ + fov_octant_##p2(&data, 1, 0.0f, end_slope, false, true); \ + \ + if (angle_end - 2.0 > 2.0f * FLT_EPSILON) { \ + start_slope = betweenf(3.0f - angle_end, 0.0f, 1.0f); \ + fov_octant_##p3(&data, 1, start_slope, 1.0f, true, false); \ + \ + if (angle_end - 3.0 > 3.0f * FLT_EPSILON) { \ + end_slope = betweenf(angle_end - 3.0f, 0.0f, 1.0f); \ + fov_octant_##p4(&data, 1, 0.0f, end_slope, false, true); \ + \ + if (angle_end - 4.0 > 4.0f * FLT_EPSILON) { \ + start_slope = betweenf(5.0f - angle_end, 0.0f, 1.0f); \ + fov_octant_##p5(&data, 1, start_slope, 1.0f, true, false); \ + \ + if (angle_end - 5.0 > 5.0f * FLT_EPSILON) { \ + end_slope = betweenf(angle_end - 5.0f, 0.0f, 1.0f); \ + fov_octant_##p6(&data, 1, 0.0f, end_slope, false, true); \ + \ + if (angle_end - 6.0 > 6.0f * FLT_EPSILON) { \ + start_slope = betweenf(7.0f - angle_end, 0.0f, 1.0f); \ + fov_octant_##p7(&data, 1, start_slope, 1.0f, true, false); \ + \ + if (angle_end - 7.0 > 7.0f * FLT_EPSILON) { \ + end_slope = betweenf(angle_end - 7.0f, 0.0f, 1.0f); \ + fov_octant_##p8(&data, 1, 0.0f, end_slope, false, false); \ }}}}}}} void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source, - int source_x, int source_y, unsigned radius, - float dir_angle, float beam_angle) { - + int source_x, int source_y, unsigned radius, + float dx, float dy, float beam_angle) { + + /* Note: angle_begin and angle_end are misnomers, since FoV calculation uses slopes, not angles. + * Hence, it may not be clear that we implicitly use a tan(x) ~ 4/pi*x approximation + * for x in range (0, pi/4) radians, or (0, 45) degrees. We can (and will) make this much + * more precise, which is why the function now uses parameters dx, dy instead of dir_angle. + * TODO: make this more precise by not using approximations */ fov_private_data_type data; - float start_slope, end_slope, angle_begin, angle_end; - + float start_slope, end_slope, angle_begin, angle_end, dir_angle; + data.settings = settings; data.map = map; data.source = source; data.source_x = source_x; data.source_y = source_y; data.radius = radius; - + if (beam_angle <= 0.0f) { return; } else if (beam_angle >= 360.0f) { _fov_circle(&data); return; } - + + dir_angle = RtoD*atan2(dy, dx); while (dir_angle >= 360.0f) { - dir_angle -= 360.0f; + dir_angle -= 360.0f; } - + while (dir_angle < 0.0f) { - dir_angle += 360.0f; + dir_angle += 360.0f; } - + /* Calculate the angles as a percentage of 45 degrees */ angle_begin = (dir_angle - 0.5*beam_angle) / 45.0f; angle_end = (dir_angle + 0.5*beam_angle) / 45.0f; @@ -554,22 +576,330 @@ void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source, angle_begin += 8.0f; angle_end += 8.0f; } - + if (1.0f - angle_begin > FLT_EPSILON) { BEAM_ANY_DIRECTION(0.0f, ppn, ppy, pmy, mpn, mmn, mmy, mpy, pmn); - } else if (2.0f - angle_begin > FLT_EPSILON) { + } else if (2.0f - angle_begin > 2.0f * FLT_EPSILON) { BEAM_ANY_DIRECTION_DIAG(1.0f, ppy, pmy, mpn, mmn, mmy, mpy, pmn, ppn); - } else if (3.0f - angle_begin > FLT_EPSILON) { + } else if (3.0f - angle_begin > 3.0f * FLT_EPSILON) { BEAM_ANY_DIRECTION(2.0f, pmy, mpn, mmn, mmy, mpy, pmn, ppn, ppy); - } else if (4.0f - angle_begin > FLT_EPSILON) { + } else if (4.0f - angle_begin > 4.0f * FLT_EPSILON) { BEAM_ANY_DIRECTION_DIAG(3.0f, mpn, mmn, mmy, mpy, pmn, ppn, ppy, pmy); - } else if (5.0f - angle_begin > FLT_EPSILON) { + } else if (5.0f - angle_begin > 5.0f * FLT_EPSILON) { BEAM_ANY_DIRECTION(4.0f, mmn, mmy, mpy, pmn, ppn, ppy, pmy, mpn); - } else if (6.0f - angle_begin > FLT_EPSILON) { + } else if (6.0f - angle_begin > 6.0f * FLT_EPSILON) { BEAM_ANY_DIRECTION_DIAG(5.0f, mmy, mpy, pmn, ppn, ppy, pmy, mpn, mmn); - } else if (7.0f - angle_begin > FLT_EPSILON) { + } else if (7.0f - angle_begin > 7.0f * FLT_EPSILON) { BEAM_ANY_DIRECTION(6.0f, mpy, pmn, ppn, ppy, pmy, mpn, mmn, mmy); - } else if (8.0f - angle_begin > FLT_EPSILON) { + } else if (8.0f - angle_begin > 8.0f * FLT_EPSILON) { BEAM_ANY_DIRECTION_DIAG(7.0f, pmn, ppn, ppy, pmy, mpn, mmn, mmy, mpy); } } + +void fov_create_los_line(fov_settings_type *settings, void *map, void *source, fov_line_data *line, + int source_x, int source_y, + int dest_x, int dest_y, + bool start_at_end) { + + line->source_x = source_x; + line->source_y = source_y; + line->dest_x = dest_x; + line->dest_y = dest_y; + line->t = 0; + line->is_blocked = false; + + if (line->source_x == line->dest_x) + { + line->dest_t = abs(line->dest_y - line->source_y); + + if (line->source_y == line->dest_y) { + return; + } + /* iterate through all y */ + int dy = (line->dest_y < line->source_y) ? -1 : 1; + int y = line->source_y; + do { + y += dy; + if (settings->opaque(map, line->source_x, y)) { + line->is_blocked = true; + line->block_t = dy*(y - line->source_y); + break; + } + } while (y != line->dest_y); + + line->step_x = 0.0f; + line->step_y = (float)dy; + if (start_at_end) { + line->t = line->dest_t; + } + } + else if (line->source_y == line->dest_y) + { + line->dest_t = abs(line->dest_x - line->source_x); + + /* iterate through all x */ + int dx = (line->dest_x < line->source_x) ? -1 : 1; + int x = line->source_x; + do { + x += dx; + if (settings->opaque(map, x, line->source_y)) { + line->is_blocked = true; + line->block_t = dx*(x - line->source_x); + break; + } + } while (x != line->dest_x); + + line->step_x = (float)dx; + line->step_y = 0.0f; + if (start_at_end) { + line->t = line->dest_t; + } + } + else + { + bool blocked_at_end = false; + + /* hurray for a plethora of short but similar variable names! (yeah, I'm sorry... I blame all the poorly written legacy physics code I've had to work with) */ + bool b0; /* true if [xy]0 is blocked */ + bool b1; /* true if [xy]1 is blocked */ + bool mb0; /* true if m[xy]0 is blocked */ + bool mb1; /* true if m[xy]1 is blocked */ + int sx = line->source_x; /* source x */ + int sy = line->source_y; /* source y */ + int tx = line->dest_x; /* target x */ + int ty = line->dest_y; /* target y */ + int dx = (tx < sx) ? -1 : 1; /* sign of x. Useful for taking abs(x_val) */ + int dy = (ty < sy) ? -1 : 1; /* sign of y. Useful for taking abs(y_val) */ + + float gx = (float)dx; /* sign of x, float. Useful for taking fabs(x_val) */ + float gy = (float)dy; /* sign of y, float Useful for taking fabs(y_val) */ + float gabs = (float)(dx*dy); /* used in place of fabs(slope_val) */ + float val; + + /* Note that multiplying by dx, dy, gx, gy, or gabs are sometimes used in place of abs and fabs */ + /* I don't mind having a little (x2) code duplication--it's much better than debugging large macros :) */ + if (dx*(tx - sx) > dy*(ty - sy)) + { + line->dest_t = dx*(tx - sx); + + int x = 0; + int y0, y1; /* lowest/highest possible y based on inner/outer edge of tiles and lower/upper slopes */ + int my0, my1; /* low/high y based on the middle of tiles */ + float slope = fov_slope((float)(tx - sx), (float)(ty - sy)); + float lower_slope = fov_slope((float)(tx - sx), (float)(ty - sy) - gy*0.5f); + float upper_slope = fov_slope((float)(tx - sx), (float)(ty - sy) + gy*0.5f); + + /* include both source and dest x in loop, but don't include (source_x, source_y) or (target_x, target_y) */ + val = gx*0.5f*upper_slope + gx*GRID_EPSILON; + y1 = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + if (y1 != 0 && gabs * upper_slope > 1.0f && settings->permissiveness < 0.301f && settings->opaque(map, sx, sy + y1)) { + val = fov_slope(gx*(0.5f - settings->permissiveness + SLOPE_EPSILON), gy*0.5f) - gabs*GRID_EPSILON; + if (gabs * val < gabs * upper_slope) { + upper_slope = val; + } + } + + while (sx + x != tx) { + x += dx; + b0 = false; + b1 = false; + mb0 = false; + mb1 = false; + + /* Just in case floating point precision errors do try to show up (i.e., really long line or very unlucky), + * let us calculate values in the same manner as done for FoV to make the errors consistent */ + val = (float)x*lower_slope - gy*GRID_EPSILON; + my0 = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + val -= gx*0.5f*lower_slope; + y0 = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + + val = (float)x*upper_slope + gy*GRID_EPSILON; + my1 = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + val += gx*0.5f*upper_slope; + y1 = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + + /* check if lower_slope is blocked */ + if (settings->opaque(map, sx + x, sy + my0)) { + b0 = true; + mb0 = true; + if(sx + x != tx || sy + my0 != ty) { + lower_slope = fov_slope((float)x - gx*(0.5f - settings->permissiveness + SLOPE_EPSILON), (float)my0 + gy*0.5f) + gabs*GRID_EPSILON; + } + } + else if (y0 != my0 && settings->opaque(map, sx + x, sy + y0)) { + val = fov_slope((float)x - gx*(0.5f - settings->permissiveness + SLOPE_EPSILON), (float)y0 + gy*0.5f) + gabs*GRID_EPSILON; + if (gabs * val > gabs * lower_slope) { + lower_slope = val; + b0 = true; + } + } + + /* check if upper_slope is blocked */ + if (settings->opaque(map, sx + x, sy + my1)) { + b1 = true; + mb1 = true; + if(sx + x != tx || sy + my1 != ty) { + upper_slope = fov_slope((float)x + gx*(0.5f - settings->permissiveness + SLOPE_EPSILON), (float)my1 - gy*0.5f) - gabs*GRID_EPSILON; + } + } + else if (y1 != my1 && settings->opaque(map, sx + x, sy + y1)) { + val = fov_slope((float)x + gx*(0.5f - settings->permissiveness + SLOPE_EPSILON), (float)y1 - gy*0.5f) - gabs*GRID_EPSILON; + if (gabs * val < gabs * upper_slope) { + upper_slope = val; + b1 = true; + } + } + + /* being "pinched" isn't blocked, because one can still look diagonally */ + if (gabs * (lower_slope - upper_slope) > 5.0f * SLOPE_EPSILON || + gy*((float)(sy - ty) + (float)(tx - sx)*lower_slope - gy*0.5f) > -GRID_EPSILON || + gy*((float)(sy - ty) + (float)(tx - sx)*upper_slope + gy*0.5f) < GRID_EPSILON) + { + line->is_blocked = true; + line->block_t = dx*x; + break; + } + else if (mb0 && b1 || b0 && mb1) { + line->is_blocked = true; + line->block_t = dx*x; + if (sx + x == tx) { + blocked_at_end = true; + } + break; + } + } + + /* still try to target a blocked destination tile if it can be reached */ + line->step_x = gx; + if (line->is_blocked && !blocked_at_end) { + line->step_y = gx * slope; + } + else if (gabs * lower_slope > gabs * slope && 0.5f - fabs((float)(sy - ty) + (float)x*lower_slope) > GRID_EPSILON) { + line->step_y = gx * lower_slope; + } + else if (gabs * upper_slope < gabs * slope && 0.5f - fabs((float)(sy - ty) + (float)x*upper_slope) > GRID_EPSILON) { + line->step_y = gx * upper_slope; + } + else { + line->step_y = gx * slope; + } + if (start_at_end) { + line->t = dx*(tx - sx); + } + } + else + { + line->dest_t = dy*(ty - sy); + + int y = 0; + int x0, x1; /* lowest/highest possible y based on inner/outer edge of tiles and lower/upper slopes */ + int mx0, mx1; /* low/high y based on the middle of tiles */ + float slope = fov_slope((float)(ty - sy), (float)(tx - sx)); + float lower_slope = fov_slope((float)(ty - sy), (float)(tx - sx) - gx*0.5f); + float upper_slope = fov_slope((float)(ty - sy), (float)(tx - sx) + gx*0.5f); + + /* include both source and dest y in loop, but don't include (source_x, source_y) or (target_x, target_y) */ + val = gy*0.5f*upper_slope + gy*GRID_EPSILON; + x1 = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + if (x1 != 0 && gabs * upper_slope > 1.0f && settings->permissiveness < 0.301f && settings->opaque(map, sx + x1, sy)) { + val = fov_slope(gy*(0.5f - settings->permissiveness + SLOPE_EPSILON), gx*0.5f) - gabs*GRID_EPSILON; + if (gabs * val < gabs * upper_slope) { + upper_slope = val; + } + } + + while (sy + y != ty) { + y += dy; + b0 = false; + b1 = false; + mb0 = false; + mb1 = false; + + /* Just in case floating point precision errors do try to show up (i.e., really long line or very unlucky), + * let us calculate values in the same manner as done for FoV to make the errors consistent */ + val = (float)y*lower_slope - gx*GRID_EPSILON; + mx0 = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + val -= gy*0.5f*lower_slope; + x0 = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + + val = (float)y*upper_slope + gx*GRID_EPSILON; + mx1 = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + val += gy*0.5f*upper_slope; + x1 = (val < 0.0f) ? -(int)(0.5f - val) : (int)(0.5f + val); + + /* check if lower_slope is blocked */ + if (settings->opaque(map, sx + mx0, sy + y)) { + b0 = true; + mb0 = true; + if(sy + y != ty || sx + mx0 != tx) { + lower_slope = fov_slope((float)y - gy*(0.5f - settings->permissiveness + SLOPE_EPSILON), (float)mx0 + gx*0.5f) + gabs*GRID_EPSILON; + } + } + else if (x0 != mx0 && settings->opaque(map, sx + x0, sy + y)) { + val = fov_slope((float)y - gy*(0.5f - settings->permissiveness + SLOPE_EPSILON), (float)x0 + gx*0.5f) + gabs*GRID_EPSILON; + if (gabs * val > gabs * lower_slope) { + lower_slope = val; + b0 = true; + } + } + + /* check if upper_slope is blocked */ + if (settings->opaque(map, sx + mx1, sy + y)) { + b1 = true; + mb1 = true; + if(sy + y != ty || sx + mx1 != tx) { + upper_slope = fov_slope((float)y + gy*(0.5f - settings->permissiveness + SLOPE_EPSILON), (float)mx1 - gx*0.5f) - gabs*GRID_EPSILON; + } + } + else if (x1 != mx1 && settings->opaque(map, sx + x1, sy + y)) { + val = fov_slope((float)y + gy*(0.5f - settings->permissiveness + SLOPE_EPSILON), (float)x1 - gx*0.5f) - gabs*GRID_EPSILON; + if (gabs * val < gabs * upper_slope) { + upper_slope = val; + b1 = true; + } + } + + /* being "pinched" isn't blocked, because one can still look diagonally */ + if (gabs * (lower_slope - upper_slope) > 5.0f * SLOPE_EPSILON || + gx*((float)(sx - tx) + (float)(ty - sy)*lower_slope - gx*0.5f) > -GRID_EPSILON || + gx*((float)(sx - tx) + (float)(ty - sy)*upper_slope + gx*0.5f) < GRID_EPSILON) + { + line->is_blocked = true; + line->block_t = dy*y; + break; + } + else if (mb0 && b1 || b0 && mb1) { + line->is_blocked = true; + line->block_t = dy*y; + if (sy + y == ty) { + blocked_at_end = true; + } + break; + } + } + + /* still try to target a blocked destination tile if it can be reached */ + line->step_y = gy; + if (line->is_blocked && !blocked_at_end) { + line->step_x = gy * slope; + } + else if (gabs * lower_slope > gabs * slope && 0.5f - fabs((float)(sx - tx) + (float)y*lower_slope) > GRID_EPSILON) { + line->step_x = gy * lower_slope; + } + else if (gabs * upper_slope < gabs * slope && 0.5f - fabs((float)(sx - tx) + (float)y*upper_slope) > GRID_EPSILON) { + line->step_x = gy * upper_slope; + } + else { + line->step_x = gy * slope; + } + if (start_at_end) { + line->t = dy*(ty - sy); + } + } + } + + if (start_at_end && line->is_blocked) { + line->t = line->block_t; + } +} + diff --git a/src/fov/fov.h b/src/fov/fov.h index 3773cb32e90a54776f0f997ac930fc53c39aa8e0..8c3ab0b8c8f8f2aeba4555449fd7d56e35be4aa7 100644 --- a/src/fov/fov.h +++ b/src/fov/fov.h @@ -33,6 +33,12 @@ #include <stdbool.h> #include <stddef.h> +// Floating point espsilons to guarantee smooth floating point transitions and behavior in an integer grid. +// There were no, and are no, floating point precision guarantees for maps greater than 1024x1024. Use double precision if desired. +// Nevertheless, map sizes greater than 10000x10000 should still behave reasonably. +#define GRID_EPSILON 2.0e-6f +#define SLOPE_EPSILON 1.53e-5f + #ifdef __cplusplus extern "C" { #endif @@ -97,9 +103,50 @@ typedef struct { /** Size of pre-calculated data. \internal */ unsigned numheights; + /** A measure of how much an opaque tile blocks a tile. 0 for square, 0.5 for diamond shape. Shapes extend to the edge of the tile. */ + float permissiveness; + /** \endcond */ } fov_settings_type; +/** struct of calculated data for field of vision lines */ +typedef struct { + /** x from which the line originates */ + int source_x; + + /** y from which the line originates */ + int source_y; + + /** x destination of line */ + /* This parameter may be deleted since it appears to be unnecessary */ + int dest_x; + + /** y destination of line */ + /* This parameter may be deleted since it appears to be unnecessary */ + int dest_y; + + /** Parametrization variable used to count the "steps" of the line */ + int t; + + /** Parametrization value of t for where line is blocked, if applicable */ + /* This parameter may be deleted since it appears to be unnecessary */ + int block_t; + + /** Parametrization value of t for where line reaches destination tile */ + int dest_t; + + /** Size of single step in x direction, so for t'th step, delta_x = t*step_x */ + float step_x; + + /** Size of single step in y direction, so for t'th step, delta_y = t*step_y */ + float step_y; + + /** Whether or not the line is blocked */ + /* This parameter may be deleted since it appears to be unnecessary */ + bool is_blocked; +} fov_line_data; + + /** The opposite direction to that given. */ #define fov_direction_opposite(direction) ((fov_direction_type)(((direction)+4)&0x7)) @@ -240,7 +287,7 @@ void fov_beam(fov_settings_type *settings, void *map, void *source, /** * Calculate a field of view from source at (x,y), pointing - * in the given direction (in degrees) and with the given angle. The larger + * in the given direction (in dx, dy) and with the given angle. The larger * the angle, the wider, "less focused" the beam. Each side of the * line pointing in the direction from the source will be half the * angle given such that the angle specified will be represented on @@ -252,16 +299,40 @@ void fov_beam(fov_settings_type *settings, void *map, void *source, * \param source_x x-axis coordinate from which to start. * \param source_y y-axis coordinate from which to start. * \param radius Euclidean distance from (x,y) after which to stop. - * \param dir_angle Direction angle, in degrees. + * \param dx Beam direction, delta x + * \param dy Beam direction, delta y * \param beam_angle The angle at the base of the beam of light, in degrees. */ void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source, int source_x, int source_y, unsigned radius, - float dir_angle, float beam_angle + float dx, float dy, float beam_angle ); +/** + * Calculate a line based on field of view (or whatever the "opaque" function) + * from source to destination (x, y). This will avoid opaque tiles if possible. + * If an unobstructed line to destination tile isn't found, then default to a + * bresenham line. + * + * \param settings Pointer to data structure containing settings. + * \param map Pointer to map data structure to be passed to callbacks. + * \param source Pointer to data structure holding source of light. + * \param line Pointer to data structure to store line information. + * \param source_x x-axis coordinate from which to start. + * \param source_y y-axis coordinate from which to start. + * \param dest_x x-axis coordinate from which to end. + * \param dest_y y-axis coordinate from which to end. + * \param start_at_end if true, the line will begin at the destination (x, y) and continue away from source (x, y) + */ +void fov_create_los_line(fov_settings_type *settings, void *map, void *source, fov_line_data *line, + int source_x, int source_y, + int dest_x, int dest_y, + bool start_at_end +); + #ifdef __cplusplus } /* extern "C" */ #endif #endif +