Path class completed and debugged.

This commit is contained in:
Auke Kok 2016-04-27 23:53:36 -07:00
parent 872b9f258f
commit 7815223fc7
2 changed files with 233 additions and 126 deletions

159
init.lua
View File

@ -54,27 +54,33 @@ obj:factor_is_near_mate = ...
--]] --]]
--
-- misc functions
--
function vector.sort(v1, v2) function vector.sort(v1, v2)
return {x = math.min(v1.x, v2.x), y = math.min(v1.y, v2.y), z = math.min(v1.z, v2.z)}, return {x = math.min(v1.x, v2.x), y = math.min(v1.y, v2.y), z = math.min(v1.z, v2.z)},
{x = math.max(v1.x, v2.x), y = math.max(v1.y, v2.y), z = math.max(v1.z, v2.z)} {x = math.max(v1.x, v2.x), y = math.max(v1.y, v2.y), z = math.max(v1.z, v2.z)}
end end
local function dir_to_yaw(vec) --
if vec.z < 0 then -- includes
return math.pi - math.atan(vec.x / vec.z) --
elseif vec.z > 0 then local modpath = minetest.get_modpath(minetest.get_current_modname())
return -math.atan(vec.x / vec.z)
elseif vec.x < 0 then dofile(modpath .. "/path.lua")
return math.pi
else
return 0 --
end -- globals
end --
local drivers = {} local drivers = {}
local factors = {} local factors = {}
--
-- Animation functions
--
local function animation_select(self, phase, segment) local function animation_select(self, phase, segment)
local state = self.entity_ai_state local state = self.entity_ai_state
@ -160,81 +166,18 @@ drivers.roam = {
return return
elseif state.roam_move then elseif state.roam_move then
-- do path movement -- do path movement
local pos = self.object:getpos() if not self.path then
if vector.distance(pos, state.roam_target) < 1.0 then
-- arrived (close enough!
state.roam_ttl = 0 state.roam_ttl = 0
return return
end end
if state.roam_path then if self.path:distance() < 1.0 then
local curspd = self.object:getvelocity() state.roam_ttl = 0
-- if jumping, let jump finish before making more adjustments return
if curspd.y <= 0.2 and curspd.y >= 0 then end
local i, v = next(state.roam_path, nil) if not self.path:step(dtime) then
if not i then
-- pathing failed -- pathing failed
state.roam_ttl = 0 state.roam_ttl = 0
return
end end
if vector.distance(pos, v) < 0.3 then
-- remove one
--FIXME shouldn't return here
local j = i
local i, v = next(state.roam_path, i)
if not v then
state.roam_path[j] = nil
state.roam_ttl = 0
return
end
end
-- prune path more?
local ii, vv = next(state.roam_path, i)
local iii, vvv = next(state.roam_path, ii)
if vv and vvv and vvv.y == v.y and vector.distance(vv,v) < 2 then
-- prune one
state.roam_path[ii] = nil
end
-- done pruning
minetest.add_particle({
pos = {x = v.x, y = v.y + 0.2, z = v.z},
velocity = vector.new(),
acceleration = vector.new(),
expirationtime = 1,
size = 2,
collisiondetection = false,
vertical = false,
texture = "wool_yellow.png",
playername = nil
})
local vo = {x = v.x, y = v.y - 0.5, z = v.z}
local vec = vector.subtract(vo, pos)
local len = vector.length(vec)
local vdif = vec.y
vec.y = 0
local dir = vector.normalize(vec)
local spd = vector.multiply(dir, 2.0)-- vel
-- don't jump from too far away
if vdif > 0.1 and len < 1.5 then
print("jump")
-- make sure we finish our jump
state.roam_ttl = math.min(3.0, state.roam_ttl)
-- jump
spd = {x = spd.x/10, y = 5, z = spd.z/10}
self.object:setvelocity(spd)
elseif vdif < 0 and len <= 1.1 then
-- drop one path node just to be sure
state.roam_path[i] = nil
-- falling down, just let if fall
else
spd.y = self.object:getvelocity().y
-- don't change yaw when jumping
self.object:setyaw(dir_to_yaw(spd))
self.object:setvelocity(spd)
end
--print(minetest.pos_to_string(spd))
end
end
else else
print("unknown roam state!") print("unknown roam state!")
end end
@ -243,7 +186,6 @@ drivers.roam = {
state.roam_ttl = math.random(3, 9) state.roam_ttl = math.random(3, 9)
-- flip state -- flip state
if state.roam_idle then if state.roam_idle then
print("going roaming")
-- get a target -- get a target
local pos = self.object:getpos() local pos = self.object:getpos()
local minp, maxp = vector.sort({ local minp, maxp = vector.sort({
@ -281,6 +223,7 @@ drivers.roam = {
print("no path found!") print("no path found!")
return return
end end
minetest.add_particle({ minetest.add_particle({
pos = {x = pick.x, y = pick.y - 0.1, z = pick.z}, pos = {x = pick.x, y = pick.y - 0.1, z = pick.z},
velocity = vector.new(), velocity = vector.new(),
@ -292,46 +235,21 @@ drivers.roam = {
texture = "wool_red.png", texture = "wool_red.png",
playername = nil playername = nil
}) })
state.roam_target = pick
-- pathing will fail if we're on a ledge. We can fix this by
-- pathing from the node below instead
local onpos = vector.round({x = pos.x, y = pos.y - 1, z = pos.z})
local on = minetest.get_node(onpos)
if not minetest.registered_nodes[on.name].walkable then
pos.y = onpos.y - 0.5
end
state.roam_path = minetest.find_path(pos, pick, 30, 1.0, .0, "Dijkstra") self.path = Path(self, pick)
if not state.roam_path then if not self.path:find() then
print("Unable to calculate path") print("Unable to calculate path")
else return
for k, v in pairs(state.roam_path) do
minetest.add_particle({
pos = v,
velocity = vector.new(),
acceleration = vector.new(),
expirationtime = 3,
size = 3,
collisiondetection = false,
vertical = false,
texture = "wool_white.png",
playername = nil
})
end
end end
-- done, roaming mode good! -- done, roaming mode good!
animation_select(self, "move") animation_select(self, "move")
state.roam_idle = nil state.roam_idle = nil
state.roam_move = true state.roam_move = true
else else
print("going idle")
animation_select(self, "idle") animation_select(self, "idle")
state.roam_idle = true state.roam_idle = true
state.roam_move = nil state.roam_move = nil
state.roam_target = nil
state.roam_path = nil
-- stop -- stop
self.object:setvelocity(vector.new()) self.object:setvelocity(vector.new())
end end
@ -346,10 +264,12 @@ drivers.roam = {
drivers.startle = { drivers.startle = {
start = function(self) start = function(self)
-- start with moving animation -- startle animation
animation_select(self, "idle") animation_select(self, "idle")
self.object:setvelocity(vector.new())
-- clear factors -- clear factors
local state = self.entity_ai_state local state = self.entity_ai_state
state.attacker = state.factors.got_hit[1]
state.factors.got_hit = nil state.factors.got_hit = nil
state.factors.anim_end = nil state.factors.anim_end = nil
end, end,
@ -425,6 +345,15 @@ local function entity_ai_on_activate(self, staticdata)
self.object:remove() self.object:remove()
return return
end end
-- path class
local state = self.entity_ai_state
if state.path_save then
self.path = Path(self, state.path_save.target)
self.path:set_config(state.path_save.config)
self.path:find()
state.path_save = {}
end
driver = self.entity_ai_state.driver driver = self.entity_ai_state.driver
print("loaded: " .. self.name .. ", driver=" .. driver) print("loaded: " .. self.name .. ", driver=" .. driver)
else else
@ -449,7 +378,7 @@ end
local function entity_ai_on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir) local function entity_ai_on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir)
local state = self.entity_ai_state local state = self.entity_ai_state
state.factors["got_hit"] = {puncher, time_from_last_punch, tool_capabilities, dir} state.factors["got_hit"] = {puncher:get_player_name(), time_from_last_punch, tool_capabilities, dir}
if self.object:get_hp() == 0 then if self.object:get_hp() == 0 then
print("death") print("death")
self.object:set_hp(1) self.object:set_hp(1)
@ -464,7 +393,11 @@ end
local function entity_ai_get_staticdata(self) local function entity_ai_get_staticdata(self)
print("saved: " .. self.name) print("saved: " .. self.name)
return minetest.serialize(self.entity_ai_state) local state = self.entity_ai_state
if self.path then
state.path_save = self.path:save()
end
return minetest.serialize(state)
end end

174
path.lua Normal file
View File

@ -0,0 +1,174 @@
--
-- Path class - manage and execute an entity path
--
-- misc helper function
local function dir_to_yaw(vec)
if vec.z < 0 then
return math.pi - math.atan(vec.x / vec.z)
elseif vec.z > 0 then
return -math.atan(vec.x / vec.z)
elseif vec.x < 0 then
return math.pi
else
return 0
end
end
-- Class definition
Path = {}
Path.__index = Path
setmetatable(Path, {
__call = function(c, ...)
return c.new(...)
end,
})
-- constructor
function Path.new(obj, to)
local self = setmetatable({}, Path)
self.object = obj.object
self.origin = self.object:getpos()
self.target = to
self.config = {
distance = 30,
jump = 1.0,
fall = 3.0,
algorithm = "Dijkstra",
}
self.path = {}
return self
end
-- to help serialization
function Path:save()
return {
target = self.target,
config = self.config
}
end
function Path:find()
-- pathing will fail if we're on a ledge. We can fix this by
-- pathing from the node below instead
local pos = vector.round(self.origin)
local onpos = {x = pos.x, y = pos.y - 1, z = pos.z}
local on = minetest.get_node(onpos)
if not minetest.registered_nodes[on.name].walkable then
pos.y = onpos.y
end
local config = self.config
self.path = minetest.find_path(pos, vector.round(self.target), config.distance, config.jump,
config.fall, config.algorithm)
if self.path ~= nil then
for k, v in pairs(self.path) do
minetest.add_particle({
pos = v,
velocity = vector.new(),
acceleration = vector.new(),
expirationtime = 3,
size = 3,
collisiondetection = false,
vertical = false,
texture = "wool_white.png",
playername = nil
})
end
end
return self.path ~= nil
end
function Path:step(dtime)
local curspd = self.object:getvelocity()
local pos = self.object:getpos()
-- if jumping, let jump finish before making more adjustments
if curspd.y <= 0.2 and curspd.y >= 0 then
local i, v = next(self.path, nil)
if not i then
return false
end
if vector.distance(pos, v) < 0.3 then
-- remove one
--FIXME shouldn't return here
local j = i
local i, v = next(self.path, i)
if not v then
return false
end
end
-- prune path more?
local ii, vv = next(self.path, i)
local iii, vvv = next(self.path, ii)
if vv and vvv and vvv.y == v.y and vector.distance(vv,v) < 2 then
-- prune one
self.path[ii] = nil
end
-- done pruning
minetest.add_particle({
pos = {x = v.x, y = v.y + 0.2, z = v.z},
velocity = vector.new(),
acceleration = vector.new(),
expirationtime = 1,
size = 2,
collisiondetection = false,
vertical = false,
texture = "wool_yellow.png",
playername = nil
})
local vo = {x = v.x, y = v.y - 0.5, z = v.z}
local vec = vector.subtract(vo, pos)
local len = vector.length(vec)
local vdif = vec.y
vec.y = 0
local dir = vector.normalize(vec)
local spd = vector.multiply(dir, 2.0)-- vel
-- don't jump from too far away
if vdif > 0.1 and len < 1.5 then
-- jump
spd = {x = spd.x/10, y = 5, z = spd.z/10}
self.object:setvelocity(spd)
elseif vdif < 0 and len <= 1.1 then
-- drop one path node just to be sure
self.path[i] = nil
-- falling down, just let if fall
else
spd.y = self.object:getvelocity().y
-- don't change yaw when jumping
self.object:setyaw(dir_to_yaw(spd))
self.object:setvelocity(spd)
end
end
return true
end
function Path:distance()
if not self.path then
return 0
end
return vector.distance(self.object:getpos(), self.target)
end
function Path:length()
if not self.path then
return 0
end
return #self.path
end
function Path:get_config()
return self.config
end
function Path:set_config(conf)
self.config = conf
end