From 9df750b26934aac0aff87844f2406597c976a1eb Mon Sep 17 00:00:00 2001 From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54> Date: Sun, 11 Sep 2011 18:55:15 +0000 Subject: [PATCH] First half of the big LOS/FOV update:<ul> <li>FoV no longer improperly peeks around blocked corners</li> <li>LoS is now equivalent to FoV</li> <li>"Smart" LoS: if you can see it, you can target and project to it</li> <li>FoV has a permissiveness setting that can be set for each level</li> <li>Distance metrics are now consistent for FoV, AoE, and talent ranges (defaults to rounded circle)</li> <li>fov_beam_any_angle function now uses "dx", "dy" as input instead of "angle"</li> </ul> git-svn-id: http://svn.net-core.org/repos/t-engine4@4368 51575b47-30f0-44d4-a5cc-537603b46e54 --- game/engines/default/engine/Actor.lua | 57 +- game/engines/default/engine/Map.lua | 4 +- game/engines/default/engine/Projectile.lua | 15 + game/engines/default/engine/Target.lua | 37 +- game/engines/default/engine/ai/talented.lua | 3 +- .../default/engine/interface/ActorProject.lua | 104 +++- .../default/engine/interface/PlayerMouse.lua | 3 +- game/engines/default/engine/utils.lua | 46 +- .../default/modules/boot/data/talents.lua | 3 +- game/modules/example/data/talents.lua | 3 +- .../modules/example_realtime/data/talents.lua | 3 +- game/modules/tome/ai/escort.lua | 3 +- game/modules/tome/ai/heal.lua | 3 +- game/modules/tome/ai/party.lua | 5 +- game/modules/tome/ai/shadow.lua | 5 +- game/modules/tome/ai/tactical.lua | 19 +- game/modules/tome/ai/worldnpcs.lua | 5 +- game/modules/tome/class/Actor.lua | 29 +- game/modules/tome/class/Game.lua | 5 +- game/modules/tome/class/NPC.lua | 42 ++ game/modules/tome/class/Player.lua | 47 +- game/modules/tome/class/interface/Combat.lua | 13 +- .../tome/data/talents/celestial/combat.lua | 3 +- .../tome/data/talents/celestial/guardian.lua | 2 +- .../talents/chronomancy/spacetime-folding.lua | 15 +- .../tome/data/talents/corruptions/scourge.lua | 7 +- .../tome/data/talents/cunning/ambush.lua | 5 +- .../tome/data/talents/cunning/dirty.lua | 7 +- .../tome/data/talents/cunning/lethality.lua | 5 +- .../tome/data/talents/cunning/poisons.lua | 5 +- .../data/talents/cunning/shadow-magic.lua | 5 +- .../tome/data/talents/cursed/dark-figure.lua | 3 +- .../tome/data/talents/cursed/darkness.lua | 14 +- .../tome/data/talents/cursed/endless-hunt.lua | 7 +- .../data/talents/cursed/force-of-will.lua | 12 +- .../tome/data/talents/cursed/shadows.lua | 3 +- .../tome/data/talents/cursed/slaughter.lua | 16 +- .../tome/data/talents/cursed/strife.lua | 55 +- game/modules/tome/data/talents/gifts/call.lua | 3 +- .../tome/data/talents/gifts/cold-drake.lua | 3 +- .../tome/data/talents/gifts/sand-drake.lua | 5 +- .../modules/tome/data/talents/gifts/slime.lua | 3 +- .../tome/data/talents/misc/horrors.lua | 23 +- .../tome/data/talents/misc/inscriptions.lua | 5 +- game/modules/tome/data/talents/misc/npcs.lua | 55 +- .../talents/psionic/augmented-mobility.lua | 15 +- .../tome/data/talents/psionic/projection.lua | 11 +- .../data/talents/psionic/psi-fighting.lua | 3 +- .../data/talents/spells/arcane-shield.lua | 5 +- .../modules/tome/data/talents/spells/fire.lua | 7 +- .../tome/data/talents/spells/golem.lua | 81 +-- .../tome/data/talents/spells/staff-combat.lua | 7 +- .../tome/data/talents/techniques/2hweapon.lua | 9 +- .../talents/techniques/battle-tactics.lua | 3 +- .../talents/techniques/combat-techniques.lua | 18 +- .../data/talents/techniques/dualweapon.lua | 7 +- .../data/talents/techniques/field-control.lua | 3 +- .../talents/techniques/finishing-moves.lua | 61 +- .../data/talents/techniques/grappling.lua | 26 +- .../data/talents/techniques/kick-boxing.lua | 56 +- .../talents/techniques/magical-combat.lua | 15 +- .../tome/data/talents/techniques/mobility.lua | 3 +- .../tome/data/talents/techniques/pugilism.lua | 22 +- .../tome/data/talents/techniques/thuggery.lua | 3 +- .../talents/techniques/unarmed-discipline.lua | 5 +- .../data/talents/techniques/weaponshield.lua | 7 +- .../tome/data/talents/undeads/ghoul.lua | 19 +- game/modules/tome/data/timed_effects.lua | 31 +- .../data/zones/crypt-kryl-feijan/zone.lua | 3 +- src/core_lua.c | 9 +- src/fov.c | 280 ++++++++- src/fov/fov.c | 562 ++++++++++++++---- src/fov/fov.h | 77 ++- 73 files changed, 1497 insertions(+), 566 deletions(-) diff --git a/game/engines/default/engine/Actor.lua b/game/engines/default/engine/Actor.lua index 3d0a5d2ac6..ff82df52d4 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 a2847eaf6d..2c174c25db 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 a6e9c3fab2..79a20e6377 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 bc3de39a6a..f7213e77d6 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 c9b9f68135..d10945d3eb 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 21e51a46ae..bf01ff868c 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 5f994bac29..a2e9c5806b 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 6b23ef0f8d..468fa64235 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 8477f2e277..fca600766a 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 1ea53df8fe..e5e96b1975 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 1ea53df8fe..e5e96b1975 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 b032e9d10e..96adbdf9a5 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 743711be2a..a4ba928ade 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 b335c8734e..497efd165e 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 ae340f6fd7..b7465549a3 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 3e2b1ce3cd..a354c16172 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 8d8002e982..1b92fb5c4a 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 2047b840ab..f75e01582b 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 d095cd11ca..7e8005e3e2 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 ea27ce4e93..6728a71b6d 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 2d2a89ad20..4fd450625e 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 146261f0ab..1364a8184b 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 d15b88c029..31e5fb0f32 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 a1870bbd10..77152c0642 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 3a065f0046..1cc31c224b 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 2b309cdc58..05154b40d7 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 5816214bc0..cf2779cf6c 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 81371b9b30..acad29bfdd 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 4c99f44ef8..d4a882a81d 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 ae5ea3b8bc..043472e38b 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 38c705ed80..dd52df29a5 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 37ac541722..185e6783a1 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 7aa69f7020..f7ee2c2fac 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 93be59e198..fcded949b6 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 094b10fd67..8976595e4b 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 d575a7e735..7621908651 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 fb823f6b00..49893170ea 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 6f32f5bdd4..bdda4f8431 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 c580789a6a..9a777233a8 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 65aad04cf9..50cf5d1ea7 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 8a2a312220..d45db8bf6b 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 f8c9ad0e96..f4b675cbe7 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 bf85746a1e..b7fd0b139a 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 a738ef2781..e86b23805a 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 9724983766..da8f82400f 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 8b2d58dff6..0e0ee645dc 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 0a51a29bd4..a6041032f3 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 3ae2272d96..3d7880f8e4 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 410cf2edd2..6ddc1f2ff7 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 691bb8a1e4..9853f190b3 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 ffafee10aa..8d462f14f9 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 55099fa278..e0a052da8b 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 4014bb5c42..ca630d05d5 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 f87eebc560..78b2796be2 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 902042b0d1..a96554f21d 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 c876393700..0aa630e70f 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 9cc78c4660..171b1550f7 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 e10d58b9d8..0c64bfd95e 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 b783a2fb18..72dcf074bd 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 b204d59158..8763b48d08 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 d5d14c720f..2d7ae9aac1 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 5069ad22fb..4420a7a76b 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 d373e42392..72c4a59c34 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 bedf7705c6..6af638a828 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 2bf8153d4f..3a30676fc6 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 a3fe57286e..f6d7a37027 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 a15eaf78ea..59d2ed26ca 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 e8a6ac34e1..63ed51af59 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 fd200fb18b..e70e4e4612 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 038043de91..9348c5a476 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 9e0508444f..82115d49ca 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 3500c19525..04cbc79888 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 3773cb32e9..8c3ab0b8c8 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 + -- GitLab