Improve pathfinding
parent
23a0e6bd4e
commit
dc4ef98f27
|
@ -1,7 +1,7 @@
|
|||
defense.director = {}
|
||||
local director = defense.director
|
||||
director.call_interval = 1.0
|
||||
director.intensity_decay = 0.92
|
||||
director.intensity_decay = 0.93
|
||||
director.max_entities = 50
|
||||
director.spawn_list = {
|
||||
{
|
||||
|
@ -20,12 +20,12 @@ director.spawn_list = {
|
|||
description = "Unggoy horde",
|
||||
name = "defense:unggoy",
|
||||
intensity_min = 0.0,
|
||||
intensity_max = 0.0,
|
||||
intensity_max = 0.1,
|
||||
group_min = 21,
|
||||
group_max = 24,
|
||||
probability = 0.8,
|
||||
day_start = 1,
|
||||
spawn_time = 71.0,
|
||||
spawn_time = 31.0,
|
||||
spawn_location = "ground",
|
||||
},
|
||||
{
|
||||
|
@ -85,7 +85,7 @@ function director:on_interval()
|
|||
end
|
||||
|
||||
if self.cooldown_timer <= 0 then
|
||||
if defense:is_dark() and #minetest.luaentities < self.max_entities and not defense.debug then
|
||||
if defense:is_dark() and #minetest.luaentities < self.max_entities then
|
||||
self:spawn_monsters()
|
||||
end
|
||||
|
||||
|
@ -239,8 +239,8 @@ function director:update_intensity()
|
|||
local mob_count = #minetest.luaentities
|
||||
|
||||
local delta =
|
||||
-0.16 * math.min(0.06, average_health - last_average_health)
|
||||
+ 2.0 * math.min(0, 1 / average_health)
|
||||
-0.2 * (average_health - last_average_health)
|
||||
+ 4.0 * math.max(0, 1 / average_health - 0.1)
|
||||
+ 0.006 * (mob_count - last_mob_count)
|
||||
|
||||
last_average_health = average_health
|
||||
|
|
|
@ -24,7 +24,7 @@ end
|
|||
|
||||
function defense:is_dark()
|
||||
local tod = minetest.get_timeofday()
|
||||
return tod < 0.21 or tod > 0.8 or defense.debug
|
||||
return tod < 0.2 or tod > 0.8 or defense.debug
|
||||
end
|
||||
|
||||
dofile2("util.lua")
|
||||
|
|
|
@ -39,15 +39,11 @@ function mobs.default_prototype:on_activate(staticdata)
|
|||
end
|
||||
|
||||
function mobs.default_prototype:on_step(dtime)
|
||||
local destination_distance = 0
|
||||
if self.destination then
|
||||
destination_distance = vector.distance(self.object:getpos(), self.destination)
|
||||
end
|
||||
|
||||
if self.pause_timer <= 0 then
|
||||
if self.destination then
|
||||
self:move(dtime, self.destination)
|
||||
if destination_distance < 1 then
|
||||
if vector.distance(self.object:getpos(), self.destination) < 0.5 then
|
||||
self.destination = nil
|
||||
end
|
||||
else
|
||||
|
@ -61,6 +57,7 @@ function mobs.default_prototype:on_step(dtime)
|
|||
self:set_animation("fall", {"jump", "attack", "move_attack"})
|
||||
end
|
||||
|
||||
-- Die when morning comes
|
||||
if not defense:is_dark() then
|
||||
local damage = self.object:get_hp() * math.random()
|
||||
if damage >= 0.5 then
|
||||
|
@ -68,14 +65,29 @@ function mobs.default_prototype:on_step(dtime)
|
|||
end
|
||||
end
|
||||
|
||||
-- Remove when far enough and may not reach the player at all
|
||||
local nearest = self:find_nearest_player()
|
||||
if self.life_timer <= 0 then
|
||||
if destination_distance > 6 then
|
||||
if nearest.distance > 6 then
|
||||
self.object:remove()
|
||||
end
|
||||
else
|
||||
self.life_timer = self.life_timer - dtime
|
||||
end
|
||||
|
||||
-- Disable collision when far enough
|
||||
if self.collide_with_objects then
|
||||
if nearest.distance > 8 then
|
||||
self.collide_with_objects = false
|
||||
self.object:set_properties({collide_with_objects = self.collide_with_objects})
|
||||
end
|
||||
else
|
||||
if nearest.distance < 3 then
|
||||
self.collide_with_objects = true
|
||||
self.object:set_properties({collide_with_objects = self.collide_with_objects})
|
||||
end
|
||||
end
|
||||
|
||||
self.timer = self.timer + dtime
|
||||
end
|
||||
|
||||
|
@ -134,7 +146,14 @@ function mobs.default_prototype:hunt()
|
|||
end
|
||||
|
||||
if direction then
|
||||
self.destination = vector.add(pos, vector.multiply(direction, 1.25))
|
||||
local sx = self.collisionbox[4] - self.collisionbox[1]
|
||||
local sy = self.collisionbox[5] - self.collisionbox[2]
|
||||
local sz = self.collisionbox[6] - self.collisionbox[3]
|
||||
local r = math.sqrt(sx*sx + sy*sy + sz*sz)/2 + 1
|
||||
local x = pos.x + direction.x * r
|
||||
local y = pos.y + direction.y * r
|
||||
local z = pos.z + direction.z * r
|
||||
self.destination = {x=x, y=y, z=z}
|
||||
else
|
||||
local r = math.max(0, self.attack_range - 2)
|
||||
local dir = vector.direction(nearest.position, self.object:getpos())
|
||||
|
@ -261,7 +280,7 @@ function mobs.move_method:air(dtime, destination)
|
|||
z=math.sin(r_angle)*r_radius
|
||||
})
|
||||
|
||||
local speed = self.move_speed * math.max(0, math.min(1, 0.8 * dist))
|
||||
local speed = self.move_speed * math.max(0, math.min(1, 1.2 * dist))
|
||||
local t
|
||||
local v = self.object:getvelocity()
|
||||
if vector.length(v) < self.move_speed * 1.5 then
|
||||
|
@ -302,7 +321,7 @@ function mobs.move_method:ground(dtime, destination)
|
|||
z=math.sin(r_angle)*r_radius
|
||||
})
|
||||
|
||||
local speed = self.move_speed * math.max(0, math.min(1, 0.8 * dist))
|
||||
local speed = self.move_speed * math.max(0, math.min(1, 1.2 * dist))
|
||||
local t
|
||||
local v = self.object:getvelocity()
|
||||
if self:is_standing() and vector.length(v) < self.move_speed * 4 then
|
||||
|
@ -319,25 +338,32 @@ function mobs.move_method:ground(dtime, destination)
|
|||
v2.y = v.y
|
||||
self.object:setvelocity(v2)
|
||||
|
||||
-- Check for obstacle to jump
|
||||
-- Check for jump
|
||||
local jump = false
|
||||
if dist > 1 then
|
||||
if self.smart_path then
|
||||
local p = self.object:getpos()
|
||||
p.y = p.y + self.collisionbox[2] + 0.5
|
||||
local sx = self.collisionbox[4] - self.collisionbox[1]
|
||||
local sz = self.collisionbox[6] - self.collisionbox[3]
|
||||
local r = math.sqrt(sx*sx + sz*sz)/2 + 0.5
|
||||
local fronts = {
|
||||
{x = dir.x * self.jump_height, y = 0, z = dir.z * self.jump_height},
|
||||
{x = dir.x * r, y = 0, z = dir.z * r},
|
||||
{x = dir.x + dir.z * r, y = 0, z = dir.z + dir.x * r},
|
||||
{x = dir.x - dir.z * r, y = 0, z = dir.z - dir.x * r},
|
||||
}
|
||||
for _,f in ipairs(fronts) do
|
||||
local node = minetest.get_node_or_nil(vector.add(p, f))
|
||||
if not node or minetest.registered_nodes[node.name].walkable then
|
||||
jump = true
|
||||
break
|
||||
if destination.y > p.y + 0.5 then
|
||||
jump = true
|
||||
end
|
||||
else
|
||||
if dist > 1 then
|
||||
local p = self.object:getpos()
|
||||
p.y = p.y + self.collisionbox[2] + 0.5
|
||||
local sx = self.collisionbox[4] - self.collisionbox[1]
|
||||
local sz = self.collisionbox[6] - self.collisionbox[3]
|
||||
local r = math.sqrt(sx*sx + sz*sz)/2 + 0.5
|
||||
local fronts = {
|
||||
{x = dir.x * self.jump_height, y = 0, z = dir.z * self.jump_height},
|
||||
{x = dir.x * r, y = 0, z = dir.z * r},
|
||||
{x = dir.x + dir.z * r, y = 0, z = dir.z + dir.x * r},
|
||||
{x = dir.x - dir.z * r, y = 0, z = dir.z - dir.x * r},
|
||||
}
|
||||
for _,f in ipairs(fronts) do
|
||||
local node = minetest.get_node_or_nil(vector.add(p, f))
|
||||
if not node or minetest.registered_nodes[node.name].walkable then
|
||||
jump = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -380,6 +406,7 @@ function mobs.register_mob(name, def)
|
|||
z = math.ceil(prototype.collisionbox[6] - prototype.collisionbox[3])
|
||||
},
|
||||
collisionbox = prototype.collisionbox,
|
||||
jump_height = math.floor(prototype.jump_height),
|
||||
cost_method = def.pathfinder_cost or defense.pathfinder.cost_method[prototype.movement]
|
||||
})
|
||||
end
|
||||
|
|
|
@ -38,13 +38,13 @@ defense.mobs.register_mob("defense:unggoy", {
|
|||
if math.random() < 0.1 then
|
||||
self.destination = vector.add(
|
||||
self.object:getpos(),
|
||||
{x=math.random(-10,10),y=0,z=math.random(-10,10)}
|
||||
{x=math.random(-4,4),y=0,z=math.random(-4,4)}
|
||||
)
|
||||
elseif math.random() < 0.1 then
|
||||
self.wander = false
|
||||
end
|
||||
else
|
||||
if math.random() < 0.05 then
|
||||
if math.random() < 0.006 then
|
||||
self.wander = true
|
||||
else
|
||||
self:hunt()
|
||||
|
@ -60,6 +60,11 @@ defense.mobs.register_mob("defense:unggoy", {
|
|||
if defense.mobs.default_prototype.is_standing(self) then
|
||||
return true
|
||||
else
|
||||
local vel = self.object:getvelocity()
|
||||
if math.abs(vel.y) > 0.05 then
|
||||
return false
|
||||
end
|
||||
|
||||
local pos = self.object:getpos()
|
||||
pos.y = pos.y - 1
|
||||
for _,o in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
|
||||
|
|
|
@ -1,15 +1,28 @@
|
|||
defense.pathfinder = {}
|
||||
local pathfinder = defense.pathfinder
|
||||
pathfinder.path_max_range = 16
|
||||
pathfinder.fields = {}
|
||||
pathfinder.path_max_range = 32
|
||||
pathfinder.classes = {}
|
||||
|
||||
local visit_queues = {}
|
||||
local chunk_size = 16
|
||||
|
||||
-- State
|
||||
local fields = {}
|
||||
local visit_queues = {}
|
||||
local player_last_update = {}
|
||||
local morning_reset = false
|
||||
|
||||
-- local cid_data = {}
|
||||
-- minetest.after(0, function()
|
||||
-- for name, def in pairs(minetest.registered_nodes) do
|
||||
-- cid_data[minetest.get_content_id(name)] = {
|
||||
-- name = name,
|
||||
-- walkable = def.walkable,
|
||||
-- }
|
||||
-- end
|
||||
-- end)
|
||||
|
||||
function pathfinder:register_class(class, properties)
|
||||
self.fields[class] = self.fields[class] or {}
|
||||
self.classes[class] = properties
|
||||
fields[class] = fields[class] or {}
|
||||
visit_queues[class] = Queue.new()
|
||||
end
|
||||
|
||||
|
@ -24,20 +37,42 @@ end
|
|||
|
||||
-- Returns a vector
|
||||
function pathfinder:get_direction(class, position)
|
||||
local field = self:get_field(class, position)
|
||||
if not field then
|
||||
local total = vector.new(0, 0, 0)
|
||||
local count = 0
|
||||
local time = minetest.get_gametime()
|
||||
|
||||
local cells = {
|
||||
position,
|
||||
{x=position.x + 1, y=position.y, z=position.z},
|
||||
{x=position.x - 1, y=position.y, z=position.z},
|
||||
{x=position.x, y=position.y + 1, z=position.z},
|
||||
{x=position.x, y=position.y - 1, z=position.z},
|
||||
{x=position.x, y=position.y, z=position.z + 1},
|
||||
{x=position.x, y=position.y, z=position.z - 1},
|
||||
}
|
||||
for _,p in ipairs(cells) do
|
||||
local field = self:get_field(class, p)
|
||||
if field then
|
||||
local last_time = player_last_update[field.player] or field.time
|
||||
if last_time + field.distance > time then
|
||||
local direction = ({{x=-1, y=0, z=0},
|
||||
{x=1, y=0, z=0},
|
||||
{x=0, y=-1, z=0},
|
||||
{x=0, y=1, z=0},
|
||||
{x=0, y=0, z=-1},
|
||||
{x=0, y=0, z=1}})
|
||||
[field.direction]
|
||||
total = vector.add(total, direction)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if count > 0 then
|
||||
return vector.normalize(total)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
if field.distance == 0 then
|
||||
return {x=0, y=0, z=0}
|
||||
end
|
||||
return ({{x=-1, y=0, z=0},
|
||||
{x=1, y=0, z=0},
|
||||
{x=0, y=-1, z=0},
|
||||
{x=0, y=1, z=0},
|
||||
{x=0, y=0, z=-1},
|
||||
{x=0, y=0, z=1}})
|
||||
[field.direction]
|
||||
end
|
||||
|
||||
-- Returns a table {time, distance}
|
||||
|
@ -50,7 +85,7 @@ function pathfinder:get_field(class, position)
|
|||
local chunk_key = math.floor(x/chunk_size) ..
|
||||
":" .. math.floor(y/chunk_size) ..
|
||||
":" .. math.floor(z/chunk_size)
|
||||
local chunk = self.fields[class][chunk_key]
|
||||
local chunk = fields[class][chunk_key]
|
||||
if not chunk then
|
||||
return nil
|
||||
end
|
||||
|
@ -62,7 +97,7 @@ function pathfinder:get_field(class, position)
|
|||
return chunk[index]
|
||||
end
|
||||
|
||||
function pathfinder:set_field(class, position, distance, direction, time)
|
||||
function pathfinder:set_field(class, position, player, distance, direction, time)
|
||||
local collisionbox = self.classes[class].collisionbox
|
||||
local x = math.floor(position.x + collisionbox[1])
|
||||
local y = math.floor(position.y + collisionbox[2])
|
||||
|
@ -71,24 +106,33 @@ function pathfinder:set_field(class, position, distance, direction, time)
|
|||
local chunk_key = math.floor(x/chunk_size) ..
|
||||
":" .. math.floor(y/chunk_size) ..
|
||||
":" .. math.floor(z/chunk_size)
|
||||
local chunk = self.fields[class][chunk_key]
|
||||
local chunk = fields[class][chunk_key]
|
||||
if not chunk then
|
||||
chunk = {}
|
||||
self.fields[class][chunk_key] = chunk
|
||||
fields[class][chunk_key] = chunk
|
||||
end
|
||||
|
||||
local cx = x % chunk_size
|
||||
local cy = y % chunk_size
|
||||
local cz = z % chunk_size
|
||||
local index = (cy * chunk_size + cz) * chunk_size + cx
|
||||
chunk[index] = {time=time, direction=direction, distance=distance}
|
||||
chunk[index] = {time=time, direction=direction, distance=distance, player=player}
|
||||
end
|
||||
|
||||
function pathfinder:update(dtime)
|
||||
if not defense:is_dark() then
|
||||
-- reset flow fields
|
||||
-- reset flow fields in the morning
|
||||
if not morning_reset then
|
||||
morning_reset = true
|
||||
player_last_update = {}
|
||||
for c,_ in pairs(self.classes) do
|
||||
fields[c] = {}
|
||||
visit_queues[c] = Queue.new()
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
morning_reset = false
|
||||
|
||||
local neighborhood = {
|
||||
{x=1, y=0, z=0},
|
||||
|
@ -102,15 +146,14 @@ function pathfinder:update(dtime)
|
|||
for c,class in pairs(self.classes) do
|
||||
local vq = visit_queues[c]
|
||||
local size = Queue.size(vq)
|
||||
minetest.debug(size)
|
||||
for i=1,math.min(size,1000 * dtime) do
|
||||
for i=1,math.min(size,20) do
|
||||
local current = Queue.pop(vq)
|
||||
for di,n in ipairs(neighborhood) do
|
||||
local npos = vector.add(current.position, n)
|
||||
npos.x = math.floor(npos.x)
|
||||
npos.y = math.floor(npos.y)
|
||||
npos.z = math.floor(npos.z)
|
||||
local cost = class.cost_method(npos, current.position, class.size)
|
||||
local cost = class.cost_method(class, npos, current.position)
|
||||
if cost then
|
||||
local next_distance = current.distance + cost
|
||||
local neighbor_field = self:get_field(c, npos)
|
||||
|
@ -119,10 +162,11 @@ function pathfinder:update(dtime)
|
|||
and neighbor_field.direction ~= di
|
||||
or neighbor_field.time == current.time
|
||||
and neighbor_field.distance > next_distance then
|
||||
self:set_field(c, npos, next_distance, di, current.time)
|
||||
if next_distance < self.path_max_range then
|
||||
self:set_field(c, npos, current.player, next_distance, di, current.time)
|
||||
if next_distance < self.path_max_range and size < 100 then
|
||||
Queue.push(vq, {
|
||||
position = npos,
|
||||
player = current.player,
|
||||
distance = next_distance,
|
||||
direction = di,
|
||||
time = current.time,
|
||||
|
@ -138,23 +182,27 @@ function pathfinder:update(dtime)
|
|||
local time = minetest.get_gametime()
|
||||
for _,p in ipairs(minetest.get_connected_players()) do
|
||||
local pos = p:getpos()
|
||||
pos.y = pos.y + 1
|
||||
for c,_ in pairs(self.classes) do
|
||||
local field = self:get_field(c, pos)
|
||||
if not field or field.distance > 0 then
|
||||
self:set_field(c, pos, 0, 0, time)
|
||||
Queue.push(visit_queues[c], {position=pos, distance=0, direction=0, time=time})
|
||||
for y=pos.y+0.1,pos.y+2 do
|
||||
local tp = {x=pos.x, y=y, z=pos.z}
|
||||
local field = self:get_field(c, tp)
|
||||
if not field or field.distance > 0 then
|
||||
local name = p:get_player_name()
|
||||
self:set_field(c, pos, name, 0, 4, time)
|
||||
Queue.push(visit_queues[c], {position=tp, player=name, distance=0, direction=0, time=time})
|
||||
player_last_update[name] = time
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
pathfinder.cost_method = {}
|
||||
function pathfinder.cost_method.air(pos, parent, size)
|
||||
function pathfinder.cost_method.air(class, pos, parent)
|
||||
-- Check if solid
|
||||
for y=pos.y,pos.y+size.y-1 do
|
||||
for z=pos.z,pos.z+size.z-1 do
|
||||
for x=pos.x,pos.x+size.x-1 do
|
||||
for y=pos.y,pos.y+class.size.y-1 do
|
||||
for z=pos.z,pos.z+class.size.z-1 do
|
||||
for x=pos.x,pos.x+class.size.x-1 do
|
||||
local node = minetest.get_node_or_nil({x=x, y=y, z=z})
|
||||
if not node then return nil end
|
||||
if minetest.registered_nodes[node.name].walkable then
|
||||
|
@ -165,33 +213,64 @@ function pathfinder.cost_method.air(pos, parent, size)
|
|||
end
|
||||
return 1
|
||||
end
|
||||
function pathfinder.cost_method.ground(pos, parent, size)
|
||||
local on_ground = false
|
||||
for z=pos.z,pos.z+size.z-1 do
|
||||
for x=pos.x,pos.x+size.x-1 do
|
||||
-- Check if solid
|
||||
for y=pos.y,pos.y+size.y-1 do
|
||||
function pathfinder.cost_method.ground(class, pos, parent)
|
||||
-- Check if in solid
|
||||
for z=pos.z,pos.z+class.size.z-1 do
|
||||
for x=pos.x,pos.x+class.size.x-1 do
|
||||
for y=pos.y,pos.y+class.size.y-1 do
|
||||
local node = minetest.get_node_or_nil({x=x, y=y, z=z})
|
||||
if not node then return nil end
|
||||
if minetest.registered_nodes[node.name].walkable then
|
||||
return pathfinder.path_max_range + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not on_ground then
|
||||
-- Check if on top of solid
|
||||
local node = minetest.get_node_or_nil({x=x, y=pos.y-1, z=z})
|
||||
-- Check if this is a fall
|
||||
if parent.y < pos.y then
|
||||
return 2
|
||||
end
|
||||
|
||||
-- Check if on top of solid
|
||||
local ground_distance = 9999
|
||||
for z=pos.z,pos.z+class.size.z-1 do
|
||||
for x=pos.x,pos.x+class.size.x-1 do
|
||||
for y=pos.y-1,pos.y-class.jump_height-1,-1 do
|
||||
local node = minetest.get_node_or_nil({x=x, y=y, z=z})
|
||||
if not node then return nil end
|
||||
if minetest.registered_nodes[node.name].walkable then
|
||||
on_ground = true
|
||||
ground_distance = math.min(ground_distance, pos.y - y)
|
||||
if ground_distance == 1 then
|
||||
return 1
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not on_ground then
|
||||
|
||||
if ground_distance > 1 then
|
||||
if ground_distance <= class.jump_height + 1 then
|
||||
local ledges = {
|
||||
{x=pos.x + class.size.x, y=pos.y - 1, z=pos.z},
|
||||
{x=pos.x - 1, y=pos.y - 1, z=pos.z},
|
||||
{x=pos.x, y=pos.y - 1, z=pos.z + class.size.z},
|
||||
{x=pos.x, y=pos.y - 1, z=pos.z - 1},
|
||||
}
|
||||
for _,l in ipairs(ledges) do
|
||||
local node = minetest.get_node_or_nil(l)
|
||||
if not node then return nil end
|
||||
if minetest.registered_nodes[node.name].walkable then
|
||||
return 1 + (ground_distance - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return pathfinder.path_max_range + 1
|
||||
end
|
||||
return 1 + math.ceil(math.abs(pos.y - parent.y))
|
||||
|
||||
return 1
|
||||
end
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
|
|
Loading…
Reference in New Issue