Skip to content
Snippets Groups Projects
Commit fe3f0311 authored by dg's avatar dg
Browse files

* Actors now compute and store their own FOV, used by the AIs

git-svn-id: http://svn.net-core.org/repos/t-engine4@420 51575b47-30f0-44d4-a5cc-537603b46e54
parent f76f8563
No related branches found
No related tags found
No related merge requests found
......@@ -60,7 +60,6 @@ function _M:move(x, y, force)
self.x, self.y = x, y
map(x, y, Map.ACTOR, self)
map:checkAllEntities(x, y, "on_move", self, force)
game.level:idleProcessActor(self)
return true
end
......
......@@ -90,11 +90,6 @@ end
--- This is the "main game loop", do something here
function _M:tick()
-- Run the level distancer
if self.level and self.level.distancer_co then
local ok, err = coroutine.resume(self.level.distancer_co)
if not ok and err then error(err) end
end
end
--- Called when a zone leaves a level
......
......@@ -10,13 +10,6 @@ function _M:init(level, map)
self.map = map
self.e_array = {}
self.entities = {}
-- This stores the distance for each actors to each other actors
-- this is computed by either the "distancer" coroutine or manualy when needed if not available
self.e_toprocess = {}
self.e_distances = {}
self.distancer_co = self:createDistancer()
self.entities_list = {}
end
......@@ -52,10 +45,6 @@ end
--- Serialization
function _M:save()
return class.save(self, {
-- cant save a thread
distancer_co = true,
-- dont save the distances table either it will be recomputed on the fly
e_distances = true,
})
end
function _M:loaded()
......@@ -66,58 +55,6 @@ function _M:loaded()
nes[e.uid] = e
end
self.entities = nes
self.e_distances = {}
self.distancer_co = self:createDistancer()
end
--- Creates the distancer coroutine
-- The "distancer" is a coroutine that can be called everytime the game has nothing to do
-- it will compute distance and LOS between all actors and sort them by distance.<br/>
-- This will speed up AI code as it uses unused CPU cycles. The distancer
-- will be called by Game tick() method when there is not much to do (like when waiting
-- for player input)
function _M:createDistancer()
local co = coroutine.create(function()
-- Infinite coroutine
while true do
local e = table.remove(self.e_toprocess)
if e then
self:computeDistances(e)
end
coroutine.yield()
end
end)
return co
end
local dist_sort = function(a, b) return a.dist < b.dist end
--- Compute distances to all other actors
function _M:computeDistances(e)
self.e_distances[e.uid] = {}
core.fov.calc_circle(e.x, e.y, e.sight, function(self, lx, ly)
if self.map:checkEntity(lx, ly, Map.TERRAIN, "block_sight") then return true end
local dst = self.map(lx, ly, Map.ACTOR)
if dst then
table.insert(self.e_distances[e.uid], {uid=dst.uid, dist=core.fov.distance(e.x, e.y, dst.x, dst.y)})
end
end, function()end, self)
table.sort(self.e_distances[e.uid], dist_sort)
end
--- Get distances to all other actors
-- This eithers computes directly if not available or use data from the distancer coroutine
function _M:getDistances(e, force)
if force and not self.e_distances[e.uid] then self:computeDistances(e) end
return self.e_distances[e.uid]
end
--- Insert an actor to process
function _M:idleProcessActor(act)
table.insert(self.e_toprocess, 1, act)
end
--- Setup an entity list for the level, this allwos the Zone to pick objects/actors/...
......
......@@ -13,25 +13,17 @@ newAI("target_simple", function(self)
-- Find closer ennemy and target it
-- Get list of actors ordered by distance
local arr = game.level:getDistances(self)
local arr = self.fov.actors_dist
local act
if not arr or #arr == 0 then
-- No target? Ask the distancer to find one
game.level:idleProcessActor(self)
return
end
for i = 1, #arr do
act = __uids[arr[i].uid]
act = self.fov.actors_dist[i]
-- print("AI looking for target", self.uid, self.name, "::", act.uid, act.name, self.fov.actors[act].sqdist)
-- find the closest ennemy
if act and self:reactionToward(act) < 0 then
if act and self:reactionToward(act) < 0 and not act.dead then
self.ai_target.actor = act
return true
end
end
-- No target ? Ask for more
game.level:idleProcessActor(self)
end)
newAI("target_player", function(self)
......
require "engine.class"
local Map = require "engine.Map"
--- Handles actors field of view
-- When an actor moves it computes a field of view and stores it in self.fov<br/>
-- When an other actor moves it can update the fov of seen actors
module(..., package.seeall, class.make)
--- Initialises stats with default values if needed
function _M:init(t)
self.fov = {actors={}, actors_dist={}}
self.fov_computed = false
self.fov_last_x = -1
self.fov_last_y = -1
self.fov_last_turn = -1
self.fov_last_change = -1
end
--- Computes actor's FOV
-- @param radius the FOV radius, defaults to 20
-- @param block the property to look for FOV blocking, defaults to "block_sight"
-- @param force set to true to force a regeneration even if we did not move
function _M:computeFOV(radius, block, force)
-- If we did not move, do not update
if not force and self.fov_last_x == self.x and self.fov_last_y == self.y and self.fov_computed then return end
radius = radius or 20
block = block or "block_sight"
local fov = {actors={}, actors_dist={}}
setmetatable(fov.actors, {__mode='k'})
setmetatable(fov.actors_dist, {__mode='v'})
local map = game.level.map
core.fov.calc_circle(self.x, self.y, radius, function(_, x, y)
if map:checkAllEntities(x, y, block, self) then return true end
end, function(_, x, y, dx, dy)
-- Note actors
local a = map(x, y, Map.ACTOR)
if a and a ~= self and not a.dead then
local t = {x=x,y=y, dx=dx, dy=dy, sqdist=dx*dx+dy*dy}
fov.actors[a] = t
fov.actors_dist[#fov.actors_dist+1] = a
a:updateFOV(self, t.sqdist)
end
end, self)
-- Sort actors by distance (squared but we do not care)
table.sort(fov.actors_dist, function(a, b) return fov.actors[a].sqdist < fov.actors[b].sqdist end)
for i = 1, #fov.actors_dist do fov.actors_dist[i].i = i end
-- print("Computed FOV for", self.uid, self.name, ":: seen ", #fov.actors_dist, "actors closeby")
self.fov = fov
self.fov_last_x = self.x
self.fov_last_y = self.y
self.fov_last_turn = game.turn
self.fov_last_change = game.turn
self.fov_computed = true
end
--- Update our fov to include the given actor at the given dist
-- @param a the actor to include
-- @param sqdist the squared distance to that actor
function _M:updateFOV(a, sqdist)
-- If we are from this turn no need to update
if self.fov_last_turn == game.turn then return end
local t = {x=a.x, y=a.y, dx=a.x-self.x, dy=a.y-self.y, sqdist=sqdist}
local fov = self.fov
if not fov.actors[a] then
fov.actors_dist[#fov.actors_dist+1] = a
end
fov.actors[a] = t
table.sort(fov.actors_dist, function(a, b) return fov.actors[a].sqdist < fov.actors[b].sqdist end)
-- print("Updated FOV for", self.uid, self.name, ":: seen ", #fov.actors_dist, "actors closeby; from", a, sqdist)
self.fov_last_change = game.turn
end
......@@ -268,7 +268,6 @@ function _M:display()
game.level.map.seens(lx, ly, true)
end
end, self)
game.level.map:redisplay()
end
end
self.level.map:display()
......
require "engine.class"
local ActorAI = require "engine.interface.ActorAI"
local ActorFOV = require "engine.interface.ActorFOV"
require "mod.class.Actor"
module(..., package.seeall, class.inherit(mod.class.Actor, engine.interface.ActorAI))
module(..., package.seeall, class.inherit(mod.class.Actor, engine.interface.ActorAI, engine.interface.ActorFOV))
function _M:init(t, no_default)
mod.class.Actor.init(self, t, no_default)
ActorAI.init(self, t)
ActorFOV.init(self, t)
end
function _M:act()
......@@ -17,6 +19,9 @@ function _M:act()
-- If AI did nothing, use energy anyway
self:doAI()
if not self.energy.used then self:useEnergy() end
-- Compute FOV, if needed
self:computeFOV(self.sight or 20)
end
--- Called by ActorLife interface
......
......@@ -4,6 +4,7 @@ require "engine.interface.PlayerRest"
require "engine.interface.PlayerRun"
require "engine.interface.PlayerHotkeys"
require "engine.interface.PlayerSlide"
local ActorFOV = require "engine.interface.ActorFOV"
local Map = require "engine.Map"
local Dialog = require "engine.Dialog"
local ActorTalents = require "engine.interface.ActorTalents"
......@@ -19,7 +20,8 @@ module(..., package.seeall, class.inherit(
engine.interface.PlayerRest,
engine.interface.PlayerRun,
engine.interface.PlayerHotkeys,
engine.interface.PlayerSlide
engine.interface.PlayerSlide,
engine.interface.ActorFOV
))
function _M:init(t, no_default)
......@@ -38,6 +40,7 @@ function _M:init(t, no_default)
}
mod.class.Actor.init(self, t, no_default)
engine.interface.PlayerHotkeys.init(self, t)
ActorFOV.init(self, t)
self.player = true
self.type = "humanoid"
self.subtype = "player"
......@@ -89,6 +92,9 @@ end
function _M:act()
if not mod.class.Actor.act(self) then return end
-- Compute FOV, if needed
self:computeFOV(self.sight or 20)
-- Clean log flasher
game.flash:empty()
......
......@@ -36,7 +36,9 @@ static void map_seen(void *m, int x, int y, int dx, int dy, int radius, void *sr
lua_rawgeti(L, LUA_REGISTRYINDEX, fov->map_ref);
lua_pushnumber(L, x);
lua_pushnumber(L, y);
lua_call(L, 3, 0);
lua_pushnumber(L, dx);
lua_pushnumber(L, dy);
lua_call(L, 5, 0);
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment