diff --git a/game/engine/Actor.lua b/game/engine/Actor.lua index 703b0000221cf37dec337d88c33f994eff6eb600..68f34478f9314bd6a9a478e0d9c571651c510d35 100644 --- a/game/engine/Actor.lua +++ b/game/engine/Actor.lua @@ -104,6 +104,12 @@ function _M:canMove(x, y, terrain_only) end end +--- Get the "path string" for this actor +-- See Map:addPathString() for more info +function _M:getPathString() + return "" +end + --- Teleports randomly to a passable grid -- @param x the coord of the teleporatation -- @param y the coord of the teleporatation @@ -133,17 +139,28 @@ end --- Knock back the actor function _M:knockback(srcx, srcy, dist) + print("[KNOCKBACK] from", srcx, srcy, "over", dist) + local l = line.new(srcx, srcy, self.x, self.y, true) local lx, ly = l(true) + local ox, oy = lx, ly dist = dist - 1 + print("[KNOCKBACK] try", lx, ly, dist) + while game.level.map:isBound(lx, ly) 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) + print("[KNOCKBACK] try", lx, ly, dist, "::", game.level.map:checkAllEntities(lx, ly, "block_move", self)) end if game.level.map:isBound(lx, ly) and not game.level.map:checkAllEntities(lx, ly, "block_move", self) then + print("[KNOCKBACK] ok knocked to", lx, ly, "::", game.level.map:checkAllEntities(lx, ly, "block_move", self)) self:move(lx, ly, true) + elseif game.level.map:isBound(ox, oy) and not game.level.map:checkAllEntities(ox, oy, "block_move", self) then + print("[KNOCKBACK] failsafe knocked to", ox, oy, "::", game.level.map:checkAllEntities(ox, oy, "block_move", self)) + self:move(ox, oy, true) end end diff --git a/game/engine/Astar.lua b/game/engine/Astar.lua index 7733af4fddb8b3b6e8988391dbe9f0caf77c4340..fa87eafaa07711c73228194b6a13b1ad0f8abe64 100644 --- a/game/engine/Astar.lua +++ b/game/engine/Astar.lua @@ -73,20 +73,42 @@ function _M:calc(sx, sy, tx, ty, use_has_seen) local f_score = {[start] = self:heuristic(sx, sy, tx, ty)} local came_from = {} - local checkPos = function(node, nx, ny) - local nnode = self:toSingle(nx, ny) - if not closed[nnode] and self.map:isBound(nx, ny) and ((use_has_seen and not self.map.has_seens(nx, ny)) or not self.map:checkEntity(nx, ny, Map.TERRAIN, "block_move", self.actor, nil, true)) then - local tent_g_score = g_score[node] + 1 -- we can adjust hre for difficult passable terrain - local tent_is_better = false - if not open[nnode] then open[nnode] = true; tent_is_better = true - elseif tent_g_score < g_score[nnode] then tent_is_better = true + local cache = self.map._fovcache.path_caches[self.actor:getPathString()] + local checkPos + if cache then + checkPos = function(node, nx, ny) + local nnode = self:toSingle(nx, ny) + if not closed[nnode] and self.map:isBound(nx, ny) and ((use_has_seen and not self.map.has_seens(nx, ny)) or not cache:get(nx, ny)) then + local tent_g_score = g_score[node] + 1 -- we can adjust hre for difficult passable terrain + local tent_is_better = false + if not open[nnode] then open[nnode] = true; tent_is_better = true + elseif tent_g_score < g_score[nnode] then tent_is_better = true + end + + if tent_is_better then + came_from[nnode] = node + g_score[nnode] = tent_g_score + h_score[nnode] = self:heuristic(tx, ty, nx, ny) + f_score[nnode] = g_score[nnode] + h_score[nnode] + end end + end + else + checkPos = function(node, nx, ny) + local nnode = self:toSingle(nx, ny) + if not closed[nnode] and self.map:isBound(nx, ny) and ((use_has_seen and not self.map.has_seens(nx, ny)) or not self.map:checkEntity(nx, ny, Map.TERRAIN, "block_move", self.actor, nil, true)) then + local tent_g_score = g_score[node] + 1 -- we can adjust hre for difficult passable terrain + local tent_is_better = false + if not open[nnode] then open[nnode] = true; tent_is_better = true + elseif tent_g_score < g_score[nnode] then tent_is_better = true + end - if tent_is_better then - came_from[nnode] = node - g_score[nnode] = tent_g_score - h_score[nnode] = self:heuristic(tx, ty, nx, ny) - f_score[nnode] = g_score[nnode] + h_score[nnode] + if tent_is_better then + came_from[nnode] = node + g_score[nnode] = tent_g_score + h_score[nnode] = self:heuristic(tx, ty, nx, ny) + f_score[nnode] = g_score[nnode] + h_score[nnode] + end end end end diff --git a/game/engine/Map.lua b/game/engine/Map.lua index 9f8f9ac86cfe3b55ba8e563d9b1815c54992bb35..a649c441a46999c604dafb5357f403ea7affeb4e 100644 --- a/game/engine/Map.lua +++ b/game/engine/Map.lua @@ -165,6 +165,7 @@ function _M:init(w, h) self.has_seens = {} self.remembers = {} self.effects = {} + self.path_strings = {} for i = 0, w * h - 1 do self.map[i] = {} end self:loaded() @@ -200,7 +201,23 @@ function _M:makeCMap() block_sight = core.fov.newCache(self.w, self.h), block_esp = core.fov.newCache(self.w, self.h), block_sense = core.fov.newCache(self.w, self.h), + path_caches = {}, } + for i, ps in ipairs(self.path_strings) do + self._fovcache.path_caches[ps] = core.fov.newCache(self.w, self.h) + end +end + +--- Adds a "path string" to the map +-- "Path strings" are strings defining what terrain an actor can cross. Their format is left to the module to decide (by overloading Actor:getPathString() )<br/> +-- They are totally optional as they re only used to compute A* paths adn the likes and even then the algorithms still work without them, only slower<br/> +-- If you use them the block_move function of your Grid class must be able to handle either an actor or a "path string" as their third argument +function _M:addPathString(ps) + for i, eps in ipairs(self.path_strings) do + if eps == ps then return end + end + self.path_strings[#self.path_strings+1] = ps + if self._fovcache then self._fovcache.path_caches[ps] = core.fov.newCache(self.w, self.h) end end function _M:loaded() @@ -291,7 +308,7 @@ function _M:cleanFOV() end --- Updates the map on the given spot --- This updates many things, from the C map object, the FOC caches, the minimap if it exists, ... +-- This updates many things, from the C map object, the FOV caches, the minimap if it exists, ... function _M:updateMap(x, y) local g = self(x, y, TERRAIN) local o = self(x, y, OBJECT) @@ -354,6 +371,12 @@ function _M:updateMap(x, y) else self._fovcache.block_esp:set(x, y, false) end if self:checkAllEntities(x, y, "block_sense", self.actor_player) then self._fovcache.block_sense:set(x, y, true) else self._fovcache.block_sense:set(x, y, false) end + + -- Update path caches from path strings + for i = 1, #self.path_strings do + local ps = self.path_strings[i] + self._fovcache.path_caches[ps]:set(x, y, self:checkEntity(x, y, "block_move", ps, false, true)) + end end --- Sets/gets a value from the map diff --git a/game/modules/example/class/Actor.lua b/game/modules/example/class/Actor.lua index 7e54dd2dd92fe3cbca41913329cb457b56ef4a10..e79da0035897e375b961ec8cbd13f751f5354aca 100644 --- a/game/modules/example/class/Actor.lua +++ b/game/modules/example/class/Actor.lua @@ -161,7 +161,7 @@ function _M:preUseTalent(ab, silent) return false end else - if ab.power and self:getMana() < ab.power * (100 + self.fatigue) / 100 then + if ab.power and self:getPower() < ab.power then game.logPlayer(self, "You do not have enough power to cast %s.", ab.name) return false end @@ -223,7 +223,7 @@ function _M:getTalentFullDescription(t) else d[#d+1] = "#6fff83#Use mode: #00FF00#Activable" end - if t.power or t.sustain_power then d[#d+1] = "#6fff83#Mana cost: #7fffd4#"..(t.power or t.sustain_power) end + if t.power or t.sustain_power then d[#d+1] = "#6fff83#Power cost: #7fffd4#"..(t.power or t.sustain_power) end if self:getTalentRange(t) > 1 then d[#d+1] = "#6fff83#Range: #FFFFFF#"..self:getTalentRange(t) else d[#d+1] = "#6fff83#Range: #FFFFFF#melee/personal" end diff --git a/game/modules/example/data/birth/descriptors.lua b/game/modules/example/data/birth/descriptors.lua index e3c71b1907915f235ef837e458d2e917a5c37be5..64f6113656c2bec3f749ad28a715ac178670766b 100644 --- a/game/modules/example/data/birth/descriptors.lua +++ b/game/modules/example/data/birth/descriptors.lua @@ -36,7 +36,10 @@ newBirthDescriptor{ name = "Destroyer", desc = { - "Boom!", + "Crashhhhh!", + }, + talents = { + [ActorTalents.T_KICK]=1, }, } @@ -47,4 +50,7 @@ newBirthDescriptor{ { "Zshhhhhhhh!", }, + talents = { + [ActorTalents.T_ACID_SPRAY]=1, + }, } diff --git a/game/modules/example/data/talents.lua b/game/modules/example/data/talents.lua index da57bd68b205d77cc81be4d184b9a2d487f148f8..02d1c92317aba8f1a791ba7613ab7676fc34e3a1 100644 --- a/game/modules/example/data/talents.lua +++ b/game/modules/example/data/talents.lua @@ -25,10 +25,36 @@ newTalent{ points = 1, cooldown = 6, power = 2, + range = 1, 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 + + target:knockback(self.x, self.y, 2 + self:getDex()) return true end, info = function(self, t) return "Kick!" end, } + +newTalent{ + name = "Acid Spray", + type = {"role/combat", 1}, + points = 1, + cooldown = 6, + power = 2, + range = 6, + action = function(self, t) + local tg = {type="ball", range=self:getTalentRange(t), radius=1, talent=t} + local x, y = self:getTarget(tg) + if not x or not y then return nil end + self:project(tg, x, y, DamageType.ACID, 1 + self:getDex(), {type="acid"}) + return true + end, + info = function(self, t) + return "Zshhhhhhhhh!" + end, +} diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua index 3daac2c678ca1b2d752d3c2667dbb23f8d38ffb7..7a4059f5e87e4b34de3d7977c4749da506a78652 100644 --- a/game/modules/tome/class/Actor.lua +++ b/game/modules/tome/class/Actor.lua @@ -205,6 +205,18 @@ function _M:move(x, y, force) return moved end +--- Get the "path string" for this actor +-- See Map:addPathString() for more info +function _M:getPathString() + local ps = self.open_door and "open_door=true;can_pass={" or "can_pass={" + for what, check in pairs(self.can_pass) do + ps = ps .. what.."="..check.."," + end + ps = ps.."}" + print("[PATH STRING] for", self.name, " :=: ", ps) + return ps +end + --- Blink through walls function _M:probabilityTravel(x, y, dist) local dirx, diry = x - self.x, y - self.y diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua index 055a6a79cf3d2058e3edd61fbf54c27aa822c117..647ca26579eb3d7950728aeb37798706972bdd7a 100644 --- a/game/modules/tome/class/Game.lua +++ b/game/modules/tome/class/Game.lua @@ -301,7 +301,14 @@ function _M:changeLevel(lev, zone) self:stopMusic() end + -- Update the minimap self:setupMiniMap() + + -- Tell the map to use path strings to speed up path calculations + for uid, e in pairs(self.level.entities) do + self.level.map:addPathString(e:getPathString()) + end + self.level.map:redisplay() end function _M:getPlayer() diff --git a/game/modules/tome/class/Grid.lua b/game/modules/tome/class/Grid.lua index f6df667897e586d0233680e33dcbeee9a9c34af2..7a2c6fb32239cb25dfe5b9b0a5e346eb81f7635e 100644 --- a/game/modules/tome/class/Grid.lua +++ b/game/modules/tome/class/Grid.lua @@ -28,16 +28,22 @@ function _M:init(t, no_default) end function _M:block_move(x, y, e, act, couldpass) + -- Path strings + if not e then e = {} + elseif type(e) == "string" then + e = loadstring(e)() + end + -- Open doors - if self.door_opened and act then - game.level.map(x, y, engine.Map.TERRAIN, game.zone.grid_list.DOOR_OPEN) + if self.door_opened and e.open_door and act then + game.level.map(x, y, engine.Map.TERRAIN, game.zone.grid_list[self.door_opened]) return true - elseif self.door_opened and not couldpass then + elseif self.door_opened and e.open_door and not couldpass then return true end -- Pass walls - if e and self.can_pass and e.can_pass then + if self.can_pass and e.can_pass then for what, check in pairs(e.can_pass) do if self.can_pass[what] and self.can_pass[what] <= check then return false end end diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua index 64eedde2f17e352d69414717b9cf68d2ae432e5d..22163119faf8f6468b5295808a5262daf78e7950 100644 --- a/game/modules/tome/class/Player.lua +++ b/game/modules/tome/class/Player.lua @@ -63,6 +63,7 @@ function _M:init(t, no_default) t.color_b=t.color_b or 230 t.player = true + t.open_door = true t.type = t.type or "humanoid" t.subtype = t.subtype or "player" t.faction = t.faction or "players" diff --git a/game/modules/tome/data/general/npcs/aquatic_demon.lua b/game/modules/tome/data/general/npcs/aquatic_demon.lua index 18637d801f6ea03ca74fb905de8c699822119eb6..69fc005bc2e6075c07a7db2c0f88773df5eafab3 100644 --- a/game/modules/tome/data/general/npcs/aquatic_demon.lua +++ b/game/modules/tome/data/general/npcs/aquatic_demon.lua @@ -33,6 +33,7 @@ newEntity{ combat_armor = 1, combat_def = 1, combat = { dam=5, atk=15, apr=7, dammod={str=0.6} }, max_life = resolvers.rngavg(100,120), + open_door = true, rank = 2, size_category = 3, can_breath={water=1}, diff --git a/game/modules/tome/data/general/npcs/ghoul.lua b/game/modules/tome/data/general/npcs/ghoul.lua index 4f80d4ce9443274bc73c232f195f2d9824ebea58..fd07dba3bdd76953ee8bd60352ff133fd8400888 100644 --- a/game/modules/tome/data/general/npcs/ghoul.lua +++ b/game/modules/tome/data/general/npcs/ghoul.lua @@ -37,6 +37,8 @@ newEntity{ resolvers.tmasteries{ ["technique/other"]=1, }, + open_door = true, + blind_immune = 1, see_invisible = 2, undead = 1, diff --git a/game/modules/tome/data/general/npcs/minotaur.lua b/game/modules/tome/data/general/npcs/minotaur.lua index de2e7417a4bbf420138adb3b27ce9b580ab0cada..17ef1ca51d8ea062f4382ae56bbc896286c9075f 100644 --- a/game/modules/tome/data/general/npcs/minotaur.lua +++ b/game/modules/tome/data/general/npcs/minotaur.lua @@ -36,6 +36,8 @@ newEntity{ rank = 2, size_category = 4, + open_door = true, + autolevel = "warrior", ai = "dumb_talented_simple", ai_state = { talent_in=5, }, energy = { mod=1.2 }, diff --git a/game/modules/tome/data/general/npcs/mummy.lua b/game/modules/tome/data/general/npcs/mummy.lua index 5e7ac091c9a6530df5b750aa672c54b5646ad72f..9a04641a89d6b9c8770c128ee9f3268944474c85 100644 --- a/game/modules/tome/data/general/npcs/mummy.lua +++ b/game/modules/tome/data/general/npcs/mummy.lua @@ -34,6 +34,8 @@ newEntity{ rank = 2, size_category = 3, + open_door = true, + resolvers.tmasteries{ ["technique/2hweapon"]=1, }, blind_immune = 1, diff --git a/game/modules/tome/data/general/npcs/orc.lua b/game/modules/tome/data/general/npcs/orc.lua index 5d6551a0f329e4c2fb382182379ca46ec2a7f064..29d12693e6aea35ccaba7171a125f4e18a3f1b28 100644 --- a/game/modules/tome/data/general/npcs/orc.lua +++ b/game/modules/tome/data/general/npcs/orc.lua @@ -34,6 +34,8 @@ newEntity{ rank = 2, size_category = 3, + open_door = true, + autolevel = "warrior", ai = "dumb_talented_simple", ai_state = { talent_in=3, }, energy = { mod=1 }, diff --git a/game/modules/tome/data/general/npcs/skeleton.lua b/game/modules/tome/data/general/npcs/skeleton.lua index 09a9e38cb75337dc4308642559be18c674bf4ad9..58ed27472590ca146151a200ee21f48a257b53a7 100644 --- a/game/modules/tome/data/general/npcs/skeleton.lua +++ b/game/modules/tome/data/general/npcs/skeleton.lua @@ -39,6 +39,8 @@ newEntity{ resolvers.tmasteries{ ["technique/other"]=0.3, ["technique/2hweapon-offense"]=0.3, ["technique/2hweapon-cripple"]=0.3 }, + open_door = true, + blind_immune = 1, fear_immune = 1, see_invisible = 2, diff --git a/game/modules/tome/data/general/npcs/thieve.lua b/game/modules/tome/data/general/npcs/thieve.lua index ca6737f8bf212445882afe09c8b525ce8284f50a..98b27b627a9a92bd93371df3a6796fe26517a89c 100644 --- a/game/modules/tome/data/general/npcs/thieve.lua +++ b/game/modules/tome/data/general/npcs/thieve.lua @@ -39,6 +39,8 @@ newEntity{ rank = 2, size_category = 3, + open_door = true, + autolevel = "rogue", ai = "dumb_talented_simple", ai_state = { talent_in=5, }, energy = { mod=1 }, diff --git a/game/modules/tome/data/general/npcs/troll.lua b/game/modules/tome/data/general/npcs/troll.lua index daa2985dfac68a35f03a99132c818da2b8325099..af16334cffb44448ad854a027cba8ee8c6ef2fd1 100644 --- a/game/modules/tome/data/general/npcs/troll.lua +++ b/game/modules/tome/data/general/npcs/troll.lua @@ -41,6 +41,8 @@ newEntity{ energy = { mod=1 }, stats = { str=20, dex=8, mag=6, con=16 }, + open_door = true, + resolvers.tmasteries{ ["technique/other"]=0.3 }, resists = { [DamageType.FIRE] = -50 }, diff --git a/game/modules/tome/data/general/npcs/vampire.lua b/game/modules/tome/data/general/npcs/vampire.lua index 60ad6044e8304b17782ea321b19a8ab4a0f98c31..099e9e7627607f14540d3bfd7c2c5f42346e0022 100644 --- a/game/modules/tome/data/general/npcs/vampire.lua +++ b/game/modules/tome/data/general/npcs/vampire.lua @@ -55,6 +55,8 @@ newEntity{ size_category = 3, rank = 2, + open_door = true, + resolvers.tmasteries{ ["technique/other"]=0.5, ["spell/phantasm"]=0.8, }, resists = { [DamageType.COLD] = 80, [DamageType.NATURE] = 80, [DamageType.LIGHT] = -50, }, diff --git a/game/modules/tome/data/general/npcs/wight.lua b/game/modules/tome/data/general/npcs/wight.lua index 0f7717cbe5e49003761b21d5f554ff1269d68c39..14d2b4f756e0c178ec7fc76d2cf987c6bc25550e 100644 --- a/game/modules/tome/data/general/npcs/wight.lua +++ b/game/modules/tome/data/general/npcs/wight.lua @@ -45,6 +45,8 @@ newEntity{ rank = 2, size_category = 3, + open_door = true, + resolvers.tmasteries{ ["technique/other"]=0.3, ["spell/air"]=0.3, ["spell/fire"]=0.3 }, resists = { [DamageType.COLD] = 80, [DamageType.FIRE] = 20, [DamageType.LIGHTNING] = 40, [DamageType.PHYSICAL] = 35, [DamageType.LIGHT] = -50, }, diff --git a/src/core_lua.c b/src/core_lua.c index d4a38880c955cfe7393210d60949d23352c49a78..b9dbf5e42407429a7213ccaf73214be0f7c0bfa5 100644 --- a/src/core_lua.c +++ b/src/core_lua.c @@ -274,6 +274,16 @@ static int lua_fovcache_set(lua_State *L) return 0; } +static int lua_fovcache_get(lua_State *L) +{ + struct lua_fovcache *cache = (struct lua_fovcache*)auxiliar_checkclass(L, "fov{cache}", 1); + int x = luaL_checknumber(L, 2); + int y = luaL_checknumber(L, 3); + + lua_pushboolean(L, cache->cache[x + y * cache->w]); + return 1; +} + static const struct luaL_reg fovlib[] = { {"new", lua_new_fov}, @@ -295,6 +305,7 @@ static const struct luaL_reg fov_reg[] = static const struct luaL_reg fovcache_reg[] = { {"set", lua_fovcache_set}, + {"get", lua_fovcache_get}, {NULL, NULL}, };