creatura/api.lua

485 lines
13 KiB
Lua
Raw Normal View History

2022-02-10 16:32:53 -08:00
--------------
-- Creatura --
--------------
creatura.api = {}
-- Math --
local floor = math.floor
local random = math.random
local function clamp(val, min, max)
if val < min then
val = min
elseif max < val then
val = max
end
return val
end
local vec_dist = vector.distance
local vec_equals = vector.equals
2022-02-10 16:32:53 -08:00
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)}
2022-02-10 16:32:53 -08:00
end
local function vec_raise(v, n)
if not v then return end
return {x = v.x, y = v.y + n, z = v.z}
2022-02-10 16:32:53 -08:00
end
---------------
-- Local API --
---------------
2022-08-08 15:52:48 -07:00
local function contains_val(tbl, val)
for _, v in pairs(tbl) do
2022-08-08 15:52:48 -07:00
if v == val then return true end
end
return false
2022-02-10 16:32:53 -08:00
end
2022-07-07 18:45:37 -07:00
----------------------------
-- Registration Functions --
----------------------------
2022-02-10 16:32:53 -08:00
creatura.registered_movement_methods = {}
function creatura.register_movement_method(name, func)
creatura.registered_movement_methods[name] = func
2022-02-10 16:32:53 -08:00
end
creatura.registered_utilities = {}
function creatura.register_utility(name, func)
creatura.registered_utilities[name] = func
2022-02-10 16:32:53 -08:00
end
2022-07-07 18:45:37 -07:00
---------------
-- Utilities --
---------------
function creatura.is_valid(mob)
if not mob then return false end
if type(mob) == "table" then mob = mob.object end
if type(mob) == "userdata" then
if mob:is_player() then
if mob:get_look_horizontal() then return mob end
else
if mob:get_yaw() then return mob end
end
end
return false
end
function creatura.is_alive(mob)
if not creatura.is_valid(mob) then
return false
end
if type(mob) == "table" then
2022-08-12 21:59:32 -07:00
return (mob.hp or mob.health or 0) > 0
2022-07-07 18:45:37 -07:00
end
if mob:is_player() then
return mob:get_hp() > 0
else
local ent = mob:get_luaentity()
2022-08-12 21:59:32 -07:00
return ent and (ent.hp or ent.health or 0) > 0
2022-07-07 18:45:37 -07:00
end
end
------------------------
-- Environment access --
------------------------
2022-02-10 16:32:53 -08:00
2022-04-03 12:46:40 -07:00
local default_node_def = {walkable = true} -- both ignore and unknown nodes are walkable
function creatura.get_node_height_from_def(name)
local def = minetest.registered_nodes[name] or default_node_def
2022-04-03 12:46:40 -07:00
if not def then return 0.5 end
if def.walkable then
if def.drawtype == "nodebox" then
if def.node_box
and def.node_box.type == "fixed" then
2022-04-03 12:46:40 -07:00
if type(def.node_box.fixed[1]) == "number" then
return 0.5 + def.node_box.fixed[5]
elseif type(def.node_box.fixed[1]) == "table" then
return 0.5 + def.node_box.fixed[1][5]
else
return 1
end
else
return 1
end
else
return 1
end
else
return 1
end
end
local get_node = minetest.get_node
2022-04-03 12:46:40 -07:00
function creatura.get_node_def(node) -- Node can be name or pos
if type(node) == "table" then
node = get_node(node).name
end
local def = minetest.registered_nodes[node] or default_node_def
if def.walkable
and creatura.get_node_height_from_def(node) < 0.26 then
def.walkable = false -- workaround for nodes like snow
end
return def
2022-04-03 12:46:40 -07:00
end
local get_node_def = creatura.get_node_def
function creatura.get_ground_level(pos, range)
range = range or 2
local above = vector.round(pos)
local under = {x = above.x, y = above.y - 1, z = above.z}
if not get_node_def(above).walkable and get_node_def(under).walkable then return above end
if get_node_def(above).walkable then
for _ = 1, range do
under = above
above = {x = above.x, y = above.y + 1, z = above.z}
if not get_node_def(above).walkable and get_node_def(under).walkable then return above end
end
end
if not get_node_def(under).walkable then
for _ = 1, range do
above = under
under = {x = under.x, y = under.y - 1, z = under.z}
if not get_node_def(above).walkable and get_node_def(under).walkable then return above end
end
end
return above
2022-05-26 15:31:55 -07:00
end
2022-02-10 16:32:53 -08:00
function creatura.is_pos_moveable(pos, width, height)
2022-09-28 14:41:56 -07:00
local edge1 = {
x = pos.x - (width + 0.2),
y = pos.y,
z = pos.z - (width + 0.2),
}
2022-09-28 14:41:56 -07:00
local edge2 = {
x = pos.x + (width + 0.2),
y = pos.y,
z = pos.z + (width + 0.2),
}
2022-09-28 14:41:56 -07:00
local base_p = {x = pos.x, y = pos.y, z = pos.z}
local top_p = {x = pos.x, y = pos.y + height, z = pos.z}
for z = edge1.z, edge2.z do
for x = edge1.x, edge2.x do
base_p.x, base_p.z = pos.x + x, pos.z + z
top_p.x, top_p.z = pos.x + x, pos.z + z
local ray = minetest.raycast(base_p, top_p, false, false)
for pointed_thing in ray do
if pointed_thing.type == "node" then
local name = get_node(pointed_thing.under).name
if creatura.get_node_def(name).walkable then
return false
end
end
end
end
end
return true
2022-02-10 16:32:53 -08:00
end
local moveable = creatura.is_pos_moveable
function creatura.fast_ray_sight(pos1, pos2, water)
local ray = minetest.raycast(pos1, pos2, false, water or false)
2022-07-21 13:25:11 -07:00
local pointed_thing = ray:next()
while pointed_thing do
if pointed_thing.type == "node"
and creatura.get_node_def(pointed_thing.under).walkable then
return false, vec_dist(pos1, pointed_thing.intersection_point), pointed_thing.ref, pointed_thing.intersection_point
end
2022-07-21 13:25:11 -07:00
pointed_thing = ray:next()
end
2022-07-21 13:25:11 -07:00
return true, vec_dist(pos1, pos2), false, pos2
2022-02-10 16:32:53 -08:00
end
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
2022-02-10 16:32:53 -08:00
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))
2022-02-10 16:32:53 -08:00
end
function creatura.sensor_floor(self, range, water)
local pos = self.object:get_pos()
local pos2 = vec_raise(pos, -range)
local _, dist, node = fast_ray_sight(pos, pos2, water or false)
return dist, node
2022-02-10 16:32:53 -08:00
end
function creatura.sensor_ceil(self, range, water)
local pos = vec_raise(self.object:get_pos(), self.height)
local pos2 = vec_raise(pos, range)
local _, dist, node = fast_ray_sight(pos, pos2, water or false)
return dist, node
2022-02-10 16:32:53 -08:00
end
2022-08-05 17:42:37 -07:00
function creatura.get_nearby_player(self, range)
2022-08-09 15:18:47 -07:00
local pos = self.object:get_pos()
if not pos then return end
2022-08-12 21:59:32 -07:00
local stored = self._nearby_obj or {}
local objects = (#stored > 0 and stored) or self:store_nearby_objects(range)
2022-02-10 16:32:53 -08:00
for _, object in ipairs(objects) do
if object:is_player()
and creatura.is_alive(object) then
return object
end
2022-02-10 16:32:53 -08:00
end
end
2022-08-05 17:42:37 -07:00
function creatura.get_nearby_players(self, range)
2022-08-09 15:18:47 -07:00
local pos = self.object:get_pos()
if not pos then return end
2022-08-12 21:59:32 -07:00
local stored = self._nearby_obj or {}
local objects = (#stored > 0 and stored) or self:store_nearby_objects(range)
local nearby = {}
2022-02-10 16:32:53 -08:00
for _, object in ipairs(objects) do
if object:is_player()
and creatura.is_alive(object) then
table.insert(nearby, object)
end
2022-02-10 16:32:53 -08:00
end
return nearby
end
2022-08-05 17:42:37 -07:00
function creatura.get_nearby_object(self, name, range)
2022-08-09 15:18:47 -07:00
local pos = self.object:get_pos()
if not pos then return end
2022-08-12 21:59:32 -07:00
local stored = self._nearby_obj or {}
local objects = (#stored > 0 and stored) or self:store_nearby_objects(range)
2022-02-10 16:32:53 -08:00
for _, object in ipairs(objects) do
2022-08-08 15:54:33 -07:00
local ent = creatura.is_alive(object) and object:get_luaentity()
2022-08-08 15:52:48 -07:00
if ent
and object ~= self.object
2022-08-08 15:52:48 -07:00
and not ent._ignore
and ((type(name) == "table" and contains_val(name, ent.name))
or ent.name == name) then
return object
end
2022-02-10 16:32:53 -08:00
end
end
2022-08-05 17:42:37 -07:00
function creatura.get_nearby_objects(self, name, range)
2022-08-09 15:18:47 -07:00
local pos = self.object:get_pos()
if not pos then return end
2022-08-12 21:59:32 -07:00
local stored = self._nearby_obj or {}
local objects = (#stored > 0 and stored) or self:store_nearby_objects(range)
local nearby = {}
2022-02-10 16:32:53 -08:00
for _, object in ipairs(objects) do
2022-08-08 15:54:33 -07:00
local ent = creatura.is_alive(object) and object:get_luaentity()
2022-08-08 15:52:48 -07:00
if ent
and object ~= self.object
2022-08-08 15:52:48 -07:00
and not ent._ignore
and ((type(name) == "table" and contains_val(name, ent.name))
or ent.name == name) then
table.insert(nearby, object)
end
2022-02-10 16:32:53 -08:00
end
return nearby
end
2022-07-07 18:45:37 -07:00
creatura.get_nearby_entity = creatura.get_nearby_object
creatura.get_nearby_entities = creatura.get_nearby_objects
2022-02-10 16:32:53 -08:00
--------------------
-- Global Mob API --
--------------------
function creatura.drop_items(self)
if not self.drops then return end
for i = 1, #self.drops do
local drop_def = self.drops[i]
local name = drop_def.name
if not name then return end
local min_amount = drop_def.min or 1
local max_amount = drop_def.max or 2
local chance = drop_def.chance or 1
local amount = random(min_amount, max_amount)
if random(chance) < 2 then
local pos = self.object:get_pos()
local item = minetest.add_item(pos, ItemStack(name .. " " .. amount))
if item then
item:add_velocity({
x = random(-2, 2),
y = 1.5,
z = random(-2, 2)
})
end
end
end
2022-02-10 16:32:53 -08:00
end
2022-08-01 14:05:10 -07:00
function creatura.basic_punch_func(self, puncher, tflp, tool_caps, dir)
if not puncher then return end
2022-08-03 23:36:23 -07:00
local tool
local tool_name = ""
2022-08-03 23:40:40 -07:00
local add_wear = false
if puncher:is_player() then
2022-08-03 23:36:23 -07:00
tool = puncher:get_wielded_item()
tool_name = tool:get_name()
2022-08-03 23:42:08 -07:00
add_wear = not minetest.is_creative_enabled(puncher)
end
if (self.immune_to
2022-08-08 15:52:48 -07:00
and contains_val(self.immune_to, tool_name)) then
return
end
2022-09-26 00:43:47 -07:00
local damage = 0
local armor_grps = self.object:get_armor_groups() or self.armor_groups or {}
for group, val in pairs(tool_caps.damage_groups or {}) do
local dmg_x = tflp / (tool_caps.full_punch_interval or 1.4)
damage = damage + val * clamp(dmg_x, 0, 1) * ((armor_grps[group] or 0) / 100.0)
end
if damage > 0 then
local dist = vec_dist(self.object:get_pos(), puncher:get_pos())
dir.y = 0.2
if self.touching_ground then
self:apply_knockback(dir, (damage / dist) * 8)
end
self:hurt(damage)
2022-07-16 21:38:40 -07:00
end
2022-08-03 23:40:40 -07:00
if add_wear then
2022-08-03 23:44:22 -07:00
local wear = floor((tool_caps.full_punch_interval / 75) * 9000)
2022-08-03 23:36:23 -07:00
tool:add_wear(wear)
puncher:set_wielded_item(tool)
end
2022-09-08 01:51:12 -07:00
if random(2) < 2 then
self:play_sound("hurt")
end
2022-09-08 01:51:12 -07:00
if (tflp or 0) > 0.5 then
self:play_sound("hit")
end
self:indicate_damage()
2022-02-10 16:32:53 -08:00
end
local path = minetest.get_modpath("creatura")
dofile(path.."/mob_meta.lua")