mirror of
https://github.com/ElCeejo/creatura.git
synced 2025-03-21 15:21:24 +00:00
Updated Physics and Vitals, New Pathfinding
This commit is contained in:
parent
69cd063c43
commit
59602c9d36
8 changed files with 1746 additions and 1136 deletions
317
api.lua
317
api.lua
|
@ -6,6 +6,7 @@ creatura.api = {}
|
||||||
|
|
||||||
-- Math --
|
-- Math --
|
||||||
|
|
||||||
|
local abs = math.abs
|
||||||
local floor = math.floor
|
local floor = math.floor
|
||||||
local random = math.random
|
local random = math.random
|
||||||
|
|
||||||
|
@ -19,12 +20,6 @@ local function clamp(val, min_n, max_n)
|
||||||
end
|
end
|
||||||
|
|
||||||
local vec_dist = vector.distance
|
local vec_dist = vector.distance
|
||||||
local vec_equals = vector.equals
|
|
||||||
local vec_add = vector.add
|
|
||||||
|
|
||||||
local function vec_center(v)
|
|
||||||
return {x = floor(v.x + 0.5), y = floor(v.y + 0.5), z = floor(v.z + 0.5)}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function vec_raise(v, n)
|
local function vec_raise(v, n)
|
||||||
if not v then return end
|
if not v then return end
|
||||||
|
@ -190,7 +185,61 @@ function creatura.is_pos_moveable(pos, width, height)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local moveable = creatura.is_pos_moveable
|
local function is_blocked_thin(pos, height)
|
||||||
|
local node
|
||||||
|
local pos2 = {
|
||||||
|
x = floor(pos.x + 0.5),
|
||||||
|
y = floor(pos.y + 0.5) - 1,
|
||||||
|
z = floor(pos.z + 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ = 1, height do
|
||||||
|
pos2.y = pos2.y + 1
|
||||||
|
node = minetest.get_node_or_nil(pos2)
|
||||||
|
|
||||||
|
if not node
|
||||||
|
or get_node_def(node.name).walkable then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function creatura.is_blocked(pos, width, height)
|
||||||
|
if width <= 0.5 then
|
||||||
|
return is_blocked_thin(pos, height)
|
||||||
|
end
|
||||||
|
|
||||||
|
local p1 = {
|
||||||
|
x = pos.x - (width + 0.2),
|
||||||
|
y = pos.y,
|
||||||
|
z = pos.z - (width + 0.2),
|
||||||
|
}
|
||||||
|
local p2 = {
|
||||||
|
x = pos.x + (width + 0.2),
|
||||||
|
y = pos.y + (height + 0.2),
|
||||||
|
z = pos.z + (width + 0.2),
|
||||||
|
}
|
||||||
|
|
||||||
|
local node
|
||||||
|
local pos2 = {}
|
||||||
|
for z = p1.z, p2.z do
|
||||||
|
pos2.z = z
|
||||||
|
for y = p1.y, p2.y do
|
||||||
|
pos2.y = y
|
||||||
|
for x = p1.x, p2.x do
|
||||||
|
pos2.x = x
|
||||||
|
node = minetest.get_node_or_nil(pos2)
|
||||||
|
|
||||||
|
if not node
|
||||||
|
or get_node_def(node.name).walkable then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
function creatura.fast_ray_sight(pos1, pos2, water)
|
function creatura.fast_ray_sight(pos1, pos2, water)
|
||||||
local ray = minetest.raycast(pos1, pos2, false, water or false)
|
local ray = minetest.raycast(pos1, pos2, false, water or false)
|
||||||
|
@ -207,127 +256,6 @@ end
|
||||||
|
|
||||||
local fast_ray_sight = creatura.fast_ray_sight
|
local fast_ray_sight = creatura.fast_ray_sight
|
||||||
|
|
||||||
function creatura.get_next_move(self, pos2)
|
|
||||||
local last_move = self._movement_data.last_move
|
|
||||||
local width = self.width
|
|
||||||
local height = self.height
|
|
||||||
local pos = self.object:get_pos()
|
|
||||||
pos = {
|
|
||||||
x = floor(pos.x),
|
|
||||||
y = pos.y + 0.01,
|
|
||||||
z = floor(pos.z)
|
|
||||||
}
|
|
||||||
pos.y = pos.y + 0.01
|
|
||||||
if last_move
|
|
||||||
and last_move.pos then
|
|
||||||
local last_call = minetest.get_position_from_hash(last_move.pos)
|
|
||||||
last_move = minetest.get_position_from_hash(last_move.move)
|
|
||||||
if vec_equals(vec_center(last_call), vec_center(pos)) then
|
|
||||||
return last_move
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local neighbors = {
|
|
||||||
vec_add(pos, {x = 1, y = 0, z = 0}),
|
|
||||||
vec_add(pos, {x = 1, y = 0, z = 1}),
|
|
||||||
vec_add(pos, {x = 0, y = 0, z = 1}),
|
|
||||||
vec_add(pos, {x = -1, y = 0, z = 1}),
|
|
||||||
vec_add(pos, {x = -1, y = 0, z = 0}),
|
|
||||||
vec_add(pos, {x = -1, y = 0, z = -1}),
|
|
||||||
vec_add(pos, {x = 0, y = 0, z = -1}),
|
|
||||||
vec_add(pos, {x = 1, y = 0, z = -1})
|
|
||||||
}
|
|
||||||
local _next
|
|
||||||
table.sort(neighbors, function(a, b)
|
|
||||||
return vec_dist(a, pos2) < vec_dist(b, pos2)
|
|
||||||
end)
|
|
||||||
for i = 1, #neighbors do
|
|
||||||
local neighbor = neighbors[i]
|
|
||||||
local can_move = fast_ray_sight(pos, neighbor)
|
|
||||||
if vec_equals(neighbor, pos2) then
|
|
||||||
can_move = true
|
|
||||||
end
|
|
||||||
if can_move
|
|
||||||
and not moveable(neighbor, width, height) then
|
|
||||||
can_move = false
|
|
||||||
if moveable(vec_raise(neighbor, 0.5), width, height) then
|
|
||||||
can_move = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if can_move
|
|
||||||
and not self:is_pos_safe(neighbor) then
|
|
||||||
can_move = false
|
|
||||||
end
|
|
||||||
if can_move then
|
|
||||||
_next = vec_raise(neighbor, 0.1)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if _next then
|
|
||||||
self._movement_data.last_move = {
|
|
||||||
pos = minetest.hash_node_position(pos),
|
|
||||||
move = minetest.hash_node_position(_next)
|
|
||||||
}
|
|
||||||
_next = {
|
|
||||||
x = floor(_next.x),
|
|
||||||
y = _next.y,
|
|
||||||
z = floor(_next.z)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
return _next
|
|
||||||
end
|
|
||||||
|
|
||||||
function creatura.get_next_move_3d(self, pos2)
|
|
||||||
local last_move = self._movement_data.last_move
|
|
||||||
local width = self.width
|
|
||||||
local height = self.height
|
|
||||||
local scan_width = width * 2
|
|
||||||
local pos = self.object:get_pos()
|
|
||||||
pos.y = pos.y + 0.5
|
|
||||||
if last_move
|
|
||||||
and last_move.pos then
|
|
||||||
local last_call = minetest.get_position_from_hash(last_move.pos)
|
|
||||||
last_move = minetest.get_position_from_hash(last_move.move)
|
|
||||||
if vec_equals(vec_center(last_call), vec_center(pos)) then
|
|
||||||
return last_move
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local neighbors = {
|
|
||||||
vec_add(pos, {x = scan_width, y = 0, z = 0}),
|
|
||||||
vec_add(pos, {x = scan_width, y = 0, z = scan_width}),
|
|
||||||
vec_add(pos, {x = 0, y = 0, z = scan_width}),
|
|
||||||
vec_add(pos, {x = -scan_width, y = 0, z = scan_width}),
|
|
||||||
vec_add(pos, {x = -scan_width, y = 0, z = 0}),
|
|
||||||
vec_add(pos, {x = -scan_width, y = 0, z = -scan_width}),
|
|
||||||
vec_add(pos, {x = 0, y = 0, z = -scan_width}),
|
|
||||||
vec_add(pos, {x = scan_width, y = 0, z = -scan_width})
|
|
||||||
}
|
|
||||||
local next
|
|
||||||
table.sort(neighbors, function(a, b)
|
|
||||||
return vec_dist(a, pos2) < vec_dist(b, pos2)
|
|
||||||
end)
|
|
||||||
for i = 1, #neighbors do
|
|
||||||
local neighbor = neighbors[i]
|
|
||||||
local can_move = fast_ray_sight({x = pos.x, y = neighbor.y, z = pos.z}, neighbor)
|
|
||||||
if not moveable(vec_raise(neighbor, 0.6), width, height) then
|
|
||||||
can_move = false
|
|
||||||
end
|
|
||||||
if vec_equals(neighbor, pos2) then
|
|
||||||
can_move = true
|
|
||||||
end
|
|
||||||
if can_move then
|
|
||||||
next = neighbor
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if next then
|
|
||||||
self._movement_data.last_move = {
|
|
||||||
pos = minetest.hash_node_position(pos),
|
|
||||||
move = minetest.hash_node_position(next)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
return vec_raise(next, clamp((pos2.y - pos.y) + -0.6, -1, 1))
|
|
||||||
end
|
|
||||||
|
|
||||||
function creatura.sensor_floor(self, range, water)
|
function creatura.sensor_floor(self, range, water)
|
||||||
local pos = self.object:get_pos()
|
local pos = self.object:get_pos()
|
||||||
local pos2 = vec_raise(pos, -range)
|
local pos2 = vec_raise(pos, -range)
|
||||||
|
@ -413,6 +341,139 @@ creatura.get_nearby_entities = creatura.get_nearby_objects
|
||||||
-- Global Mob API --
|
-- Global Mob API --
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
function creatura.default_water_physics(self)
|
||||||
|
local pos = self.stand_pos
|
||||||
|
local stand_node = self.stand_node
|
||||||
|
if not pos or not stand_node then return end
|
||||||
|
local gravity = self._movement_data.gravity or -9.8
|
||||||
|
local submergence = self.liquid_submergence or 0.25
|
||||||
|
local drag = self.liquid_drag or 0.7
|
||||||
|
|
||||||
|
if minetest.get_item_group(stand_node.name, "liquid") > 0 then -- In Liquid
|
||||||
|
local vel = self.object:get_velocity()
|
||||||
|
if not vel then return end
|
||||||
|
|
||||||
|
self.in_liquid = stand_node.name
|
||||||
|
|
||||||
|
if submergence < 1 then
|
||||||
|
local mob_level = pos.y + (self.height * submergence)
|
||||||
|
|
||||||
|
-- Find Water Surface
|
||||||
|
local nodes = minetest.find_nodes_in_area_under_air(
|
||||||
|
{x = pos.x, y = pos.y, z = pos.z},
|
||||||
|
{x = pos.x, y = pos.y + 3, z = pos.z},
|
||||||
|
"group:liquid"
|
||||||
|
) or {}
|
||||||
|
|
||||||
|
local surface_level = (#nodes > 0 and nodes[#nodes].y or pos.y + self.height + 3)
|
||||||
|
surface_level = floor(surface_level + 0.9)
|
||||||
|
|
||||||
|
local height_diff = mob_level - surface_level
|
||||||
|
|
||||||
|
-- Apply Bouyancy
|
||||||
|
if height_diff <= 0 then
|
||||||
|
local displacement = clamp(abs(height_diff) / submergence, 0.5, 1) * self.width
|
||||||
|
|
||||||
|
self.object:set_acceleration({x = 0, y = displacement, z = 0})
|
||||||
|
else
|
||||||
|
self.object:set_acceleration({x = 0, y = gravity, z = 0})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Apply Drag
|
||||||
|
self.object:set_velocity({
|
||||||
|
x = vel.x * (1 - self.dtime * drag),
|
||||||
|
y = vel.y * (1 - self.dtime * drag),
|
||||||
|
z = vel.z * (1 - self.dtime * drag)
|
||||||
|
})
|
||||||
|
else
|
||||||
|
self.in_liquid = nil
|
||||||
|
|
||||||
|
self.object:set_acceleration({x = 0, y = gravity, z = 0})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function creatura.default_vitals(self)
|
||||||
|
local pos = self.stand_pos
|
||||||
|
local node = self.stand_node
|
||||||
|
if not pos or node then return end
|
||||||
|
|
||||||
|
local max_fall = self.max_fall or 3
|
||||||
|
local in_liquid = self.in_liquid
|
||||||
|
local on_ground = self.touching_ground
|
||||||
|
local damage = 0
|
||||||
|
|
||||||
|
-- Fall Damage
|
||||||
|
if max_fall > 0
|
||||||
|
and not in_liquid then
|
||||||
|
local fall_start = self._fall_start or (not on_ground and pos.y)
|
||||||
|
if fall_start
|
||||||
|
and on_ground then
|
||||||
|
damage = floor(fall_start - pos.y)
|
||||||
|
if damage < max_fall then
|
||||||
|
damage = 0
|
||||||
|
else
|
||||||
|
local resist = self.fall_resistance or 0
|
||||||
|
damage = damage - damage * resist
|
||||||
|
end
|
||||||
|
fall_start = nil
|
||||||
|
end
|
||||||
|
self._fall_start = fall_start
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Environment Damage
|
||||||
|
if self:timer(1) then
|
||||||
|
local stand_def = creatura.get_node_def(node.name)
|
||||||
|
local max_breath = self.max_breath or 0
|
||||||
|
|
||||||
|
-- Suffocation
|
||||||
|
if max_breath > 0 then
|
||||||
|
local head_pos = {x = pos.x, y = pos.y + self.height, z = pos.z}
|
||||||
|
local head_def = creatura.get_node_def(head_pos)
|
||||||
|
if head_def.groups
|
||||||
|
and (minetest.get_item_group(head_def.name, "water") > 0
|
||||||
|
or (head_def.walkable
|
||||||
|
and head_def.groups.disable_suffocation ~= 1
|
||||||
|
and head_def.drawtype == "normal")) then
|
||||||
|
local breath = self._breath
|
||||||
|
if breath <= 0 then
|
||||||
|
damage = damage + 1
|
||||||
|
else
|
||||||
|
self._breath = breath - 1
|
||||||
|
self:memorize("_breath", breath)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Burning
|
||||||
|
local fire_resist = self.fire_resistance or 0
|
||||||
|
if fire_resist < 1
|
||||||
|
and minetest.get_item_group(stand_def.name, "igniter") > 0
|
||||||
|
and stand_def.damage_per_second then
|
||||||
|
damage = (damage or 0) + stand_def.damage_per_second * fire_resist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Apply Damage
|
||||||
|
if damage > 0 then
|
||||||
|
self:hurt(damage)
|
||||||
|
self:indicate_damage()
|
||||||
|
if random(4) < 2 then
|
||||||
|
self:play_sound("hurt")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Entity Cramming
|
||||||
|
if self:timer(5) then
|
||||||
|
local objects = minetest.get_objects_inside_radius(pos, 0.2)
|
||||||
|
if #objects > 10 then
|
||||||
|
self:indicate_damage()
|
||||||
|
self.hp = self:memorize("hp", -1)
|
||||||
|
self:death_func()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function creatura.drop_items(self)
|
function creatura.drop_items(self)
|
||||||
if not self.drops then return end
|
if not self.drops then return end
|
||||||
local pos = self.object:get_pos()
|
local pos = self.object:get_pos()
|
||||||
|
|
25
doc.txt
25
doc.txt
|
@ -213,4 +213,27 @@ Global Mob API
|
||||||
* `creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage)`
|
* `creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage)`
|
||||||
* Deals damage
|
* Deals damage
|
||||||
* Applies knockback
|
* Applies knockback
|
||||||
* Visualy and audibly indicates damage
|
* Visualy and audibly indicates damage
|
||||||
|
|
||||||
|
Pathfinding
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Creatura's pathfinder uses the A* algorithm for speed, as well as Theta* for decent performance and more natural looking paths.
|
||||||
|
|
||||||
|
Both pathfinders will carry out pathfinding over multiple server steps to reduce lag spikes which does result in the path not
|
||||||
|
being returned immediately, so your code will have to account for this.
|
||||||
|
|
||||||
|
The maximum amount of time the pathfinder can spend per-step (in microseconds) can be adjusted in settings.
|
||||||
|
|
||||||
|
|
||||||
|
* `creatura.pathfinder.find_path(self, pos1, pos2, get_neighbors)`
|
||||||
|
* Finds a path from `pos1` to `pos2`
|
||||||
|
* `get_neighbors` is a function used to find valid neighbors
|
||||||
|
* `creatura.pathfinder.get_neighbors_fly` and `creatura.pathfinder.get_neighbors_swim` are bundled by default
|
||||||
|
|
||||||
|
|
||||||
|
* `creatura.pathfinder.find_path_theta(self, pos1, pos2, get_neighbors)`
|
||||||
|
* Finds a path from `pos1` to `pos2`
|
||||||
|
* Returns a path with arbitrary angles for natural looking paths at the expense of performance
|
||||||
|
* `get_neighbors` is a function used to find valid neighbors
|
||||||
|
* `creatura.pathfinder.get_neighbors_fly` and `creatura.pathfinder.get_neighbors_swim` are bundled by default
|
3
init.lua
3
init.lua
|
@ -3,7 +3,8 @@ creatura = {}
|
||||||
local path = minetest.get_modpath("creatura")
|
local path = minetest.get_modpath("creatura")
|
||||||
|
|
||||||
dofile(path.."/api.lua")
|
dofile(path.."/api.lua")
|
||||||
dofile(path.."/pathfinder.lua")
|
dofile(path.."/pathfinding.lua")
|
||||||
|
dofile(path.."/pathfinder_deprecated.lua")
|
||||||
dofile(path.."/methods.lua")
|
dofile(path.."/methods.lua")
|
||||||
|
|
||||||
-- Optional Files --
|
-- Optional Files --
|
||||||
|
|
88
methods.lua
88
methods.lua
|
@ -468,49 +468,41 @@ end
|
||||||
return path
|
return path
|
||||||
end]]
|
end]]
|
||||||
|
|
||||||
creatura.register_movement_method("creatura:theta_pathfind", function(self)
|
creatura.register_movement_method("creatura:pathfind_theta", function(self)
|
||||||
local path = {}
|
local path = {}
|
||||||
local box = clamp(self.width, 0.5, 1.5)
|
local steer_to
|
||||||
|
local steer_int = 0
|
||||||
|
local arrival_threshold = clamp(self.width, 0.5, 1)
|
||||||
|
|
||||||
|
self:set_gravity(-9.8)
|
||||||
local function func(_self, goal, speed_factor)
|
local function func(_self, goal, speed_factor)
|
||||||
local pos = _self.object:get_pos()
|
local pos = _self.object:get_pos()
|
||||||
if not pos then return end
|
if not pos or not goal then return end
|
||||||
pos.y = pos.y + 0.5
|
|
||||||
-- Return true when goal is reached
|
if vec_dist(pos, goal) < arrival_threshold then
|
||||||
if vec_dist(pos, goal) < box * 1.33 then
|
|
||||||
_self:halt()
|
_self:halt()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
self:set_gravity(-9.8)
|
|
||||||
-- Get movement direction
|
-- Calculate Movement
|
||||||
local steer_to = get_avoidance_dir(_self, goal)
|
|
||||||
local goal_dir = vec_dir(pos, goal)
|
|
||||||
if steer_to then
|
|
||||||
goal_dir = steer_to
|
|
||||||
if #path < 1 then
|
|
||||||
path = creatura.find_theta_path(_self, pos, goal, _self.width, _self.height, 300) or {}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #path > 0 then
|
|
||||||
goal_dir = vec_dir(pos, path[2] or path[1])
|
|
||||||
if vec_dist(pos, path[1]) < box then
|
|
||||||
table.remove(path, 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local yaw = _self.object:get_yaw()
|
|
||||||
local goal_yaw = dir2yaw(goal_dir)
|
|
||||||
local speed = abs(_self.speed or 2) * speed_factor or 0.5
|
|
||||||
local turn_rate = abs(_self.turn_rate or 5)
|
local turn_rate = abs(_self.turn_rate or 5)
|
||||||
-- Movement
|
local speed = abs(_self.speed or 2) * speed_factor or 0.5
|
||||||
local yaw_diff = abs(diff(yaw, goal_yaw))
|
local path_dir = #path > 0 and vec_dir(pos, path[2] or path[1])
|
||||||
if yaw_diff < pi * 0.25
|
|
||||||
or steer_to then
|
steer_int = (steer_int > 0 and steer_int - _self.dtime) or 1 / math.max(speed, 1)
|
||||||
_self:set_forward_velocity(speed)
|
steer_to = path_dir or (steer_int <= 0 and creatura.calc_steering(_self, goal)) or steer_to
|
||||||
else
|
|
||||||
_self:set_forward_velocity(speed * 0.33)
|
path = (#path > 0 and path) or (creatura.pathfinder.find_path_theta(_self, pos, goal) or {})
|
||||||
end
|
|
||||||
if yaw_diff > 0.1 then
|
if path_dir
|
||||||
_self:turn_to(goal_yaw, turn_rate)
|
and ((path[2] and vec_dist(pos, path[2]) < arrival_threshold)
|
||||||
|
or vec_dist(pos, path[1]) < arrival_threshold) then
|
||||||
|
table.remove(path, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Apply Movement
|
||||||
|
_self:turn_to(dir2yaw(steer_to or vec_dir(pos, goal)), turn_rate)
|
||||||
|
_self:set_forward_velocity(speed)
|
||||||
end
|
end
|
||||||
return func
|
return func
|
||||||
end)
|
end)
|
||||||
|
@ -519,25 +511,34 @@ creatura.register_movement_method("creatura:pathfind", function(self)
|
||||||
local path = {}
|
local path = {}
|
||||||
local steer_to
|
local steer_to
|
||||||
local steer_int = 0
|
local steer_int = 0
|
||||||
|
local arrival_threshold = clamp(self.width, 0.5, 1)
|
||||||
|
|
||||||
self:set_gravity(-9.8)
|
self:set_gravity(-9.8)
|
||||||
local function func(_self, goal, speed_factor)
|
local function func(_self, goal, speed_factor)
|
||||||
local pos = _self.object:get_pos()
|
local pos = _self.object:get_pos()
|
||||||
if not pos or not goal then return end
|
if not pos or not goal then return end
|
||||||
if vec_dist(pos, goal) < clamp(self.width, 0.5, 1) then
|
|
||||||
|
if vec_dist(pos, goal) < arrival_threshold then
|
||||||
_self:halt()
|
_self:halt()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Calculate Movement
|
-- Calculate Movement
|
||||||
local turn_rate = abs(_self.turn_rate or 5)
|
local turn_rate = abs(_self.turn_rate or 5)
|
||||||
local speed = abs(_self.speed or 2) * speed_factor or 0.5
|
local speed = abs(_self.speed or 2) * speed_factor or 0.5
|
||||||
steer_int = (not steer_to and steer_int > 0 and steer_int - _self.dtime) or 1 / math.max(speed, 1)
|
local path_dir = #path > 0 and vec_dir(pos, path[2] or path[1])
|
||||||
steer_to = (steer_int <= 0 and creatura.calc_steering(_self, goal)) or steer_to
|
|
||||||
if steer_to then
|
steer_int = (steer_int > 0 and steer_int - _self.dtime) or 1 / math.max(speed, 1)
|
||||||
path = creatura.find_lvm_path(_self, pos, goal, _self.width, _self.height, 400) or {}
|
steer_to = path_dir or (steer_int <= 0 and creatura.calc_steering(_self, goal)) or steer_to
|
||||||
if #path > 0 then
|
|
||||||
steer_to = vec_dir(pos, path[2] or path[1])
|
path = (#path > 0 and path) or (creatura.pathfinder.find_path(_self, pos, goal) or {})
|
||||||
end
|
|
||||||
|
if path_dir
|
||||||
|
and ((path[2] and vec_dist(pos, path[2]) < arrival_threshold + 0.5)
|
||||||
|
or vec_dist(pos, path[1]) < arrival_threshold) then
|
||||||
|
table.remove(path, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply Movement
|
-- Apply Movement
|
||||||
_self:turn_to(dir2yaw(steer_to or vec_dir(pos, goal)), turn_rate)
|
_self:turn_to(dir2yaw(steer_to or vec_dir(pos, goal)), turn_rate)
|
||||||
_self:set_forward_velocity(speed)
|
_self:set_forward_velocity(speed)
|
||||||
|
@ -545,6 +546,7 @@ creatura.register_movement_method("creatura:pathfind", function(self)
|
||||||
return func
|
return func
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
||||||
-- Steering
|
-- Steering
|
||||||
|
|
||||||
creatura.register_movement_method("creatura:steer_small", function(self)
|
creatura.register_movement_method("creatura:steer_small", function(self)
|
||||||
|
|
181
mob_meta.lua
181
mob_meta.lua
|
@ -87,8 +87,8 @@ local mob = {
|
||||||
},
|
},
|
||||||
follow = {},
|
follow = {},
|
||||||
fancy_collide = false,
|
fancy_collide = false,
|
||||||
bouyancy_multiplier = 1,
|
liquid_submergence = 0.25,
|
||||||
hydrodynamics_multiplier = 1
|
liquid_drag = 1
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -505,12 +505,11 @@ function mob:set_mesh(id)
|
||||||
self.object:set_properties({
|
self.object:set_properties({
|
||||||
mesh = meshes[mesh_no]
|
mesh = meshes[mesh_no]
|
||||||
})
|
})
|
||||||
self:memorize("mesh_no", self.mesh_no)
|
self.mesh_no = mesh_no
|
||||||
if self.mesh_textures then
|
if self.mesh_textures then
|
||||||
self.textures = self.mesh_textures[mesh_no]
|
self.textures = self.mesh_textures[mesh_no]
|
||||||
self.texture_no = random(#self.textures)
|
self.texture_no = random(#self.textures)
|
||||||
self:set_texture(self.texture_no, self.textures)
|
self:set_texture(self.texture_no, self.textures)
|
||||||
self:memorize("texture_no", self.texture_no)
|
|
||||||
end
|
end
|
||||||
return meshes[mesh_no]
|
return meshes[mesh_no]
|
||||||
end
|
end
|
||||||
|
@ -818,22 +817,20 @@ function mob:activate(staticdata, dtime)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Initialize Stats and Visuals
|
-- Initialize Stats and Visuals
|
||||||
|
if self.meshes
|
||||||
|
and #self.meshes > 0 then
|
||||||
|
if not self.mesh_no
|
||||||
|
or not self.meshes[self.mesh_no] then
|
||||||
|
self.mesh_no = random(#self.meshes)
|
||||||
|
end
|
||||||
|
self:set_mesh(self.mesh_no)
|
||||||
|
end
|
||||||
|
|
||||||
if not self.textures then
|
if not self.textures then
|
||||||
local textures = self:get_props().textures
|
local textures = self:get_props().textures
|
||||||
if textures then self.textures = textures end
|
if textures then self.textures = textures end
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.meshes then
|
|
||||||
local mesh_no = self.mesh_no or random(#self.meshes)
|
|
||||||
if self.mesh_textures then
|
|
||||||
self.textures = self.mesh_textures[mesh_no]
|
|
||||||
end
|
|
||||||
self.mesh_no = mesh_no
|
|
||||||
self.object:set_properties({
|
|
||||||
mesh = self.meshes[mesh_no]
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
if not self.perm_data then
|
if not self.perm_data then
|
||||||
if self.memory then
|
if self.memory then
|
||||||
self.perm_data = self.memory
|
self.perm_data = self.memory
|
||||||
|
@ -857,11 +854,14 @@ function mob:activate(staticdata, dtime)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
self._breath = self:recall("_breath") or (self.max_breath or 30)
|
self._breath = self:recall("_breath") or (self.max_breath or 30)
|
||||||
--self._border = index_box_border(self)
|
--self._border = index_box_border(self)
|
||||||
|
|
||||||
if self.textures
|
if self.textures
|
||||||
and self.texture_no then
|
and self.texture_no then
|
||||||
|
if not self.textures[self.texture_no] then
|
||||||
|
self.texture_no = random(#self.textures)
|
||||||
|
end
|
||||||
self:set_texture(self.texture_no, self.textures)
|
self:set_texture(self.texture_no, self.textures)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -960,11 +960,11 @@ function mob:on_step(dtime, moveresult)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function mob:on_deactivate()
|
function mob:on_deactivate(removal)
|
||||||
self._task = {}
|
self._task = {}
|
||||||
self._action = {}
|
self._action = {}
|
||||||
if self.deactivate_func then
|
if self.deactivate_func then
|
||||||
self:deactivate_func(self)
|
self:deactivate_func(removal)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1005,65 +1005,14 @@ local function collision_detection(self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function water_physics(self, pos, node)
|
local mob_friction = 7
|
||||||
-- Props
|
|
||||||
local gravity = self._movement_data.gravity
|
|
||||||
local height = self.height
|
|
||||||
-- Vectors
|
|
||||||
pos.y = pos.y + 0.01
|
|
||||||
if minetest.get_item_group(node.name, "liquid") < 1 then
|
|
||||||
self.object:set_acceleration({
|
|
||||||
x = 0,
|
|
||||||
y = gravity,
|
|
||||||
z = 0
|
|
||||||
})
|
|
||||||
if self.in_liquid then
|
|
||||||
self.in_liquid = false
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
self.in_liquid = node.name
|
|
||||||
self.object:set_acceleration({
|
|
||||||
x = 0,
|
|
||||||
y = gravity * 0.5,
|
|
||||||
z = 0
|
|
||||||
})
|
|
||||||
local center = {
|
|
||||||
x = pos.x,
|
|
||||||
y = pos.y + height * 0.5,
|
|
||||||
z = pos.z
|
|
||||||
}
|
|
||||||
if minetest.get_item_group(minetest.get_node(center).name, "liquid") < 1 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
-- Calculate Physics
|
|
||||||
local vel = self.object:get_velocity()
|
|
||||||
local bouyancy_x = self.bouyancy_multiplier or 1
|
|
||||||
local bouyancy = (abs(gravity * 0.5) / height) * bouyancy_x
|
|
||||||
if bouyancy > 0 then
|
|
||||||
if bouyancy > 4.9 then bouyancy = 4.9 end
|
|
||||||
self.object:set_acceleration({
|
|
||||||
x = 0,
|
|
||||||
y = 0,
|
|
||||||
z = 0
|
|
||||||
})
|
|
||||||
vel.y = vel.y + (bouyancy - vel.y) * (self.dtime * 0.5)
|
|
||||||
end
|
|
||||||
local hydrodynamics_x = self.hydrodynamics_multiplier or 0.7
|
|
||||||
vel.x = vel.x * hydrodynamics_x
|
|
||||||
vel.y = vel.y * ((bouyancy == 0 and hydrodynamics_x) or 1)
|
|
||||||
vel.z = vel.z * hydrodynamics_x
|
|
||||||
-- Apply Physics
|
|
||||||
self.object:set_velocity(vel)
|
|
||||||
end
|
|
||||||
|
|
||||||
function mob:_physics()
|
function mob:_physics()
|
||||||
local pos = self.stand_pos
|
-- Physics
|
||||||
local node = self.stand_node
|
creatura.default_water_physics(self)
|
||||||
if not pos or not node then return end
|
|
||||||
water_physics(self, pos, node)
|
|
||||||
-- Object collision
|
|
||||||
collision_detection(self)
|
collision_detection(self)
|
||||||
|
|
||||||
|
-- Cache Environment Info
|
||||||
local in_liquid = self.in_liquid
|
local in_liquid = self.in_liquid
|
||||||
local on_ground = self.touching_ground
|
local on_ground = self.touching_ground
|
||||||
if not in_liquid
|
if not in_liquid
|
||||||
|
@ -1077,9 +1026,7 @@ function mob:_physics()
|
||||||
--and not move_data.func
|
--and not move_data.func
|
||||||
and move_data.gravity ~= 0 then
|
and move_data.gravity ~= 0 then
|
||||||
local vel = self.object:get_velocity()
|
local vel = self.object:get_velocity()
|
||||||
local friction = self.dtime * 10
|
local friction = math.min(self.dtime * mob_friction, 0.5)
|
||||||
if friction > 0.5 then friction = 0.5 end
|
|
||||||
if not on_ground then friction = 0.25 end
|
|
||||||
local nvel = {x = vel.x * (1 - friction), y = vel.y, z = vel.z * (1 - friction)}
|
local nvel = {x = vel.x * (1 - friction), y = vel.y, z = vel.z * (1 - friction)}
|
||||||
self.object:set_velocity(nvel)
|
self.object:set_velocity(nvel)
|
||||||
end
|
end
|
||||||
|
@ -1124,6 +1071,8 @@ function mob:_execute_utilities()
|
||||||
step_delay = nil,
|
step_delay = nil,
|
||||||
score = 0
|
score = 0
|
||||||
}
|
}
|
||||||
|
end
|
||||||
|
if not self._util_cooldown then
|
||||||
self._util_cooldown = {}
|
self._util_cooldown = {}
|
||||||
end
|
end
|
||||||
local loop_data = {
|
local loop_data = {
|
||||||
|
@ -1182,6 +1131,7 @@ function mob:_execute_utilities()
|
||||||
self._util_cooldown[i] = cooldown
|
self._util_cooldown[i] = cooldown
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if loop_data.utility
|
if loop_data.utility
|
||||||
and loop_data.args then
|
and loop_data.args then
|
||||||
if not self._utility_data
|
if not self._utility_data
|
||||||
|
@ -1197,7 +1147,8 @@ function mob:_execute_utilities()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if self._utility_data.utility then
|
|
||||||
|
if self._utility_data.utility then -- If a utility is currently selected
|
||||||
local util_data = self._utility_data
|
local util_data = self._utility_data
|
||||||
if not util_data.func then
|
if not util_data.func then
|
||||||
self:initiate_utility(util_data.utility, unpack(util_data.args))
|
self:initiate_utility(util_data.utility, unpack(util_data.args))
|
||||||
|
@ -1255,78 +1206,6 @@ end
|
||||||
|
|
||||||
-- Vitals
|
-- Vitals
|
||||||
|
|
||||||
function mob:_vitals()
|
|
||||||
local pos = self.stand_pos
|
|
||||||
local node = self.stand_node
|
|
||||||
if not pos or not node then return end
|
|
||||||
local max_fall = self.max_fall or 3
|
|
||||||
local in_liquid = self.in_liquid
|
|
||||||
local on_ground = self.touching_ground
|
|
||||||
local damage = 0
|
|
||||||
if max_fall > 0
|
|
||||||
and not in_liquid then
|
|
||||||
local fall_start = self._fall_start or (not on_ground and pos.y)
|
|
||||||
if fall_start then
|
|
||||||
if on_ground then
|
|
||||||
damage = fall_start - pos.y
|
|
||||||
if damage < max_fall then
|
|
||||||
damage = 0
|
|
||||||
else
|
|
||||||
local resist = self.fall_resistance or 0
|
|
||||||
damage = damage - damage * resist
|
|
||||||
end
|
|
||||||
fall_start = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self._fall_start = fall_start
|
|
||||||
end
|
|
||||||
if self:timer(1) then
|
|
||||||
local stand_def = creatura.get_node_def(node.name)
|
|
||||||
local max_breath = self.max_breath
|
|
||||||
if not max_breath
|
|
||||||
or max_breath > 0 then
|
|
||||||
local breath = self._breath or max_breath
|
|
||||||
local head_pos = vec_raise(pos, self.height - 0.01)
|
|
||||||
local head_def = creatura.get_node_def(head_pos)
|
|
||||||
if minetest.get_item_group(head_def.name, "liquid") > 0
|
|
||||||
or (head_def.walkable
|
|
||||||
and head_def.drawtype == "normal") then
|
|
||||||
if breath <= 0 then
|
|
||||||
damage = (damage or 0) + 1
|
|
||||||
else
|
|
||||||
breath = breath - 1
|
|
||||||
end
|
|
||||||
else
|
|
||||||
breath = (breath < max_breath and breath + 1) or max_breath
|
|
||||||
end
|
|
||||||
self._breath = self:memorize("_breath", breath)
|
|
||||||
end
|
|
||||||
if (not self.fire_resistance
|
|
||||||
or self.fire_resistance < 1)
|
|
||||||
and minetest.get_item_group(stand_def.name, "igniter") > 0
|
|
||||||
and stand_def.damage_per_second then
|
|
||||||
local resist = self.fire_resistance or 0.5
|
|
||||||
damage = (damage or 0) + stand_def.damage_per_second * resist
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if damage > 0 then
|
|
||||||
self:hurt(damage)
|
|
||||||
self:indicate_damage()
|
|
||||||
if random(4) < 2 then
|
|
||||||
self:play_sound("hurt")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Entity Cramming
|
|
||||||
if self:timer(5) then
|
|
||||||
local objects = minetest.get_objects_inside_radius(pos, 0.2)
|
|
||||||
if #objects > 10 then
|
|
||||||
self:indicate_damage()
|
|
||||||
self.hp = self:memorize("hp", -1)
|
|
||||||
self:death_func()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function creatura.register_mob(name, def)
|
function creatura.register_mob(name, def)
|
||||||
local box_width = def.hitbox and def.hitbox.width or 0.5
|
local box_width = def.hitbox and def.hitbox.width or 0.5
|
||||||
local box_height = def.hitbox and def.hitbox.height or 1
|
local box_height = def.hitbox and def.hitbox.height or 1
|
||||||
|
@ -1354,6 +1233,8 @@ function creatura.register_mob(name, def)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def._vitals = def._vitals or creatura.default_vitals
|
||||||
|
|
||||||
def.on_activate = function(self, staticdata, dtime)
|
def.on_activate = function(self, staticdata, dtime)
|
||||||
return self:activate(staticdata, dtime)
|
return self:activate(staticdata, dtime)
|
||||||
end
|
end
|
||||||
|
|
File diff suppressed because it is too large
Load diff
620
pathfinding.lua
Normal file
620
pathfinding.lua
Normal file
|
@ -0,0 +1,620 @@
|
||||||
|
-----------------
|
||||||
|
-- Pathfinding --
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
local a_star_alloted_time = tonumber(minetest.settings:get("creatura_a_star_alloted_time")) or 500
|
||||||
|
local theta_star_alloted_time = tonumber(minetest.settings:get("creatura_theta_star_alloted_time")) or 700
|
||||||
|
|
||||||
|
creatura.pathfinder = {}
|
||||||
|
|
||||||
|
local max_open = 300
|
||||||
|
|
||||||
|
-- Math
|
||||||
|
|
||||||
|
local floor = math.floor
|
||||||
|
local abs = math.abs
|
||||||
|
|
||||||
|
local vec_add, vec_dist, vec_new, vec_round = vector.add, vector.distance, vector.new, vector.round
|
||||||
|
|
||||||
|
local function vec_raise(v, n)
|
||||||
|
return {x = v.x, y = v.y + n, z = v.z}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Heuristic
|
||||||
|
|
||||||
|
local function get_distance(start_pos, end_pos)
|
||||||
|
local distX = abs(start_pos.x - end_pos.x)
|
||||||
|
local distZ = abs(start_pos.z - end_pos.z)
|
||||||
|
|
||||||
|
if distX > distZ then
|
||||||
|
return 14 * distZ + 10 * (distX - distZ)
|
||||||
|
else
|
||||||
|
return 14 * distX + 10 * (distZ - distX)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_distance_to_neighbor(start_pos, end_pos)
|
||||||
|
local distX = abs(start_pos.x - end_pos.x)
|
||||||
|
local distY = abs(start_pos.y - end_pos.y)
|
||||||
|
local distZ = abs(start_pos.z - end_pos.z)
|
||||||
|
|
||||||
|
if distX > distZ then
|
||||||
|
return (14 * distZ + 10 * (distX - distZ)) * (distY + 1)
|
||||||
|
else
|
||||||
|
return (14 * distX + 10 * (distZ - distX)) * (distY + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Blocked Movement Checks
|
||||||
|
|
||||||
|
local is_blocked = creatura.is_blocked
|
||||||
|
|
||||||
|
local function get_line_of_sight(a, b)
|
||||||
|
local steps = floor(vec_dist(a, b))
|
||||||
|
local line = {}
|
||||||
|
|
||||||
|
for i = 0, steps do
|
||||||
|
local pos
|
||||||
|
|
||||||
|
if steps > 0 then
|
||||||
|
pos = {
|
||||||
|
x = a.x + (b.x - a.x) * (i / steps),
|
||||||
|
y = a.y + (b.y - a.y) * (i / steps),
|
||||||
|
z = a.z + (b.z - a.z) * (i / steps)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
pos = a
|
||||||
|
end
|
||||||
|
table.insert(line, pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
if #line < 1 then
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
for i = 1, #line do
|
||||||
|
local node = minetest.get_node(line[i])
|
||||||
|
if creatura.get_node_def(node.name).walkable then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_on_ground(pos)
|
||||||
|
local ground = {
|
||||||
|
x = pos.x,
|
||||||
|
y = pos.y - 1,
|
||||||
|
z = pos.z
|
||||||
|
}
|
||||||
|
if creatura.get_node_def(ground).walkable then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Neighbor Check Grids
|
||||||
|
|
||||||
|
local neighbor_grid = {
|
||||||
|
{x = 1, y = 0, z = 0},
|
||||||
|
{x = 1, y = 0, z = 1},
|
||||||
|
{x = 0, y = 0, z = 1},
|
||||||
|
{x = -1, y = 0, z = 1},
|
||||||
|
{x = -1, y = 0, z = 0},
|
||||||
|
{x = -1, y = 0, z = -1},
|
||||||
|
{x = 0, y = 0, z = -1},
|
||||||
|
{x = 1, y = 0, z = -1}
|
||||||
|
}
|
||||||
|
|
||||||
|
local neighbor_grid_climb = {
|
||||||
|
{x = 1, y = 0, z = 0},
|
||||||
|
{x = 1, y = 0, z = 1},
|
||||||
|
{x = 0, y = 0, z = 1},
|
||||||
|
{x = -1, y = 0, z = 1},
|
||||||
|
{x = -1, y = 0, z = 0},
|
||||||
|
{x = -1, y = 0, z = -1},
|
||||||
|
{x = 0, y = 0, z = -1},
|
||||||
|
{x = 1, y = 0, z = -1},
|
||||||
|
|
||||||
|
{x = 0, y = 1, z = 0},
|
||||||
|
{x = 0, y = -1, z = 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
local neighbor_grid_3d = {
|
||||||
|
-- Central
|
||||||
|
{x = 1, y = 0, z = 0},
|
||||||
|
{x = 0, y = 0, z = 1},
|
||||||
|
{x = -1, y = 0, z = 0},
|
||||||
|
{x = 0, y = 0, z = -1},
|
||||||
|
-- Directly Up or Down
|
||||||
|
{x = 0, y = 1, z = 0},
|
||||||
|
{x = 0, y = -1, z = 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Get Neighbors
|
||||||
|
|
||||||
|
local function get_neighbors(pos, width, height, open, closed, parent)
|
||||||
|
local result = {}
|
||||||
|
local neighbor
|
||||||
|
local can_move
|
||||||
|
local hashed_pos
|
||||||
|
local step
|
||||||
|
|
||||||
|
for i = 1, #neighbor_grid do
|
||||||
|
neighbor = vec_add(pos, neighbor_grid[i])
|
||||||
|
can_move = get_line_of_sight({x = pos.x, y = neighbor.y, z = pos.z}, neighbor)
|
||||||
|
hashed_pos = minetest.hash_node_position(neighbor)
|
||||||
|
|
||||||
|
if parent
|
||||||
|
and vec_dist(parent, neighbor) < vec_dist(pos, neighbor) then
|
||||||
|
can_move = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if open[hashed_pos]
|
||||||
|
or closed[hashed_pos] then
|
||||||
|
can_move = false
|
||||||
|
elseif can_move then
|
||||||
|
can_move = not is_blocked(neighbor, width, height)
|
||||||
|
|
||||||
|
if not can_move then -- Step Up
|
||||||
|
step = vec_raise(neighbor, 1)
|
||||||
|
can_move = not is_blocked(vec_round(step), width, height)
|
||||||
|
neighbor = vec_round(step)
|
||||||
|
else
|
||||||
|
step = creatura.get_ground_level(vec_new(neighbor), 1)
|
||||||
|
if step.y < neighbor.y
|
||||||
|
and not is_blocked(vec_round(step), width, height) then
|
||||||
|
neighbor = step
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if can_move then
|
||||||
|
table.insert(result, neighbor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function creatura.pathfinder.get_neighbors_climb(pos, width, height, open, closed)
|
||||||
|
local result = {}
|
||||||
|
local neighbor
|
||||||
|
local can_move
|
||||||
|
local hashed_pos
|
||||||
|
local step
|
||||||
|
|
||||||
|
for i = 1, #neighbor_grid_climb do
|
||||||
|
neighbor = vec_add(pos, neighbor_grid_climb[i])
|
||||||
|
can_move = get_line_of_sight({x = pos.x, y = neighbor.y, z = pos.z}, neighbor)
|
||||||
|
hashed_pos = minetest.hash_node_position(neighbor)
|
||||||
|
|
||||||
|
if open[hashed_pos]
|
||||||
|
or closed[hashed_pos] then
|
||||||
|
can_move = false
|
||||||
|
elseif can_move then
|
||||||
|
can_move = not is_blocked(neighbor, width, height)
|
||||||
|
|
||||||
|
if not can_move then -- Step Up
|
||||||
|
step = vec_raise(neighbor, 1)
|
||||||
|
can_move = not is_blocked(vec_round(step), width, height)
|
||||||
|
neighbor = vec_round(step)
|
||||||
|
elseif i < 9 then
|
||||||
|
step = creatura.get_ground_level(vec_new(neighbor), 1)
|
||||||
|
if step.y < neighbor.y
|
||||||
|
and not is_blocked(vec_round(step), width, height) then
|
||||||
|
neighbor = step
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if can_move then
|
||||||
|
table.insert(result, neighbor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function creatura.pathfinder.get_neighbors_fly(pos, width, height, open, closed, parent)
|
||||||
|
local result = {}
|
||||||
|
local neighbor
|
||||||
|
local can_move
|
||||||
|
local hashed_pos
|
||||||
|
|
||||||
|
for i = 1, #neighbor_grid_3d do
|
||||||
|
neighbor = vec_add(pos, neighbor_grid_3d[i])
|
||||||
|
can_move = get_line_of_sight({x = pos.x, y = pos.y, z = pos.z}, neighbor)
|
||||||
|
hashed_pos = minetest.hash_node_position(neighbor)
|
||||||
|
|
||||||
|
if parent
|
||||||
|
and vec_dist(parent, neighbor) < vec_dist(pos, neighbor) then
|
||||||
|
can_move = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if open[hashed_pos]
|
||||||
|
or closed[hashed_pos] then
|
||||||
|
can_move = false
|
||||||
|
elseif can_move then
|
||||||
|
can_move = not is_blocked(neighbor, width, height)
|
||||||
|
end
|
||||||
|
|
||||||
|
if can_move then
|
||||||
|
table.insert(result, neighbor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result, true
|
||||||
|
end
|
||||||
|
|
||||||
|
function creatura.pathfinder.get_neighbors_swim(pos, width, height, open, closed, parent)
|
||||||
|
local result = {}
|
||||||
|
local neighbor
|
||||||
|
local can_move
|
||||||
|
local hashed_pos
|
||||||
|
|
||||||
|
for i = 1, #neighbor_grid_3d do
|
||||||
|
neighbor = vec_add(pos, neighbor_grid_3d[i])
|
||||||
|
can_move = get_line_of_sight({x = pos.x, y = pos.y, z = pos.z}, neighbor)
|
||||||
|
hashed_pos = minetest.hash_node_position(neighbor)
|
||||||
|
|
||||||
|
if (parent
|
||||||
|
and vec_dist(parent, neighbor) < vec_dist(pos, neighbor))
|
||||||
|
or creatura.get_node_def(neighbor).drawtype ~= "liquid" then
|
||||||
|
can_move = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if open[hashed_pos]
|
||||||
|
or closed[hashed_pos] then
|
||||||
|
can_move = false
|
||||||
|
elseif can_move then
|
||||||
|
can_move = not is_blocked(neighbor, width, height)
|
||||||
|
end
|
||||||
|
|
||||||
|
if can_move then
|
||||||
|
table.insert(result, neighbor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result, true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A*
|
||||||
|
|
||||||
|
function creatura.pathfinder.find_path(self, pos1, pos2, neighbor_func)
|
||||||
|
local us_time = minetest.get_us_time()
|
||||||
|
local check_vertical = false
|
||||||
|
neighbor_func = neighbor_func or get_neighbors
|
||||||
|
|
||||||
|
local start = self._path_data.start or {
|
||||||
|
x = floor(pos1.x + 0.5),
|
||||||
|
y = floor(pos1.y + 0.5),
|
||||||
|
z = floor(pos1.z + 0.5)
|
||||||
|
}
|
||||||
|
local goal = {
|
||||||
|
x = floor(pos2.x + 0.5),
|
||||||
|
y = floor(pos2.y + 0.5),
|
||||||
|
z = floor(pos2.z + 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
self._path_data.start = start
|
||||||
|
|
||||||
|
if goal.x == start.x
|
||||||
|
and goal.z == start.z then -- No path can be found
|
||||||
|
self._path_data = {}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local openSet = self._path_data.open or {}
|
||||||
|
local closedSet = self._path_data.closed or {}
|
||||||
|
|
||||||
|
local start_index = minetest.hash_node_position(start)
|
||||||
|
|
||||||
|
openSet[start_index] = {
|
||||||
|
pos = start,
|
||||||
|
parent = nil,
|
||||||
|
gScore = 0,
|
||||||
|
fScore = get_distance(start, goal)
|
||||||
|
}
|
||||||
|
|
||||||
|
local count = self._path_data.count or 1
|
||||||
|
local current_id, current
|
||||||
|
local adjacent
|
||||||
|
local neighbor
|
||||||
|
|
||||||
|
local temp_gScore
|
||||||
|
local new_gScore
|
||||||
|
local hCost
|
||||||
|
|
||||||
|
local hashed_pos
|
||||||
|
local parent_open
|
||||||
|
local parent_closed
|
||||||
|
|
||||||
|
while count > 0 do
|
||||||
|
|
||||||
|
-- Initialize ID and data
|
||||||
|
current_id, current = next(openSet)
|
||||||
|
|
||||||
|
-- Find lowest f cost
|
||||||
|
for i, v in pairs(openSet) do
|
||||||
|
if v.fScore < current.fScore then
|
||||||
|
current_id = i
|
||||||
|
current = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not current_id then self._path_data = {} return end -- failsafe
|
||||||
|
|
||||||
|
-- Add lowest fScore to closedSet and remove from openSet
|
||||||
|
openSet[current_id] = nil
|
||||||
|
closedSet[current_id] = current
|
||||||
|
|
||||||
|
if ((check_vertical or is_on_ground(goal))
|
||||||
|
and current_id == minetest.hash_node_position(goal))
|
||||||
|
or ((not check_vertical and not is_on_ground(goal))
|
||||||
|
and goal.x == current.pos.x
|
||||||
|
and goal.z == current.pos.z) then
|
||||||
|
local path = {}
|
||||||
|
local fail_safe = 0
|
||||||
|
|
||||||
|
for _ in pairs(closedSet) do
|
||||||
|
fail_safe = fail_safe + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
repeat
|
||||||
|
if not closedSet[current_id] then self._path_data = {} return end
|
||||||
|
table.insert(path, closedSet[current_id].pos)
|
||||||
|
current_id = closedSet[current_id].parent
|
||||||
|
until current_id == start_index or #path >= fail_safe
|
||||||
|
|
||||||
|
if not closedSet[current_id] then self._path_data = {} return nil end
|
||||||
|
|
||||||
|
table.insert(path, closedSet[current_id].pos)
|
||||||
|
|
||||||
|
local reverse_path = {}
|
||||||
|
repeat table.insert(reverse_path, table.remove(path)) until #path == 0
|
||||||
|
|
||||||
|
self._path_data = {}
|
||||||
|
return reverse_path
|
||||||
|
end
|
||||||
|
|
||||||
|
parent_open = openSet[current.parent]
|
||||||
|
parent_closed = closedSet[current.parent]
|
||||||
|
adjacent, check_vertical = neighbor_func(
|
||||||
|
current.pos,
|
||||||
|
self.width,
|
||||||
|
self.height,
|
||||||
|
openSet,
|
||||||
|
closedSet,
|
||||||
|
(parent_closed and parent_closed.pos) or (parent_open and parent_open.pos)
|
||||||
|
)
|
||||||
|
-- Fly, Swim, and Climb all return true for check_vertical to properly check if goal has been reached
|
||||||
|
|
||||||
|
-- Go through neighboring nodes
|
||||||
|
for i = 1, #adjacent do
|
||||||
|
neighbor = {
|
||||||
|
pos = adjacent[i],
|
||||||
|
parent = current_id,
|
||||||
|
gScore = 0,
|
||||||
|
fScore = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
temp_gScore = current.gScore + get_distance_to_neighbor(current.pos, neighbor.pos)
|
||||||
|
new_gScore = 0
|
||||||
|
|
||||||
|
hashed_pos = minetest.hash_node_position(neighbor.pos)
|
||||||
|
|
||||||
|
if openSet[hashed_pos] then
|
||||||
|
new_gScore = openSet[hashed_pos].gScore
|
||||||
|
end
|
||||||
|
|
||||||
|
if (temp_gScore < new_gScore
|
||||||
|
or not openSet[hashed_pos])
|
||||||
|
and not closedSet[hashed_pos] then
|
||||||
|
if not openSet[hashed_pos] then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
hCost = get_distance_to_neighbor(neighbor.pos, goal)
|
||||||
|
|
||||||
|
neighbor.gScore = temp_gScore
|
||||||
|
neighbor.fScore = temp_gScore + hCost
|
||||||
|
openSet[hashed_pos] = neighbor
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if minetest.get_us_time() - us_time > a_star_alloted_time then
|
||||||
|
self._path_data = {
|
||||||
|
start = start,
|
||||||
|
open = openSet,
|
||||||
|
closed = closedSet,
|
||||||
|
count = count
|
||||||
|
}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if count > (max_open or 100) then
|
||||||
|
self._path_data = {}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Theta*
|
||||||
|
|
||||||
|
function creatura.pathfinder.find_path_theta(self, pos1, pos2, neighbor_func)
|
||||||
|
local us_time = minetest.get_us_time()
|
||||||
|
local check_vertical = false
|
||||||
|
neighbor_func = neighbor_func or get_neighbors
|
||||||
|
|
||||||
|
local start = self._path_data.start or {
|
||||||
|
x = floor(pos1.x + 0.5),
|
||||||
|
y = floor(pos1.y + 0.5),
|
||||||
|
z = floor(pos1.z + 0.5)
|
||||||
|
}
|
||||||
|
local goal = {
|
||||||
|
x = floor(pos2.x + 0.5),
|
||||||
|
y = floor(pos2.y + 0.5),
|
||||||
|
z = floor(pos2.z + 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
self._path_data.start = start
|
||||||
|
|
||||||
|
if goal.x == start.x
|
||||||
|
and goal.z == start.z then -- No path can be found
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local openSet = self._path_data.open or {}
|
||||||
|
local closedSet = self._path_data.closed or {}
|
||||||
|
|
||||||
|
local start_index = minetest.hash_node_position(start)
|
||||||
|
|
||||||
|
openSet[start_index] = {
|
||||||
|
pos = start,
|
||||||
|
parent = nil,
|
||||||
|
gScore = 0,
|
||||||
|
fScore = get_distance(start, goal)
|
||||||
|
}
|
||||||
|
|
||||||
|
local count = self._path_data.count or 1
|
||||||
|
local current_id, current
|
||||||
|
local current_parent
|
||||||
|
local adjacent
|
||||||
|
local neighbor
|
||||||
|
|
||||||
|
local temp_gScore
|
||||||
|
local new_gScore
|
||||||
|
local hCost
|
||||||
|
|
||||||
|
local hashed_pos
|
||||||
|
local parent_open
|
||||||
|
local parent_closed
|
||||||
|
|
||||||
|
while count > 0 do
|
||||||
|
|
||||||
|
-- Initialize ID and data
|
||||||
|
current_id, current = next(openSet)
|
||||||
|
|
||||||
|
-- Find lowest f cost
|
||||||
|
for i, v in pairs(openSet) do
|
||||||
|
if v.fScore < current.fScore then
|
||||||
|
current_id = i
|
||||||
|
current = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not current_id then return end -- failsafe
|
||||||
|
|
||||||
|
-- Add lowest fScore to closedSet and remove from openSet
|
||||||
|
openSet[current_id] = nil
|
||||||
|
closedSet[current_id] = current
|
||||||
|
|
||||||
|
if ((check_vertical or is_on_ground(goal))
|
||||||
|
and current_id == minetest.hash_node_position(goal))
|
||||||
|
or ((not check_vertical and not is_on_ground(goal))
|
||||||
|
and goal.x == current.pos.x
|
||||||
|
and goal.z == current.pos.z) then
|
||||||
|
local path = {}
|
||||||
|
local fail_safe = 0
|
||||||
|
|
||||||
|
for _ in pairs(closedSet) do
|
||||||
|
fail_safe = fail_safe + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
repeat
|
||||||
|
if not closedSet[current_id] then return end
|
||||||
|
table.insert(path, closedSet[current_id].pos)
|
||||||
|
current_id = closedSet[current_id].parent
|
||||||
|
until current_id == start_index or #path >= fail_safe
|
||||||
|
|
||||||
|
if not closedSet[current_id] then self._path_data = {} return nil end
|
||||||
|
|
||||||
|
table.insert(path, closedSet[current_id].pos)
|
||||||
|
|
||||||
|
local reverse_path = {}
|
||||||
|
repeat table.insert(reverse_path, table.remove(path)) until #path == 0
|
||||||
|
|
||||||
|
self._path_data = {}
|
||||||
|
return reverse_path
|
||||||
|
end
|
||||||
|
|
||||||
|
parent_open = openSet[current.parent]
|
||||||
|
parent_closed = closedSet[current.parent]
|
||||||
|
adjacent, check_vertical = neighbor_func(
|
||||||
|
current.pos,
|
||||||
|
self.width,
|
||||||
|
self.height,
|
||||||
|
openSet,
|
||||||
|
closedSet,
|
||||||
|
(parent_closed and parent_closed.pos) or (parent_open and parent_open.pos)
|
||||||
|
)
|
||||||
|
-- Fly, Swim, and Climb all return true for check_vertical to properly check if goal has been reached
|
||||||
|
|
||||||
|
-- Go through neighboring nodes
|
||||||
|
for i = 1, #adjacent do
|
||||||
|
neighbor = {
|
||||||
|
pos = adjacent[i],
|
||||||
|
parent = current_id,
|
||||||
|
gScore = 0,
|
||||||
|
fScore = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
hashed_pos = minetest.hash_node_position(neighbor.pos)
|
||||||
|
|
||||||
|
if not openSet[hashed_pos]
|
||||||
|
and not closedSet[hashed_pos] then
|
||||||
|
current_parent = closedSet[current.parent] or closedSet[start_index]
|
||||||
|
if not current_parent then
|
||||||
|
current_parent = openSet[current.parent] or openSet[start_index]
|
||||||
|
end
|
||||||
|
|
||||||
|
if current_parent
|
||||||
|
and get_line_of_sight(current_parent.pos, neighbor.pos) then
|
||||||
|
temp_gScore = current_parent.gScore + get_distance_to_neighbor(current_parent.pos, neighbor.pos)
|
||||||
|
new_gScore = 999
|
||||||
|
|
||||||
|
if openSet[hashed_pos] then
|
||||||
|
new_gScore = openSet[hashed_pos].gScore
|
||||||
|
end
|
||||||
|
|
||||||
|
if temp_gScore < new_gScore then
|
||||||
|
hCost = get_distance_to_neighbor(neighbor.pos, goal)
|
||||||
|
neighbor.gScore = temp_gScore
|
||||||
|
neighbor.fScore = temp_gScore + hCost
|
||||||
|
neighbor.parent = minetest.hash_node_position(current_parent.pos)
|
||||||
|
openSet[hashed_pos] = neighbor
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
temp_gScore = current.gScore + get_distance_to_neighbor(current_parent.pos, neighbor.pos)
|
||||||
|
new_gScore = 999
|
||||||
|
|
||||||
|
if openSet[hashed_pos] then
|
||||||
|
new_gScore = openSet[hashed_pos].gScore
|
||||||
|
end
|
||||||
|
|
||||||
|
if temp_gScore < new_gScore then
|
||||||
|
hCost = get_distance_to_neighbor(neighbor.pos, goal)
|
||||||
|
neighbor.gScore = temp_gScore
|
||||||
|
neighbor.fScore = temp_gScore + hCost
|
||||||
|
|
||||||
|
openSet[hashed_pos] = neighbor
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if minetest.get_us_time() - us_time > theta_star_alloted_time then
|
||||||
|
self._path_data = {
|
||||||
|
start = start,
|
||||||
|
open = openSet,
|
||||||
|
closed = closedSet,
|
||||||
|
count = count
|
||||||
|
}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if count > (max_open or 100) then
|
||||||
|
self._path_data = {}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
38
spawning.lua
38
spawning.lua
|
@ -87,6 +87,19 @@ function creatura.register_spawn_item(name, def)
|
||||||
def.description = def.description or "Spawn " .. format_name(name)
|
def.description = def.description or "Spawn " .. format_name(name)
|
||||||
def.inventory_image = def.inventory_image or inventory_image
|
def.inventory_image = def.inventory_image or inventory_image
|
||||||
def.on_place = function(itemstack, player, pointed_thing)
|
def.on_place = function(itemstack, player, pointed_thing)
|
||||||
|
-- If the player right-clicks something like a chest or item frame then
|
||||||
|
-- run the node's on_rightclick callback
|
||||||
|
local under = pointed_thing.under
|
||||||
|
local node = minetest.get_node(under)
|
||||||
|
local node_def = minetest.registered_nodes[node.name]
|
||||||
|
if node_def and node_def.on_rightclick and
|
||||||
|
not (player and player:is_player() and
|
||||||
|
player:get_player_control().sneak) then
|
||||||
|
return node_def.on_rightclick(under, node, player, itemstack,
|
||||||
|
pointed_thing) or itemstack
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Otherwise spawn the mob
|
||||||
local pos = minetest.get_pointed_thing_position(pointed_thing, true)
|
local pos = minetest.get_pointed_thing_position(pointed_thing, true)
|
||||||
if minetest.is_protected(pos, player and player:get_player_name() or "") then return end
|
if minetest.is_protected(pos, player and player:get_player_name() or "") then return end
|
||||||
local mobdef = minetest.registered_entities[name]
|
local mobdef = minetest.registered_entities[name]
|
||||||
|
@ -518,8 +531,11 @@ local function can_spawn(pos, width, height)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local mobs_spawn = minetest.settings:get_bool("mobs_spawn") ~= false
|
||||||
|
|
||||||
function creatura.register_abm_spawn(mob, def)
|
function creatura.register_abm_spawn(mob, def)
|
||||||
local chance = def.chance or 3000
|
local chance = def.chance or 3000
|
||||||
|
local chance_on_load = def.chance_on_load or def.chance / 32
|
||||||
local interval = def.interval or 30
|
local interval = def.interval or 30
|
||||||
local min_height = def.min_height or 0
|
local min_height = def.min_height or 0
|
||||||
local max_height = def.max_height or 128
|
local max_height = def.max_height or 128
|
||||||
|
@ -534,10 +550,15 @@ function creatura.register_abm_spawn(mob, def)
|
||||||
local nodes = def.nodes or {"group:soil", "group:stone"}
|
local nodes = def.nodes or {"group:soil", "group:stone"}
|
||||||
local neighbors = def.neighbors or {"air"}
|
local neighbors = def.neighbors or {"air"}
|
||||||
local spawn_on_load = def.spawn_on_load or false
|
local spawn_on_load = def.spawn_on_load or false
|
||||||
|
local spawn_active = def.spawn_active or true
|
||||||
local spawn_in_nodes = def.spawn_in_nodes or false
|
local spawn_in_nodes = def.spawn_in_nodes or false
|
||||||
local spawn_cap = def.spawn_cap or 5
|
local spawn_cap = def.spawn_cap or 5
|
||||||
|
|
||||||
local function spawn_func(pos, _, _, aocw)
|
local function spawn_func(pos, aocw, is_lbm)
|
||||||
|
|
||||||
|
if not mobs_spawn then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if not spawn_in_nodes then
|
if not spawn_in_nodes then
|
||||||
pos.y = pos.y + 1
|
pos.y = pos.y + 1
|
||||||
|
@ -549,8 +570,8 @@ function creatura.register_abm_spawn(mob, def)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if spawn_on_load then -- Manual checks for LBMs
|
if is_lbm then -- Manual checks for LBMs
|
||||||
if random(chance) > 1 then return end
|
if random(chance_on_load or chance) > 1 then return end
|
||||||
if not minetest.find_node_near(pos, 1, neighbors) then return end
|
if not minetest.find_node_near(pos, 1, neighbors) then return end
|
||||||
if pos.y > max_height or pos.y < min_height then return end
|
if pos.y > max_height or pos.y < min_height then return end
|
||||||
end
|
end
|
||||||
|
@ -674,11 +695,12 @@ function creatura.register_abm_spawn(mob, def)
|
||||||
label = mob .. " spawning",
|
label = mob .. " spawning",
|
||||||
nodenames = nodes,
|
nodenames = nodes,
|
||||||
run_at_every_load = false,
|
run_at_every_load = false,
|
||||||
action = function(pos, ...)
|
action = function(pos, _, _, aocw)
|
||||||
spawn_func(pos, ...)
|
spawn_func(pos, aocw, true)
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
else
|
end
|
||||||
|
if spawn_active then
|
||||||
minetest.register_abm({
|
minetest.register_abm({
|
||||||
label = mob .. " spawning",
|
label = mob .. " spawning",
|
||||||
nodenames = nodes,
|
nodenames = nodes,
|
||||||
|
@ -688,8 +710,8 @@ function creatura.register_abm_spawn(mob, def)
|
||||||
min_y = min_height,
|
min_y = min_height,
|
||||||
max_y = max_height,
|
max_y = max_height,
|
||||||
catch_up = false,
|
catch_up = false,
|
||||||
action = function(pos, ...)
|
action = function(pos, _, _, aocw)
|
||||||
spawn_func(pos, ...)
|
spawn_func(pos, aocw, false)
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue