-
dg authored
git-svn-id: http://svn.net-core.org/repos/t-engine4@3494 51575b47-30f0-44d4-a5cc-537603b46e54
dg authoredgit-svn-id: http://svn.net-core.org/repos/t-engine4@3494 51575b47-30f0-44d4-a5cc-537603b46e54
ActorAI.lua 4.87 KiB
-- TE4 - T-Engine 4
-- Copyright (C) 2009, 2010, 2011 Nicolas Casalini
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
require "engine.class"
require "engine.Actor"
local Map = require "engine.Map"
--- Handles actors artificial intelligence (or dumbness ... ;)
module(..., package.seeall, class.make)
_M.ai_def = {}
--- Define AI
function _M:newAI(name, fct)
_M.ai_def[name] = fct
end
--- Defines AIs
-- Static!
function _M:loadDefinition(dir)
for i, file in ipairs(fs.list(dir)) do
if file:find("%.lua$") then
local f, err = loadfile(dir.."/"..file)
if not f and err then error(err) end
setfenv(f, setmetatable({
Map = require("engine.Map"),
newAI = function(name, fct) self:newAI(name, fct) end,
}, {__index=_G}))
f()
end
end
end
function _M:init(t)
self.ai_state = self.ai_state or {}
self.ai_target = self.ai_target or {}
self:autoLoadedAI()
end
function _M:autoLoadedAI()
-- Make the table with weak values, so that threat list does not prevent garbage collection
setmetatable(self.ai_target, {__mode='v'})
end
local coords = {
[1] = { 4, 2, 7, 3 },
[2] = { 1, 3, 4, 6 },
[3] = { 2, 6, 1, 9 },
[4] = { 7, 1, 8, 2 },
[5] = {},
[6] = { 9, 3, 8, 2 },
[7] = { 4, 8, 1, 9 },
[8] = { 7, 9, 4, 6 },
[9] = { 8, 6, 7, 3 },
}
function _M:aiCanPass(x, y)
-- Nothing blocks, just go on
if not game.level.map:checkAllEntities(x, y, "block_move", self, true) then return true end
-- If there is an other actor, check hostility, if hostile, we move to attack
local target = game.level.map(x, y, Map.ACTOR)
if target and self:reactionToward(target) < 0 then return true end
-- If there is a target (not hostile) and we can move it, do so
if target and self:attr("move_body") then return true end
return false
end
--- Move one step to the given target if possible
-- This tries the most direct route, if not available it checks sides and always tries to get closer
function _M:moveDirection(x, y)
local l = line.new(self.x, self.y, x, y)
local lx, ly = l()
if lx and ly then
local target = game.level.map(lx, ly, Map.ACTOR)
-- if we are blocked, try some other way
if not self:aiCanPass(lx, ly) then
local dirx = lx - self.x
local diry = ly - self.y
local dir = coord_to_dir[dirx][diry]
local list = coords[dir]
local l = {}
-- Find possibilities
for i = 1, #list do
local dx, dy = self.x + dir_to_coord[list[i]][1], self.y + dir_to_coord[list[i]][2]
if self:aiCanPass(dx, dy) then
l[#l+1] = {dx,dy, (dx-x)^2 + (dy-y)^2}
end
end
-- Move to closest
if #l > 0 then
table.sort(l, function(a,b) return a[3]<b[3] end)
return self:move(l[1][1], l[1][2])
end
else
return self:move(lx, ly)
end
end
end
--- Main entry point for AIs
function _M:doAI()
if not self.ai then return end
if self.dead then return end
-- if self.x < game.player.x - 10 or self.x > game.player.x + 10 or self.y < game.player.y - 10 or self.y > game.player.y + 10 then return end
-- If we have a target but it is dead (it was not yet garbage collected but it'll come)
-- we forget it
if self.ai_target.actor and self.ai_target.actor.dead then self.ai_target.actor = nil end
return self:runAI(self.ai)
end
function _M:runAI(ai)
if not ai or not self.x then return end
return _M.ai_def[ai](self)
end
--- Returns the current target
function _M:getTarget(typ)
local tx, ty = self:aiSeeTargetPos(self.ai_target.actor)
return tx, ty, self.ai_target.actor
end
--- Sets the current target
function _M:setTarget(target)
self.ai_target.actor = target
end
--- Returns the seen coords of the target
-- This will usually return the exact coords, but if the target is only partially visible (or not at all)
-- it will return estimates, to throw the AI a bit off
-- @param target the target we are tracking
-- @return x, y coords to move/cast to
function _M:aiSeeTargetPos(target)
if not target then return self.x, self.y end
local tx, ty = target.x, target.y
local see, chance = self:canSee(target)
-- Directly seeing it, no spread at all
if see then
return tx, ty
-- Ok we can see it, spread coords around, the less chance to see it we had the more we spread
else
chance = math.floor((100 - chance) / 10)
tx = tx + rng.range(0, chance * 2) - chance
ty = ty + rng.range(0, chance * 2) - chance
return tx, ty
end
end