creatura/mob_meta.lua

1249 lines
30 KiB
Lua
Raw Normal View History

2022-02-10 16:32:53 -08:00
--------------
-- Mob Meta --
--------------
-- Math --
local pi = math.pi
local pi2 = pi * 2
local abs = math.abs
local floor = math.floor
local random = math.random
local sin = math.sin
local cos = math.cos
local atan2 = math.atan2
local function diff(a, b) -- Get difference between 2 angles
return atan2(sin(b - a), cos(b - a))
2022-02-10 16:32:53 -08:00
end
local vec_dir = vector.direction
local vec_dist = vector.distance
local vec_multi = vector.multiply
local vec_sub = vector.subtract
local vec_add = vector.add
local vec_normal = vector.normalize
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)
return {x = v.x, y = v.y + n, z = v.z}
2022-02-10 16:32:53 -08:00
end
2022-08-13 22:20:21 -07:00
local function get_sightline(pos1, pos2)
local dir = vec_dir(pos1, pos2)
local dist = vec_dist(pos1, pos2)
local pos
2022-08-13 22:20:21 -07:00
for i = 0, dist do
if dist > 0 then
pos = {
x = pos1.x + dir.x * (i / dist),
y = pos1.y + dir.y * (i / dist),
z = pos1.z + dir.z * (i / dist)
}
else
pos = pos1
end
if creatura.get_node_def(pos).walkable then
return false
end
end
return true
2022-02-10 16:32:53 -08:00
end
-- Local Utilities --
local function is_value_in_table(tbl, val)
for _, v in pairs(tbl) do
if v == val then
return true
end
end
return false
2022-05-30 05:07:00 -07:00
end
2022-02-10 16:32:53 -08:00
-------------------------
-- Physics/Vitals Tick --
-------------------------
local mob = {
max_health = 20,
2022-08-27 20:07:53 -07:00
max_breath = 30,
fire_resistance = 0.5,
fall_resistance = 0,
armor_groups = {fleshy = 100},
damage = 2,
speed = 4,
2022-02-10 16:32:53 -08:00
tracking_range = 16,
despawn_after = nil,
max_fall = 3,
2022-02-10 16:32:53 -08:00
stepheight = 1.1,
hitbox = {
width = 0.5,
height = 1
},
follow = {},
fancy_collide = false,
liquid_submergence = 0.25,
liquid_drag = 1
2022-08-27 20:07:53 -07:00
2022-02-10 16:32:53 -08:00
}
local mob_meta = {__index = mob}
function mob:indicate_damage()
self._original_texture_mod = self._original_texture_mod or self.object:get_texture_mod()
self.object:set_texture_mod(self._original_texture_mod .. "^[colorize:#FF000040")
minetest.after(0.2, function()
if creatura.is_alive(self) then
self.object:set_texture_mod(self._original_texture_mod)
end
end)
2022-02-10 16:32:53 -08:00
end
-- Set Movement Data
function mob:move(pos, method, speed_factor, anim)
self._movement_data.goal = pos
self._movement_data.method = method
self._movement_data.last_neighbor = nil
self._movement_data.gravity = self._movement_data.gravity or -9.8
self._movement_data.speed = (self.speed or 2) * (speed_factor or 1)
if anim then
self._movement_data.anim = anim
end
2022-02-10 16:32:53 -08:00
end
-- Clear Movement Data
function mob:halt()
self._movement_data = {
goal = nil,
method = nil,
2022-06-21 09:44:10 -07:00
func = nil,
last_neighbor = nil,
gravity = self._movement_data.gravity or -9.8,
speed = 0
}
2022-08-06 16:19:48 -07:00
--self.object:set_velocity({x = 0, y = 0, z = 0})
self._path_data = {}
2022-08-08 15:52:48 -07:00
self._tyaw = self.object:get_yaw() or 0
2022-02-10 16:32:53 -08:00
end
-- Turn to specified yaw
local function interp_rad(a, b, w)
local cs = (1 - w) * cos(a) + w * cos(b)
local sn = (1 - w) * sin(a) + w * sin(b)
return atan2(sn, cs)
end
2022-08-06 16:19:48 -07:00
local function turn(self, tyaw, rate)
2022-08-06 21:50:50 -07:00
rate = rate or 5
2022-08-06 16:19:48 -07:00
local rot = self.object:get_rotation()
local yaw = self.object:get_yaw()
if not yaw then return end
local step = math.min(self.dtime * rate, abs(diff(yaw, tyaw)) % (pi2))
rot.y = interp_rad(yaw, tyaw, step)
2022-11-29 11:15:47 -08:00
if rot.y ~= rot.y then return end
2022-08-06 16:19:48 -07:00
self.object:set_rotation(rot)
end
2022-02-10 16:32:53 -08:00
function mob:turn_to(tyaw, rate)
2022-08-06 16:19:48 -07:00
if self.step_delay then
self._tyaw = tyaw
self._movement_data.turn_rate = rate or 5
return
end
turn(self, tyaw, rate)
2022-08-05 17:42:37 -07:00
end
function mob:do_turn()
2022-08-06 16:19:48 -07:00
if not self.step_delay then return end
2022-08-05 17:42:37 -07:00
local tyaw = self._tyaw
local rate = self._movement_data.turn_rate or 5
2022-08-06 16:19:48 -07:00
if not tyaw then return end
turn(self, self._tyaw, rate)
2022-02-10 16:32:53 -08:00
end
-- Set Gravity (default of -9.8)
function mob:set_gravity(gravity)
self._movement_data.gravity = gravity or -9.8
2022-02-10 16:32:53 -08:00
end
-- Sets Velocity to desired speed in mobs current look direction
2022-08-06 16:19:48 -07:00
function mob:set_forward_velocity(speed)
2022-08-12 23:24:39 -07:00
if self.step_delay then
self._movement_data.horz_vel = speed
else
local yaw = self.object:get_yaw()
local vel = self.object:get_velocity()
vel.x = sin(yaw) * -speed
vel.z = cos(yaw) * speed
self.object:set_velocity(vel)
end
2022-02-10 16:32:53 -08:00
end
-- Sets Velocity on y axis
function mob:set_vertical_velocity(speed)
2022-08-12 23:24:39 -07:00
if self.step_delay then
self._movement_data.vert_vel = speed
else
local vel = self.object:get_velocity()
vel.y = speed
self.object:set_velocity(vel)
end
2022-08-06 16:19:48 -07:00
end
function mob:do_velocity()
2022-08-12 23:24:39 -07:00
if not self.step_delay then return end
2022-08-06 16:19:48 -07:00
local data = self._movement_data or {}
local vel = self.object:get_velocity()
local yaw = self.object:get_yaw()
2022-11-29 11:15:47 -08:00
if not vel or not yaw then return end
2022-09-08 01:51:12 -07:00
local horz_vel = data.horz_vel --or (data.gravity >= 0 and 0)
local vert_vel = data.vert_vel --or (data.gravity >= 0 and 0)
2022-08-09 15:18:47 -07:00
vel.x = (horz_vel and (sin(yaw) * -horz_vel)) or vel.x
2022-08-06 16:19:48 -07:00
vel.y = vert_vel or vel.y
2022-08-09 15:18:47 -07:00
vel.z = (horz_vel and (cos(yaw) * horz_vel)) or vel.z
self.object:set_velocity(vel)
2022-02-10 16:32:53 -08:00
end
-- Applies knockback in 'dir'
function mob:apply_knockback(dir, power)
if not dir then return end
power = power or 6
local knockback = vec_multi(dir, power)
self.object:add_velocity(knockback)
2022-02-10 16:32:53 -08:00
end
-- Punch 'target'
function mob:punch_target(target) --
target:punch(self.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = self.damage or 5},
})
2022-02-10 16:32:53 -08:00
end
-- Apply damage to mob
function mob:hurt(health)
if self.protected then return end
self.hp = self.hp - math.ceil(health)
2022-07-16 21:38:40 -07:00
if self.hp < 0 then self.hp = 0 end
2022-02-10 16:32:53 -08:00
end
-- Add HP to mob
function mob:heal(health)
if self.protected then return end
self.hp = self.hp + math.ceil(health)
if self.hp > self.max_health then
self.hp = self.max_health
end
2022-02-10 16:32:53 -08:00
end
-- Return position at center of mobs hitbox
function mob:get_center_pos()
2022-06-09 05:45:45 -07:00
local pos = self.object:get_pos()
if not pos then return end
return vec_raise(pos, self.height * 0.5 or 0.5)
2022-02-10 16:32:53 -08:00
end
-- Return true if position is within box
function mob:pos_in_box(pos, size)
if not pos then return false end
local center = self:get_center_pos()
2022-06-09 05:45:45 -07:00
if not center then return false end
local width = size or self.width
local height = size or (self.height * 0.5)
if not size
and self.width < 0.5 then
width = 0.5
end
local edge_a = {
x = center.x - width,
y = center.y - height,
z = center.z - width
}
local edge_b = {
x = center.x + width,
y = center.y + height,
z = center.z + width
}
local minp, maxp = vector.sort(edge_a, edge_b)
if pos.x >= minp.x
and pos.y >= minp.y
and pos.z >= minp.z
and pos.x <= maxp.x
and pos.y <= maxp.y
and pos.z <= maxp.z then
return true
end
return false
2022-02-10 16:32:53 -08:00
end
-- Terrain Navigation --
function mob:get_wander_pos(min_range, max_range, dir)
local pos = vec_center(self.object:get_pos())
pos.y = floor(pos.y + 0.5)
if creatura.get_node_def(pos).walkable then -- Occurs if small mob is touching a fence
local offset = vector.add(pos, vec_multi(vec_dir(pos, self.object:get_pos()), 1.5))
pos.x = floor(offset.x + 0.5)
pos.z = floor(offset.z + 0.5)
pos = creatura.get_ground_level(pos, 1)
end
local width = self.width
local outset = random(min_range, max_range)
if width < 0.6 then width = 0.6 end
local move_dir = vec_normal({
x = random(-10, 10) * 0.1,
y = 0,
z = random(-10, 10) * 0.1
})
local pos2 = vec_add(pos, vec_multi(move_dir, width))
if creatura.get_node_def(pos2).walkable
and not dir then
for _ = 1, 3 do
move_dir = {
x = move_dir.z,
y = 0,
z = move_dir.x * -1
}
pos2 = vec_add(pos, vec_multi(move_dir, width))
if not creatura.get_node_def(pos2).walkable then
break
end
end
elseif dir then
move_dir = dir
end
for i = 1, outset do
local a_pos = vec_add(pos2, vec_multi(move_dir, i))
local b_pos = {x = a_pos.x, y = a_pos.y - 1, z = a_pos.z}
if creatura.get_node_def(a_pos).walkable
or not creatura.get_node_def(b_pos).walkable then
a_pos = creatura.get_ground_level(a_pos, floor(self.stepheight or 1))
end
if not creatura.get_node_def(a_pos).walkable then
pos2 = a_pos
else
break
end
end
return pos2
2022-02-10 16:32:53 -08:00
end
2022-02-23 01:41:56 -08:00
function mob:get_wander_pos_3d(min_range, max_range, dir, vert_bias)
local pos = vec_center(self.object:get_pos())
if creatura.get_node_def(pos).walkable then -- Occurs if small mob is touching a fence
local offset = vector.add(pos, vec_multi(vec_dir(pos, self.object:get_pos()), 1.5))
pos.x = floor(offset.x + 0.5)
pos.z = floor(offset.z + 0.5)
pos = creatura.get_ground_level(pos, 1)
end
local width = self.width
local outset = random(min_range, max_range)
if width < 0.6 then width = 0.6 end
local move_dir = vec_normal({
x = random(-10, 10) * 0.1,
y = vert_bias or random(-10, 10) * 0.1,
z = random(-10, 10) * 0.1
})
local pos2 = vec_add(pos, vec_multi(move_dir, width))
if creatura.get_node_def(pos2).walkable
and not dir then
for _ = 1, 3 do
move_dir = {
x = move_dir.z,
y = move_dir.y,
z = move_dir.x * -1
}
pos2 = vec_add(pos, vec_multi(move_dir, width))
if not creatura.get_node_def(pos2).walkable then
break
end
end
elseif dir then
move_dir = dir
end
for i = 1, outset do
local a_pos = vec_add(pos2, vec_multi(move_dir, i))
if creatura.get_node_def(a_pos).walkable then
a_pos = creatura.get_ground_level(a_pos, floor(self.stepheight or 1))
end
if not creatura.get_node_def(a_pos).walkable then
pos2 = a_pos
else
break
end
end
return pos2
2022-02-10 16:32:53 -08:00
end
2022-11-03 23:55:20 -07:00
function mob:is_pos_safe(pos, ignore_liquid)
if not pos then return end
2022-11-03 23:55:20 -07:00
local n_def = creatura.get_node_def(pos)
if minetest.get_item_group(n_def.name, "igniter") > 0
or (not ignore_liquid
and (n_def.drawtype == "liquid"
or creatura.get_node_def(vec_raise(pos, -1)).drawtype == "liquid")) then return false end
local fall_safe = false
local fall_pos = {x = pos.x, y = floor(pos.y + 0.5), z = pos.z}
if self.max_fall ~= 0 then
for _ = 1, self.max_fall or 3 do
fall_pos.y = fall_pos.y - 1
if creatura.get_node_def(fall_pos).walkable then
fall_safe = true
break
end
end
else
fall_safe = true
end
return fall_safe
2022-02-10 16:32:53 -08:00
end
-- Set mobs animation (if specified animation isn't already playing)
2023-01-14 00:11:45 -08:00
function mob:animate(animation, transition)
2023-02-20 00:04:55 -08:00
if not animation
or not self.animations[animation] then
return
end
-- Handle Transition Data
2023-01-14 00:11:45 -08:00
local transition_data = self._anim_transition or {}
2023-02-20 00:04:55 -08:00
local parent_anim = transition_data.parent
local child_anim = transition_data.child
if child_anim
and animation == parent_anim
and transition == child_anim then
2023-01-14 00:11:45 -08:00
local timer = transition_data.timer
2023-02-20 00:04:55 -08:00
transition_data.timer = (timer > 0 and timer - self.dtime) or 0
if timer <= 0 then
animation = child_anim
2023-01-14 00:11:45 -08:00
end
else
transition_data = {}
end
self._anim_transition = transition_data
2023-02-20 00:04:55 -08:00
-- Set Animation
if not self._anim
or self._anim ~= animation
or (transition
2023-02-20 00:04:55 -08:00
and not transition_data.timer) then
local anim = self.animations[animation]
2022-08-12 21:59:32 -07:00
if anim[2] then anim = anim[random(#anim)] end
self.object:set_animation(anim.range, anim.speed, anim.frame_blend, anim.loop)
self._anim = animation
2023-02-20 00:04:55 -08:00
-- Set Transition Data
if transition
and (not transition_data.timer
or transition_data.timer > 0) then
2023-01-14 00:11:45 -08:00
local anim_length = (anim.range.y - anim.range.x) / anim.speed
self._anim_transition = {
parent = animation,
child = transition,
timer = anim_length
}
end
end
2023-01-14 00:11:45 -08:00
return animation
2022-02-10 16:32:53 -08:00
end
-- Set texture to variable at 'id' index in 'tbl' or 'textures'
function mob:set_texture(id, tbl)
local _table = self.textures
if tbl then
_table = tbl
end
if not _table
or not _table[id] then
return
end
2022-07-16 21:38:40 -07:00
local tex = _table[id]
if type(tex) == "table" then
self.object:set_properties({
textures = {unpack(tex)}
})
else
self.object:set_properties({
textures = {tex}
})
end
return _table[id]
2022-02-10 16:32:53 -08:00
end
-- Set/reset mesh
function mob:set_mesh(id)
local mesh = self.mesh
2023-03-14 20:04:14 -07:00
if mesh
and not self.meshes then
self.object:set_properties({
mesh = mesh
})
return mesh
end
local meshes = self.meshes or {}
if #meshes > 0 then
local mesh_no = id or self.mesh_no or random(#meshes)
self.object:set_properties({
mesh = meshes[mesh_no]
})
self.mesh_no = mesh_no
2023-03-14 20:04:14 -07:00
if self.mesh_textures then
self.textures = self.mesh_textures[mesh_no]
self.texture_no = random(#self.textures)
self:set_texture(self.texture_no, self.textures)
end
return meshes[mesh_no]
end
end
2022-02-10 16:32:53 -08:00
-- Set scale to base scale times 'x' and update bordering positions
function mob:set_scale(x)
local def = minetest.registered_entities[self.name]
2022-10-14 20:07:51 -07:00
local scale = def.visual_size or {x = 1, y = 1}
local box = def.collisionbox
local new_box = {}
for k, v in ipairs(box) do
new_box[k] = v * x
end
self.object:set_properties({
visual_size = {
x = scale.x * x,
y = scale.y * x
},
collisionbox = new_box
})
2022-09-24 16:57:33 -07:00
--self._border = index_box_border(self)
2022-02-10 16:32:53 -08:00
end
-- Fixes mob scale being changed when attached to a parent
function mob:fix_attached_scale(parent)
local scale = self:get_visual_size()
local parent_size = parent:get_properties().visual_size
self.object:set_properties({
visual_size = {
x = scale.x / parent_size.x,
y = scale.y / parent_size.y
},
})
2022-02-10 16:32:53 -08:00
end
-- Add sets 'id' to 'val' in permanent data
function mob:memorize(id, val)
self.perm_data[id] = val
return self.perm_data[id]
2022-02-10 16:32:53 -08:00
end
-- Remove 'id' from permanent data
function mob:forget(id)
self.perm_data[id] = nil
2022-02-10 16:32:53 -08:00
end
-- Return value from 'id' in permanent data
function mob:recall(id)
return self.perm_data[id]
2022-02-10 16:32:53 -08:00
end
-- Return true on interval specified by 'n'
function mob:timer(n)
local t1 = floor(self.active_time)
local t2 = floor(self.active_time + self.dtime)
if t2 > t1 and t2%n == 0 then return true end
2022-02-10 16:32:53 -08:00
end
-- Play 'sound' from self.sounds
function mob:play_sound(sound)
local spec = self.sounds and self.sounds[sound]
local parameters = {object = self.object}
2022-02-10 16:32:53 -08:00
if type(spec) == "table" then
local name = spec.name
local pitch = 1.0
2022-02-10 16:32:53 -08:00
pitch = pitch - (random(-10, 10) * 0.005)
2022-02-10 16:32:53 -08:00
parameters.gain = spec.gain or 1
parameters.max_hear_distance = spec.distance or 8
parameters.fade = spec.fade or 1
parameters.pitch = pitch
return minetest.sound_play(name, parameters)
end
return minetest.sound_play(spec, parameters)
2022-02-10 16:32:53 -08:00
end
-- Return current collisionbox
function mob:get_hitbox()
2022-08-27 20:07:53 -07:00
if not self:get_props() then return self.collisionbox end
return self:get_props().collisionbox
2022-02-10 16:32:53 -08:00
end
-- Return height of current collisionbox
function mob:get_height()
local hitbox = self:get_hitbox()
return hitbox[5] - hitbox[2]
2022-02-10 16:32:53 -08:00
end
-- Return current visual size
function mob:get_visual_size()
2022-08-27 20:07:53 -07:00
if not self:get_props() then return end
return self:get_props().visual_size
2022-02-10 16:32:53 -08:00
end
local function is_group_in_table(tbl, name)
for _, v in pairs(tbl) do
2022-10-14 20:07:51 -07:00
if type(v) == "string"
and minetest.get_item_group(name, v:split(":")[2]) > 0 then
return true
end
end
return false
2022-02-10 16:32:53 -08:00
end
function mob:follow_wielded_item(player)
if not player
or not self.follow then return end
local item = player:get_wielded_item()
local name = item:get_name()
if type(self.follow) == "string"
and (name == self.follow
or minetest.get_item_group(name, self.follow:split(":")[2]) > 0) then
return item, name
end
if type(self.follow) == "table"
and (is_value_in_table(self.follow, name)
or is_group_in_table(self.follow, name)) then
return item, name
end
2022-02-10 16:32:53 -08:00
end
2022-06-21 09:44:10 -07:00
function mob:follow_item(stack)
if not stack
or not self.follow then return end
local name = stack:get_name()
if type(self.follow) == "string"
and (name == self.follow
or minetest.get_item_group(name, self.follow:split(":")[2]) > 0) then
return stack, name
end
if type(self.follow) == "table"
and (is_value_in_table(self.follow, name)
or is_group_in_table(self.follow, name)) then
return stack, name
end
end
2022-02-10 16:32:53 -08:00
function mob:get_target(target)
local alive = creatura.is_alive(target)
if not alive then
return false, false, nil
end
if type(target) == "table" then
target = target.object
end
local pos = self:get_center_pos()
2022-06-09 05:45:45 -07:00
if not pos then return false, false, nil end
local tpos = target:get_pos()
tpos.y = floor(tpos.y + 0.5)
2022-08-13 22:20:21 -07:00
local line_of_sight = get_sightline(pos, tpos)
return true, line_of_sight, tpos
2022-02-10 16:32:53 -08:00
end
2022-08-12 21:59:32 -07:00
function mob:store_nearby_objects(radius)
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 track_radius = self.tracking_range or 8
if track_radius < 8 then track_radius = 8 end
local objects = minetest.get_objects_inside_radius(pos, radius or track_radius)
2022-08-09 15:18:47 -07:00
if #objects < 1 then return end
local objs = {}
for _, object in ipairs(objects) do
if creatura.is_alive(object)
and object ~= self.object then
local ent = object:get_luaentity()
local player = object:is_player()
if (ent
and not ent._ignore)
or player then
table.insert(objs, object)
end
end
end
self._nearby_objs = objs
2022-08-12 21:59:32 -07:00
return objs
2022-08-09 15:18:47 -07:00
end
2022-08-27 20:07:53 -07:00
function mob:get_props()
local props = self.properties or self.object and self.object:get_properties()
self.properties = props
return props
end
2022-02-10 16:32:53 -08:00
-- Actions
function mob:set_action(func)
self._action = func
2022-02-10 16:32:53 -08:00
end
function mob:get_action()
if type(self._action) ~= "table" then
return self._action
end
return nil
2022-02-10 16:32:53 -08:00
end
function mob:clear_action()
self._action = {}
self._movement_data.goal = nil
self._movement_data.func = nil
2022-02-10 16:32:53 -08:00
end
function mob:set_utility(func)
2022-08-12 21:59:32 -07:00
if not self._utility_data then return end
self._utility_data.func = func
2022-02-10 16:32:53 -08:00
end
function mob:get_utility()
if not self._utility_data then return end
return self._utility_data.utility
2022-02-10 16:32:53 -08:00
end
function mob:initiate_utility(utility, ...)
local func = creatura.registered_utilities[utility]
2022-05-31 13:28:13 -07:00
if not func or not self._utility_data then return end
self._utility_data.utility = utility
self:clear_action()
func(...)
2022-02-10 16:32:53 -08:00
end
function mob:set_utility_score(n)
2023-08-06 19:42:55 -07:00
if not self._utility_data then return end
self._utility_data.score = n or 0
2022-02-10 16:32:53 -08:00
end
2022-08-06 21:50:50 -07:00
function mob:get_utility_score()
2022-08-06 21:47:14 -07:00
return (self._utility_data and self._utility_data.score) or 0
end
2022-04-29 12:37:21 -05:00
function mob:try_initiate_utility(utility, score, ...)
2022-05-20 15:38:53 -07:00
if self._utility_data
and score >= self._utility_data.score then
2022-04-29 12:37:21 -05:00
self:initiate_utility(utility, ...)
self:set_utility_score(score)
end
end
2022-05-20 15:38:53 -07:00
function mob:clear_utility()
self._utility_data = {
utility = nil,
func = nil,
score = 0
}
2022-05-20 15:38:53 -07:00
end
2022-02-10 16:32:53 -08:00
-- Functions
function mob:activate(staticdata, dtime)
2022-08-27 20:07:53 -07:00
self:get_props()
self.width = self:get_hitbox()[4] or 0.5
self.height = self:get_height() or 1
self._tyaw = self.object:get_yaw()
self.last_yaw = self.object:get_yaw()
self.in_liquid = false
self.is_falling = false
self.touching_ground = false
-- Backend Data (Should not be modified unless modder knows what they're doing)
self._movement_data = {
goal = nil,
method = nil,
2022-06-21 09:44:10 -07:00
func = nil,
last_neighbor = nil,
gravity = -9.8,
speed = 0
}
self._path_data = {}
self._path = {}
self._task = {}
self._action = {}
local pos = self.object:get_pos()
local node = minetest.get_node(pos)
if node
and minetest.get_item_group(node.name, "liquid") > 0 then
self.in_liquid = node.name
end
-- Staticdata
2022-09-30 15:21:41 -07:00
local data = minetest.deserialize(staticdata)
if data then
local tp
for k, v in pairs(data) do
tp = type(v)
if tp ~= "function"
and tp ~= "nil"
and tp ~= "userdata" then
self[k] = v
end
end
end
-- 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
2022-02-10 16:32:53 -08:00
if not self.textures then
2022-08-27 20:07:53 -07:00
local textures = self:get_props().textures
2022-02-10 16:32:53 -08:00
if textures then self.textures = textures end
end
if not self.perm_data then
if self.memory then
self.perm_data = self.memory
else
self.perm_data = {}
end
2022-07-16 21:38:40 -07:00
if #self.textures > 0 then self.texture_no = random(#self.textures) end
end
2022-02-10 16:32:53 -08:00
2022-09-24 16:57:33 -07:00
self.active_time = self:recall("active_time") or 0
if self:recall("despawn_after") ~= nil then
self.despawn_after = self:recall("despawn_after")
end
2022-09-24 16:57:33 -07:00
self._despawn = self:recall("_despawn") or nil
2022-02-10 16:32:53 -08:00
if self._despawn
2022-08-22 18:34:07 -07:00
and self.despawn_after
and self.object then
self.object:remove()
return
end
2022-02-10 16:32:53 -08:00
self._breath = self:recall("_breath") or (self.max_breath or 30)
2022-09-24 16:57:33 -07:00
--self._border = index_box_border(self)
2022-02-10 16:32:53 -08:00
if self.textures
and self.texture_no then
if not self.textures[self.texture_no] then
self.texture_no = random(#self.textures)
end
2022-02-10 16:32:53 -08:00
self:set_texture(self.texture_no, self.textures)
end
self.max_health = self.max_health or 10
2022-02-10 16:32:53 -08:00
self.hp = self.hp or self.max_health
if type(self.armor_groups) ~= "table" then
2022-02-10 16:32:53 -08:00
self.armor_groups = {}
end
self.armor_groups.immortal = 1
self.object:set_armor_groups(self.armor_groups)
if self.timer
and type(self.timer) == "number" then -- fix crash for converted mobs_redo mobs
self.timer = function(_self, n)
local t1 = floor(_self.active_time)
local t2 = floor(_self.active_time + _self.dtime)
if t2 > t1 and t2%n == 0 then return true end
end
end
2022-02-10 16:32:53 -08:00
2022-08-09 15:18:47 -07:00
self:store_nearby_objects()
if self.activate_func then
self:activate_func(self, staticdata, dtime)
end
2022-02-10 16:32:53 -08:00
end
function mob:staticdata()
local data = {}
data.perm_data = self.perm_data
data.hp = self.hp or self.max_health
data.texture_no = self.texture_no or random(#self.textures)
2023-01-26 14:35:09 -08:00
data.mesh_no = self.mesh_no or (self.meshes and random(#self.meshes))
return minetest.serialize(data)
2022-02-10 16:32:53 -08:00
end
function mob:on_step(dtime, moveresult)
if not self.hp then return end
2022-07-24 21:15:06 -07:00
self.last_yaw = self.object:get_yaw()
2022-08-13 20:06:48 -07:00
self.stand_pos = self.object:get_pos()
if not self.stand_pos then return end
self.stand_pos.y = self.stand_pos.y + 0.01
self.stand_node = self.stand_node or minetest.get_node(self.stand_pos)
self.dtime = dtime or 0.09
self.moveresult = moveresult or {}
self.touching_ground = false
if moveresult then
self.touching_ground = moveresult.touching_ground
end
2022-08-12 21:59:32 -07:00
local prop_tick = self._prop_tick or 0
prop_tick = prop_tick - 1
if prop_tick <= 0 then
2022-08-13 20:06:48 -07:00
self.stand_node = minetest.get_node(self.stand_pos)
2022-08-27 20:07:53 -07:00
prop_tick = 6
end
if self:timer(1) then
self.width = self:get_hitbox()[4] or 0.5
self.height = self:get_height() or 1
end
2022-08-13 20:06:48 -07:00
if self._vitals then
self:_vitals()
end
if self._physics then
2022-09-28 15:18:47 -07:00
self:_physics(moveresult)
2022-08-09 15:18:47 -07:00
end
2022-08-13 20:06:48 -07:00
self._prop_tick = prop_tick
2022-08-09 15:18:47 -07:00
if self:timer(10) then self:store_nearby_objects() end -- Reduce expensive calls
2022-08-06 16:19:48 -07:00
self:do_velocity()
self:do_turn()
if self.utility_stack
and self._execute_utilities then
self:_execute_utilities()
end
-- Die
if self.hp <= 0
and self.death_func then
self:death_func()
self:halt()
return
end
if self.step_func
and self.perm_data then
self:step_func(dtime, moveresult)
end
2022-08-27 20:07:53 -07:00
self.properties = nil
self.active_time = self.active_time + dtime
2022-09-24 16:57:33 -07:00
self:memorize("active_time", self.active_time)
if self.despawn_after then
local despawn = math.floor(self.active_time / self.despawn_after)
2022-09-24 17:05:04 -07:00
if despawn > 1 then self.object:remove() return end
if despawn > 0
and not self._despawn then
self._despawn = self:memorize("_despawn", true)
2022-09-24 16:57:33 -07:00
end
end
2022-02-10 16:32:53 -08:00
end
function mob:on_deactivate(removal)
self._task = {}
self._action = {}
if self.deactivate_func then
self:deactivate_func(removal)
end
2022-02-10 16:32:53 -08:00
end
----------------
-- Object API --
----------------
-- Physics
local function collision_detection(self)
2022-06-11 14:51:51 -07:00
if not creatura.is_alive(self)
or self.fancy_collide == false then return end
local pos = self.stand_pos
local width = self.width + 0.25
local objects = minetest.get_objects_in_area(vec_sub(pos, width), vec_add(pos, width))
if #objects < 2 then return end
local pos2
local dir
local vel, vel2
for i = 2, #objects do
local object = objects[i]
if creatura.is_alive(object)
and not self.object:get_attach()
and not object:get_attach() then
if i > 5 then break end
pos2 = object:get_pos()
dir = vec_dir(pos, pos2)
dir.y = 0
if dir.x == 0 and dir.z == 0 then
dir = vector.new(random(-1, 1) * random(), 0,
random(-1, 1) * random())
end
vel = vec_multi(dir, 1.5)
vel2 = vec_multi(dir, -2) -- multiplying by -2 accounts for friction
self.object:add_velocity(vel2)
object:add_velocity(vel)
end
end
2022-02-10 16:32:53 -08:00
end
local mob_friction = 7
2022-02-10 16:32:53 -08:00
2022-09-24 17:01:08 -07:00
function mob:_physics()
-- Physics
creatura.default_water_physics(self)
collision_detection(self)
-- Cache Environment Info
2022-08-09 15:18:47 -07:00
local in_liquid = self.in_liquid
local on_ground = self.touching_ground
if not in_liquid
and not on_ground then
self.is_falling = true
else
self.is_falling = false
end
2022-08-09 15:18:47 -07:00
local move_data = self._movement_data
if not in_liquid
2022-08-27 20:07:53 -07:00
--and not move_data.func
2022-08-09 15:18:47 -07:00
and move_data.gravity ~= 0 then
local vel = self.object:get_velocity()
local friction = math.min(self.dtime * mob_friction, 0.5)
2022-09-26 00:43:47 -07:00
local nvel = {x = vel.x * (1 - friction), y = vel.y, z = vel.z * (1 - friction)}
self.object:set_velocity(nvel)
end
2022-02-10 16:32:53 -08:00
end
-- Movement Control
2022-06-21 09:44:10 -07:00
function mob:move_to(goal, method, speed_factor)
local get_method = creatura.registered_movement_methods[method]
local data = self._movement_data
2022-06-21 09:44:10 -07:00
if get_method
and not data.func then
self._movement_data.func = get_method(self, goal, speed_factor)
return self._movement_data.func(self, goal, speed_factor)
end
if data.func then
local move = data.func
return move(self, goal, speed_factor)
end
2022-02-10 16:32:53 -08:00
end
-- Execute Actions
2022-02-10 16:32:53 -08:00
local function tbl_equals(tbl1, tbl2)
local match = true
for k, v in pairs(tbl1) do
if not tbl2[k]
and tbl2[k] ~= v then
match = false
break
end
end
return match
2022-02-10 16:32:53 -08:00
end
function mob:_execute_utilities()
local is_alive = self.hp > 0
if not self._utility_data then
self._utility_data = {
utility = nil,
func = nil,
2022-08-06 21:47:14 -07:00
step_delay = nil,
score = 0
}
end
if not self._util_cooldown then
2023-03-14 20:04:14 -07:00
self._util_cooldown = {}
end
local loop_data = {
utility = nil,
func = nil,
2022-08-06 21:47:14 -07:00
step_delay = nil,
score = 0
}
if (self:timer(self.util_timer or 1)
or not self._utility_data.func)
and is_alive then
local util_data = self._utility_data
local util_stack = self.utility_stack
local utility
local get_score
2023-03-14 20:04:14 -07:00
local cooldown
local step_delay
local score, args
for i = 1, #util_stack do
utility = util_stack[i].utility
get_score = util_stack[i].get_score
2023-03-14 20:04:14 -07:00
cooldown = self._util_cooldown[i] or 0
step_delay = util_stack[i].step_delay
score, args = get_score(self)
2023-03-14 20:04:14 -07:00
if cooldown > 0 then
cooldown = cooldown - (self.util_timer or 1)
end
if util_data.utility
and utility == util_data.utility
and util_data.score > 0
and score <= 0 then
self._utility_data = {
utility = nil,
func = nil,
2022-08-06 21:47:14 -07:00
step_delay = nil,
score = 0
}
util_data = self._utility_data
end
2023-03-14 20:04:14 -07:00
if score > 0
and score >= util_data.score
2023-03-14 20:04:14 -07:00
and score >= loop_data.score
and cooldown <= 0 then
loop_data = {
utility = utility,
score = score,
2023-03-14 20:04:14 -07:00
util_no = i,
2022-08-06 21:47:14 -07:00
step_delay = step_delay,
args = args
}
end
2023-03-14 20:04:14 -07:00
self._util_cooldown[i] = cooldown
end
end
if loop_data.utility
2022-05-31 14:07:50 -07:00
and loop_data.args then
2022-05-31 16:54:00 -07:00
if not self._utility_data
or not self._utility_data.args then
self._utility_data = loop_data
2022-05-31 14:07:50 -07:00
else
local no_data = not self._utility_data.utility and not self._utility_data.args
local same_args = tbl_equals(self._utility_data.args, loop_data.args)
local new_util = self._utility_data.utility ~= loop_data.utility or not same_args
2022-05-31 14:07:50 -07:00
if no_data
or new_util then -- if utilities are different or utilities are the same and args are different set new data
self._utility_data = loop_data
end
end
end
if self._utility_data.utility then -- If a utility is currently selected
2022-08-06 21:47:14 -07:00
local util_data = self._utility_data
if not util_data.func then
self:initiate_utility(util_data.utility, unpack(util_data.args))
end
local func = util_data.func
2022-08-06 22:25:00 -07:00
if util_data.step_delay
and self.hp > 0 then
2022-08-06 21:47:14 -07:00
self.step_delay = util_data.step_delay
else
self.step_delay = nil
self._step_delay = 0
end
2022-08-05 17:42:37 -07:00
local step_delay = self.step_delay and (self._step_delay or 0)
if not func then return end
2022-08-05 17:42:37 -07:00
if step_delay then
if step_delay > 0 then
self._step_delay = step_delay - self.dtime
return
else
self._step_delay = self.step_delay
end
end
local dtime = self.dtime
2022-08-05 17:55:30 -07:00
self.dtime = dtime + (self.step_delay or 0)
2022-08-12 21:59:32 -07:00
if self.horz_vel
and self.horz_vel ~= 0 then
self:set_forward_velocity(nil)
end
if self.vert_vel
and self.vert_vel ~= 0 then
self:set_vertical_velocity(nil)
end
2023-03-14 20:04:14 -07:00
local func_complete, func_cooldown = func(self)
if func_complete then
if util_data.util_no then
self._util_cooldown[util_data.util_no] = func_cooldown
end
self._utility_data = {
utility = nil,
func = nil,
score = 0
}
self:clear_action()
end
2022-06-21 09:44:10 -07:00
local action = self._action
if action
and type(action) ~= "table" then
if action(self) then
self:clear_action()
end
end
2022-08-05 17:42:37 -07:00
self.dtime = dtime
end
2022-02-10 16:32:53 -08:00
end
-- Vitals
function creatura.register_mob(name, def)
local box_width = def.hitbox and def.hitbox.width or 0.5
local box_height = def.hitbox and def.hitbox.height or 1
local hitbox = {-box_width, 0, -box_width, box_width, box_height, box_width}
def.physical = def.physical or true
def.collide_with_objects = def.collide_with_objects or false
def.visual = "mesh"
2022-12-06 17:03:36 -08:00
def.mesh = def.mesh or (def.meshes and def.meshes[1])
def.makes_footstep_sound = def.makes_footstep_sound or false
if def.static_save ~= false then
def.static_save = true
end
2023-01-14 00:11:45 -08:00
def.collisionbox = def.collisionbox or hitbox
def._creatura_mob = true
def.sounds = def.sounds or {}
if not def.sounds.hit then
def.sounds.hit = {
name = "creatura_hit",
gain = 0.5,
distance = 16,
variations = 3
}
end
def._vitals = def._vitals or creatura.default_vitals
def.on_activate = function(self, staticdata, dtime)
2022-02-10 16:32:53 -08:00
return self:activate(staticdata, dtime)
end
def.get_staticdata = function(self)
return self:staticdata(self)
end
minetest.register_entity(name, setmetatable(def, mob_meta))
2022-02-16 15:34:17 +00:00
end