2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- translation and mod checks
|
|
|
|
|
|
|
|
local S = minetest.get_translator("mobs")
|
|
|
|
local FS = function(...) return minetest.formspec_escape(S(...)) end
|
2020-10-26 17:38:53 +01:00
|
|
|
local use_cmi = minetest.global_exists("cmi")
|
2024-12-19 12:55:40 +01:00
|
|
|
local use_mc2 = minetest.get_modpath("mcl_core") -- MineClonia support
|
|
|
|
local use_vh1 = minetest.get_modpath("visual_harm_1ndicators")
|
|
|
|
local use_tr = minetest.get_modpath("toolranks")
|
|
|
|
local use_invisibility = minetest.get_modpath("invisibility")
|
|
|
|
|
|
|
|
-- Node check helper
|
|
|
|
|
|
|
|
local function has(nodename)
|
|
|
|
if nodename and minetest.registered_nodes[nodename] then return nodename end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Global table
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
mobs = {
|
|
|
|
mod = "redo",
|
2024-12-19 12:55:40 +01:00
|
|
|
version = "20241219",
|
|
|
|
spawning_mobs = {},
|
|
|
|
translate = S,
|
|
|
|
node_snow = has(minetest.registered_aliases["mapgen_snow"])
|
|
|
|
or has("mcl_core:snow") or has("default:snow") or "air",
|
|
|
|
node_dirt = has(minetest.registered_aliases["mapgen_dirt"])
|
|
|
|
or has("mcl_core:dirt") or has("default:dirt") or "mobs:fallback_node"
|
2020-10-26 17:38:53 +01:00
|
|
|
}
|
2024-12-19 12:55:40 +01:00
|
|
|
mobs.fallback_node = mobs.node_dirt
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-04-05 14:46:21 +02:00
|
|
|
-- localize common functions
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
local pi = math.pi
|
|
|
|
local square = math.sqrt
|
|
|
|
local sin = math.sin
|
|
|
|
local cos = math.cos
|
|
|
|
local abs = math.abs
|
|
|
|
local min = math.min
|
|
|
|
local max = math.max
|
|
|
|
local random = math.random
|
|
|
|
local floor = math.floor
|
|
|
|
local ceil = math.ceil
|
|
|
|
local rad = math.rad
|
2024-12-19 12:55:40 +01:00
|
|
|
local deg = math.deg
|
2020-10-26 17:38:53 +01:00
|
|
|
local atann = math.atan
|
|
|
|
local atan = function(x)
|
2024-12-19 12:55:40 +01:00
|
|
|
if not x or x ~= x then return 0 else return atann(x) end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
local table_copy = table.copy
|
|
|
|
local table_remove = table.remove
|
|
|
|
local vdirection = vector.direction
|
|
|
|
local vmultiply = vector.multiply
|
|
|
|
local vsubtract = vector.subtract
|
|
|
|
local settings = minetest.settings
|
|
|
|
|
|
|
|
-- creative check
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
local creative_cache = minetest.settings:get_bool("creative_mode")
|
|
|
|
function mobs.is_creative(name)
|
2024-12-19 12:55:40 +01:00
|
|
|
return creative_cache or minetest.check_player_privs(name, {creative = true})
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Load settings
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
local damage_enabled = settings:get_bool("enable_damage")
|
|
|
|
local mobs_spawn = settings:get_bool("mobs_spawn") ~= false
|
|
|
|
local peaceful_only = settings:get_bool("only_peaceful_mobs")
|
|
|
|
local disable_blood = settings:get_bool("mobs_disable_blood")
|
2024-12-19 12:55:40 +01:00
|
|
|
local mob_hit_effect = settings:get_bool("mob_hit_effect")
|
2020-10-26 17:38:53 +01:00
|
|
|
local mobs_drop_items = settings:get_bool("mobs_drop_items") ~= false
|
|
|
|
local mobs_griefing = settings:get_bool("mobs_griefing") ~= false
|
|
|
|
local spawn_protected = settings:get_bool("mobs_spawn_protected") ~= false
|
2021-07-24 12:22:10 +02:00
|
|
|
local spawn_monster_protected = settings:get_bool("mobs_spawn_monster_protected") ~= false
|
2020-10-26 17:38:53 +01:00
|
|
|
local remove_far = settings:get_bool("remove_far_mobs") ~= false
|
2020-12-02 17:45:37 +01:00
|
|
|
local mob_area_spawn = settings:get_bool("mob_area_spawn")
|
2020-10-26 17:38:53 +01:00
|
|
|
local difficulty = tonumber(settings:get("mob_difficulty")) or 1.0
|
|
|
|
local max_per_block = tonumber(settings:get("max_objects_per_block") or 99)
|
|
|
|
local mob_nospawn_range = tonumber(settings:get("mob_nospawn_range") or 12)
|
2024-12-19 12:55:40 +01:00
|
|
|
local active_limit = tonumber(settings:get("mob_active_limit")) or 0
|
2020-10-26 17:38:53 +01:00
|
|
|
local mob_chance_multiplier = tonumber(settings:get("mob_chance_multiplier") or 1)
|
2021-01-06 16:05:43 +01:00
|
|
|
local peaceful_player_enabled = settings:get_bool("enable_peaceful_player")
|
2021-06-05 14:06:31 +02:00
|
|
|
local mob_smooth_rotate = settings:get_bool("mob_smooth_rotate") ~= false
|
2024-12-19 12:55:40 +01:00
|
|
|
local mob_height_fix = settings:get_bool("mob_height_fix") ~= false
|
|
|
|
local mob_log_spawn = settings:get_bool("mob_log_spawn") == true
|
2020-10-26 17:38:53 +01:00
|
|
|
local active_mobs = 0
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- loop interval for node and main functions timers
|
|
|
|
local node_timer_interval = tonumber(settings:get("mob_node_timer_interval") or 0.25)
|
|
|
|
local main_timer_interval = tonumber(settings:get("mob_main_timer_interval") or 1.0)
|
|
|
|
|
|
|
|
-- pathfinding settings
|
|
|
|
local pathfinding_enable = settings:get_bool("mob_pathfinding_enable") or true
|
|
|
|
-- how long before stuck mobs start searching
|
|
|
|
local pathfinding_stuck_timeout = tonumber(
|
|
|
|
settings:get("mob_pathfinding_stuck_timeout")) or 3.0
|
|
|
|
-- how long will mob follow path before giving up
|
|
|
|
local pathfinding_stuck_path_timeout = tonumber(
|
|
|
|
settings:get("mob_pathfinding_stuck_path_timeout")) or 5.0
|
|
|
|
-- which algorithm to use, Dijkstra(default) or A*_noprefetch or A*
|
|
|
|
-- fix settings not allowing "*"
|
|
|
|
local pathfinding_algorithm = settings:get("mob_pathfinding_algorithm") or "Dijkstra"
|
|
|
|
|
|
|
|
if pathfinding_algorithm == "AStar_noprefetch" then
|
|
|
|
pathfinding_algorithm = "A*_noprefetch"
|
|
|
|
elseif pathfinding_algorithm == "AStar" then
|
|
|
|
pathfinding_algorithm = "A*"
|
|
|
|
end
|
|
|
|
|
|
|
|
-- max search distance from search positions (default 16)
|
|
|
|
local pathfinding_searchdistance = tonumber(
|
|
|
|
settings:get("mob_pathfinding_searchdistance") or 16)
|
|
|
|
-- max jump height (default 4)
|
|
|
|
local pathfinding_max_jump = tonumber(settings:get("mob_pathfinding_max_jump") or 4)
|
|
|
|
-- max drop height (default 6)
|
|
|
|
local pathfinding_max_drop = tonumber(settings:get("mob_pathfinding_max_drop") or 6)
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- Peaceful mode message so players will know there are no monsters
|
|
|
|
if peaceful_only then
|
|
|
|
minetest.register_on_joinplayer(function(player)
|
|
|
|
minetest.chat_send_player(player:get_player_name(),
|
2024-12-19 12:55:40 +01:00
|
|
|
S("** Peaceful Mode Active - No Monsters Will Spawn"))
|
2020-10-26 17:38:53 +01:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- calculate aoc range for mob count
|
|
|
|
local aoc_range = tonumber(settings:get("active_block_range")) * 16
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- can we attack Creatura mobs ?
|
|
|
|
local creatura = minetest.get_modpath("creatura") and
|
|
|
|
settings:get_bool("mobs_attack_creatura") == true
|
|
|
|
|
|
|
|
-- default mob settings
|
|
|
|
|
|
|
|
mobs.mob_class = {
|
|
|
|
state = "stand",
|
2020-10-26 17:38:53 +01:00
|
|
|
fly_in = "air",
|
|
|
|
owner = "",
|
|
|
|
order = "",
|
2024-12-19 12:55:40 +01:00
|
|
|
jump = true, jump_height = 4,
|
2020-10-26 17:38:53 +01:00
|
|
|
lifetimer = 180, -- 3 minutes
|
|
|
|
texture_mods = "",
|
|
|
|
view_range = 5,
|
2024-12-19 12:55:40 +01:00
|
|
|
walk_velocity = 1, run_velocity = 2,
|
|
|
|
light_damage = 0, light_damage_min = 14, light_damage_max = 15,
|
2020-10-26 17:38:53 +01:00
|
|
|
water_damage = 0,
|
2021-04-05 14:46:21 +02:00
|
|
|
lava_damage = 4,
|
|
|
|
fire_damage = 4,
|
2020-12-07 17:01:24 +01:00
|
|
|
air_damage = 0,
|
2024-12-19 12:55:40 +01:00
|
|
|
node_damage = true,
|
2020-10-26 17:38:53 +01:00
|
|
|
suffocation = 2,
|
2024-12-19 12:55:40 +01:00
|
|
|
fall_damage = true,
|
2020-10-26 17:38:53 +01:00
|
|
|
fall_speed = -10, -- must be lower than -2 (default: -10)
|
|
|
|
drops = {},
|
|
|
|
armor = 100,
|
|
|
|
sounds = {},
|
|
|
|
knock_back = true,
|
|
|
|
walk_chance = 50,
|
|
|
|
stand_chance = 30,
|
|
|
|
attack_chance = 5,
|
2024-12-19 12:55:40 +01:00
|
|
|
attack_patience = 11,
|
2020-10-26 17:38:53 +01:00
|
|
|
passive = false,
|
2024-12-19 12:55:40 +01:00
|
|
|
blood_amount = 5, blood_texture = "mobs_blood.png",
|
2020-10-26 17:38:53 +01:00
|
|
|
shoot_offset = 0,
|
|
|
|
floats = 1, -- floats in water by default
|
|
|
|
replace_offset = 0,
|
|
|
|
timer = 0,
|
2024-12-19 12:55:40 +01:00
|
|
|
env_damage_timer = 0,
|
2020-10-26 17:38:53 +01:00
|
|
|
tamed = false,
|
|
|
|
pause_timer = 0,
|
|
|
|
horny = false,
|
|
|
|
hornytimer = 0,
|
|
|
|
child = false,
|
|
|
|
gotten = false,
|
|
|
|
health = 0,
|
|
|
|
reach = 3,
|
|
|
|
docile_by_day = false,
|
|
|
|
time_of_day = 0.5,
|
|
|
|
fear_height = 0,
|
|
|
|
runaway_timer = 0,
|
|
|
|
immune_to = {},
|
|
|
|
explosion_timer = 3,
|
|
|
|
allow_fuse_reset = true,
|
|
|
|
stop_to_explode = true,
|
2024-12-19 12:55:40 +01:00
|
|
|
dogshoot_count = 0, dogshoot_count_max = 5, dogshoot_count2_max = 5,
|
2020-10-26 17:38:53 +01:00
|
|
|
group_attack = false,
|
|
|
|
attack_monsters = false,
|
|
|
|
attack_animals = false,
|
|
|
|
attack_players = true,
|
|
|
|
attack_npcs = true,
|
2024-12-19 12:55:40 +01:00
|
|
|
attack_ignore = nil,
|
|
|
|
friendly_fire = true,
|
2020-10-26 17:38:53 +01:00
|
|
|
facing_fence = false,
|
2021-08-18 21:42:51 +02:00
|
|
|
_breed_countdown = nil,
|
2024-12-19 12:55:40 +01:00
|
|
|
_tame_countdown = nil,
|
2020-10-26 17:38:53 +01:00
|
|
|
_cmi_is_mob = true
|
|
|
|
}
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local mob_class = mobs.mob_class -- Compatibility
|
2020-10-26 17:38:53 +01:00
|
|
|
local mob_class_meta = {__index = mob_class}
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- return True if number of mobs is at limit
|
|
|
|
|
|
|
|
local function at_limit()
|
|
|
|
|
|
|
|
if active_limit and active_limit > 0
|
|
|
|
and active_mobs and active_mobs >= active_limit then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
2021-04-05 14:46:21 +02:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- play sound
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:mob_sound(sound)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not sound then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local pitch = self.child and 1.5 or 1.0 -- higher pitch for a child
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
pitch = pitch + random(-10, 10) * 0.005 -- little random pitch difference
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
minetest.sound_play(sound, {
|
|
|
|
object = self.object,
|
|
|
|
gain = 1.0,
|
|
|
|
max_hear_distance = (self.sounds and self.sounds.distance) or 10,
|
|
|
|
pitch = pitch
|
|
|
|
}, true)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- attack player/mob
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mob_class:do_attack(player, force)
|
|
|
|
|
|
|
|
if self.state == "attack" and not force then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.attack = player
|
|
|
|
self.state = "attack"
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if random(100) < 90 then self:mob_sound(self.sounds.war_cry) end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- calculate distance
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local function get_distance(a, b)
|
|
|
|
|
|
|
|
if not a or not b then return 50 end -- nil check and default distance
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
|
|
|
|
|
|
|
|
return square(x * x + y * y + z * z)
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- are we a real player ?
|
|
|
|
|
|
|
|
local function is_player(player)
|
|
|
|
|
|
|
|
if player and type(player) == "userdata" and minetest.is_player(player) then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- collision function based on jordan4ibanez' open_ai mod
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:collision()
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local pos = self.object:get_pos() ; if not pos then return 0, 0 end
|
2020-10-26 17:38:53 +01:00
|
|
|
local x, z = 0, 0
|
2024-12-19 12:55:40 +01:00
|
|
|
local prop = self.object:get_properties()
|
|
|
|
local width = -prop.collisionbox[1] + prop.collisionbox[4] + 0.5
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
for _,object in pairs(minetest.get_objects_inside_radius(pos, width)) do
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-05-16 11:33:49 +02:00
|
|
|
if object:is_player() then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local pos2 = object:get_pos()
|
2024-12-19 12:55:40 +01:00
|
|
|
local vx, vz = pos.x - pos2.x, pos.z - pos2.z
|
|
|
|
local force = width - (vx * vx + vz * vz) ^ 0.5
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if force > 0 then
|
|
|
|
force = force * 2 ; x = x + vx * force ; z = z + vz * force
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
return x, z
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- check if string exists in another string or table
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local function check_for(look_for, look_inside)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if type(look_inside) == "string" and look_inside == look_for then return true
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
elseif type(look_inside) == "table" then
|
|
|
|
|
|
|
|
for _, str in pairs(look_inside) do
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if str == look_for then return true end
|
2021-03-25 16:46:51 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if str and str:find("group:") then
|
2021-03-25 16:46:51 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local group = str:split(":")[2] or ""
|
2021-03-25 16:46:51 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if minetest.get_item_group(look_for, group) ~= 0 then return true end
|
2021-03-25 16:46:51 +01:00
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- move mob in facing direction
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:set_velocity(v)
|
|
|
|
|
|
|
|
-- halt mob if it has been ordered to stay
|
|
|
|
if self.order == "stand" then
|
|
|
|
|
2021-07-24 12:22:10 +02:00
|
|
|
local vel = self.object:get_velocity() or {y = 0}
|
|
|
|
|
|
|
|
self.object:set_velocity({x = 0, y = vel.y, z = 0})
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local c_x, c_y = 0, 0
|
|
|
|
|
|
|
|
-- can mob be pushed, if so calculate direction
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.pushable then c_x, c_y = self:collision() end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2020-12-02 17:45:37 +01:00
|
|
|
local yaw = (self.object:get_yaw() or 0) + (self.rotate or 0)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- nil check for velocity
|
2020-12-02 17:45:37 +01:00
|
|
|
v = v or 0.01
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- check if standing in liquid with max viscosity of 7
|
|
|
|
local visc = min(minetest.registered_nodes[self.standing_in].liquid_viscosity, 7)
|
|
|
|
|
|
|
|
-- only slow mob trying to move while inside a viscous fluid that
|
|
|
|
-- they aren't meant to be in (fish in water, spiders in cobweb etc)
|
2024-12-19 12:55:40 +01:00
|
|
|
if v > 0 and visc and visc > 0 and not check_for(self.standing_in, self.fly_in) then
|
2020-10-26 17:38:53 +01:00
|
|
|
v = v / (visc + 1)
|
|
|
|
end
|
|
|
|
|
2020-12-02 17:45:37 +01:00
|
|
|
-- set velocity
|
2024-12-19 12:55:40 +01:00
|
|
|
local vel = self.object:get_velocity() or {y = 0}
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:set_velocity({
|
|
|
|
x = (sin(yaw) * -v) + c_x, y = vel.y, z = (cos(yaw) * v) + c_y})
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- calculate mob velocity
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mob_class:get_velocity()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local v = self.object:get_velocity() ; if not v then return 0 end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
return (v.x * v.x + v.z * v.z) ^ 0.5
|
|
|
|
end
|
|
|
|
|
|
|
|
-- set and return valid yaw
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mob_class:set_yaw(yaw, delay)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not yaw or yaw ~= yaw then yaw = 0 end
|
2021-06-05 14:06:31 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
delay = mob_smooth_rotate and delay or 0
|
2021-06-05 14:06:31 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- simplified yaw clamp
|
|
|
|
if yaw > 6.283185 then
|
|
|
|
yaw = yaw - 6.283185
|
|
|
|
elseif yaw < 0 then
|
|
|
|
yaw = 6.283185 + yaw
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if delay == 0 then self.object:set_yaw(yaw) ; return yaw ; end
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
self.target_yaw = yaw
|
|
|
|
self.delay = delay
|
|
|
|
|
|
|
|
return self.target_yaw
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mobs:yaw(entity, yaw, delay) -- [deprecated]
|
2020-10-26 17:38:53 +01:00
|
|
|
mob_class.set_yaw(entity, yaw, delay)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- set defined animation
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:set_animation(anim, force)
|
|
|
|
|
|
|
|
if not self.animation or not anim then return end
|
|
|
|
|
|
|
|
self.animation.current = self.animation.current or ""
|
|
|
|
|
|
|
|
-- only use different animation for attacks when using same set
|
|
|
|
if force ~= true and anim ~= "punch" and anim ~= "shoot"
|
|
|
|
and string.find(self.animation.current, anim) then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local num = 0
|
|
|
|
|
|
|
|
-- check for more than one animation (max 4)
|
|
|
|
for n = 1, 4 do
|
|
|
|
|
|
|
|
if self.animation[anim .. n .. "_start"]
|
|
|
|
and self.animation[anim .. n .. "_end"] then
|
|
|
|
num = n
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- choose random animation from set
|
|
|
|
if num > 0 then
|
|
|
|
num = random(0, num)
|
|
|
|
anim = anim .. (num ~= 0 and num or "")
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if (anim == self.animation.current and force ~= true)
|
2020-10-26 17:38:53 +01:00
|
|
|
or not self.animation[anim .. "_start"]
|
|
|
|
or not self.animation[anim .. "_end"] then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
self.animation.current = anim
|
|
|
|
|
|
|
|
self.object:set_animation({
|
|
|
|
x = self.animation[anim .. "_start"],
|
|
|
|
y = self.animation[anim .. "_end"]},
|
2024-12-19 12:55:40 +01:00
|
|
|
self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
|
2020-10-26 17:38:53 +01:00
|
|
|
0, self.animation[anim .. "_loop"] ~= false)
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mobs:set_animation(entity, anim) -- [deprecated]
|
2020-10-26 17:38:53 +01:00
|
|
|
entity.set_animation(entity, anim)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- check line of sight using raycasting (thanks Astrobe)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mob_class:line_of_sight(pos1, pos2)
|
|
|
|
|
|
|
|
local ray = minetest.raycast(pos1, pos2, true, false) -- ignore entities
|
2020-10-26 17:38:53 +01:00
|
|
|
local thing = ray:next()
|
2024-12-19 12:55:40 +01:00
|
|
|
local name, nodedef
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
while thing do
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if thing.type == "node" then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
name = minetest.get_node(thing.under).name
|
|
|
|
nodedef = minetest.registered_items[name]
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if nodedef and nodedef.walkable then return false end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
thing = ray:next()
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mobs:line_of_sight(entity, pos1, pos2) -- [deprecated]
|
|
|
|
return entity:line_of_sight(pos1, pos2)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- if mob not flying in medium it's suppose to, try and find some to return to
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
function mob_class:attempt_flight_correction(override)
|
|
|
|
|
|
|
|
if self:flight_check() and override ~= true then return true end
|
|
|
|
|
2021-01-23 12:24:04 +01:00
|
|
|
local pos = self.object:get_pos() ; if not pos then return true end
|
2024-12-19 12:55:40 +01:00
|
|
|
local flyable_nodes = minetest.find_nodes_in_area(
|
|
|
|
{x = pos.x - 1, y = pos.y - 1, z = pos.z - 1},
|
|
|
|
{x = pos.x + 1, y = pos.y + 1, z = pos.z + 1}, self.fly_in)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if #flyable_nodes == 0 then return false end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local escape_target = flyable_nodes[random(#flyable_nodes)]
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- stop swimming mobs moving above water surface
|
|
|
|
if escape_target.y > pos.y and #minetest.find_nodes_in_area(
|
|
|
|
{x = escape_target.x, y = escape_target.y + 1, z = escape_target.z},
|
|
|
|
{x = escape_target.x, y = escape_target.y + 1, z = escape_target.z},
|
|
|
|
self.fly_in) == 0 then
|
|
|
|
escape_target.y = pos.y
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
local escape_direction = vdirection(pos, escape_target)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:set_velocity(vmultiply(escape_direction, 1))
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- are we flying in what we are suppose to? (taikedz)
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:flight_check()
|
|
|
|
|
|
|
|
local def = minetest.registered_nodes[self.standing_in]
|
|
|
|
|
|
|
|
if not def then return false end
|
|
|
|
|
|
|
|
-- are we standing inside what we should be to fly/swim ?
|
2024-12-19 12:55:40 +01:00
|
|
|
if check_for(self.standing_in, self.fly_in) then return true end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- stops mobs getting stuck inside stairs and plantlike nodes
|
2024-12-19 12:55:40 +01:00
|
|
|
if def.drawtype ~= "airlike" and def.drawtype ~= "liquid"
|
2020-10-26 17:38:53 +01:00
|
|
|
and def.drawtype ~= "flowingliquid" then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- turn mob to face position
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
function mob_class:yaw_to_pos(target, rot)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
rot = rot or 0
|
|
|
|
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
local vec = {x = target.x - pos.x, z = target.z - pos.z}
|
|
|
|
local yaw = (atan(vec.z / vec.x) + rot + pi / 2) - self.rotate
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if target.x > pos.x then yaw = yaw + pi end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
return self:set_yaw(yaw, rot)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mobs:yaw_to_pos(self, target, rot) -- [deprecated]
|
|
|
|
return self:yaw_to_pos(target, rot)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
-- if stay near set then periodically check for nodes and turn towards them
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:do_stay_near()
|
|
|
|
|
|
|
|
if not self.stay_near then return false end
|
|
|
|
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
local searchnodes = self.stay_near[1]
|
|
|
|
local chance = self.stay_near[2] or 10
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not pos or random(chance) > 1 then return false end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if type(searchnodes) == "string" then searchnodes = {self.stay_near[1]} end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local r = self.view_range
|
|
|
|
local nearby_nodes = minetest.find_nodes_in_area(
|
2024-12-19 12:55:40 +01:00
|
|
|
{x = pos.x - r, y = pos.y - 1, z = pos.z - r},
|
|
|
|
{x = pos.x + r, y = pos.y + 1, z = pos.z + r}, searchnodes)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if #nearby_nodes < 1 then return false end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:yaw_to_pos(nearby_nodes[random(#nearby_nodes)])
|
2020-10-26 17:38:53 +01:00
|
|
|
self:set_animation("walk")
|
|
|
|
self:set_velocity(self.walk_velocity)
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- custom particle effects
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
local function effect(pos, amount, texture, min_size, max_size, radius, grav, glow, fall)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
radius = radius or 2
|
2024-12-19 12:55:40 +01:00
|
|
|
grav = grav or -10
|
|
|
|
fall = fall == true and 0 or fall == false and radius or -radius
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
minetest.add_particlespawner({
|
|
|
|
amount = amount,
|
|
|
|
time = 0.25,
|
2024-12-19 12:55:40 +01:00
|
|
|
minpos = pos, maxpos = pos,
|
2020-10-26 17:38:53 +01:00
|
|
|
minvel = {x = -radius, y = fall, z = -radius},
|
|
|
|
maxvel = {x = radius, y = radius, z = radius},
|
2024-12-19 12:55:40 +01:00
|
|
|
minacc = {x = 0, y = grav, z = 0},
|
|
|
|
maxacc = {x = 0, y = grav, z = 0},
|
|
|
|
minexptime = 0.1, maxexptime = 1,
|
|
|
|
minsize = min_size or 0.5, maxsize = max_size or 1,
|
2020-10-26 17:38:53 +01:00
|
|
|
texture = texture,
|
2024-12-19 12:55:40 +01:00
|
|
|
glow = glow or 0
|
2020-10-26 17:38:53 +01:00
|
|
|
})
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mobs:effect(pos, amount, texture, min_size, max_size, radius, grav, glow, fall)
|
|
|
|
effect(pos, amount, texture, min_size, max_size, radius, grav, glow, fall)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- Thanks Wuzzy for the editable settings
|
2021-08-18 21:42:51 +02:00
|
|
|
|
|
|
|
local HORNY_TIME = 30
|
|
|
|
local HORNY_AGAIN_TIME = 60 * 5 -- 5 minutes
|
|
|
|
local CHILD_GROW_TIME = 60 * 20 -- 20 minutes
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- update nametag and infotext
|
2021-08-18 21:42:51 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mob_class:update_tag(newname)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local col
|
|
|
|
local prop = self.object:get_properties()
|
|
|
|
local qua = prop.hp_max / 6
|
|
|
|
local old_nametag = prop.nametag
|
|
|
|
local old_nametag_color = self.nametag_col
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- backwards compatibility
|
|
|
|
if self.nametag and self.nametag ~= "" then
|
|
|
|
newname = self.nametag
|
|
|
|
self.nametag = nil
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if newname or (self._nametag and self._nametag ~= "") then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self._nametag = newname or self._nametag -- adopt new name if one found
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- choose tag colour depending on mob health
|
|
|
|
if self.health <= qua then
|
|
|
|
self.nametag_col = "#FF0000"
|
|
|
|
elseif self.health <= (qua * 2) then
|
|
|
|
self.nametag_col = "#FF7A00"
|
|
|
|
elseif self.health <= (qua * 3) then
|
|
|
|
self.nametag_col = "#FFB500"
|
|
|
|
elseif self.health <= (qua * 4) then
|
|
|
|
self.nametag_col = "#FFFF00"
|
|
|
|
elseif self.health <= (qua * 5) then
|
|
|
|
self.nametag_col = "#B4FF00"
|
|
|
|
elseif self.health > (qua * 5) then
|
|
|
|
self.nametag_col = "#00FF00"
|
|
|
|
end
|
2021-08-18 21:42:51 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self._nametag ~= old_nametag or self.nametag_col ~= old_nametag_color then
|
2021-08-18 21:42:51 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:set_properties({
|
|
|
|
nametag = self._nametag, nametag_color = self.nametag_col})
|
|
|
|
end
|
|
|
|
end
|
2021-08-18 21:42:51 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local text = ""
|
2021-08-18 21:42:51 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.horny then
|
|
|
|
text = "\nLoving: " .. (self.hornytimer - (HORNY_TIME + HORNY_AGAIN_TIME))
|
|
|
|
elseif self.child then
|
2021-08-18 21:42:51 +02:00
|
|
|
text = "\nGrowing: " .. (self.hornytimer - CHILD_GROW_TIME)
|
2024-12-19 12:55:40 +01:00
|
|
|
elseif self._tame_countdown then
|
|
|
|
text = "\nTaming: " .. self._tame_countdown
|
2021-08-18 21:42:51 +02:00
|
|
|
elseif self._breed_countdown then
|
|
|
|
text = "\nBreeding: " .. self._breed_countdown
|
2024-12-19 12:55:40 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
if self.protected then
|
2021-08-18 21:42:51 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.protected == 2 then
|
|
|
|
text = text .. "\nProtection: Level 2"
|
|
|
|
else
|
|
|
|
text = text .. "\nProtection: Level 1"
|
|
|
|
end
|
2021-08-18 21:42:51 +02:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.infotext = "Entity: " .. self.name .. " | Type: " .. self.type
|
|
|
|
.. ("\nHealth: " .. self.health .. " / " .. prop.hp_max)
|
|
|
|
.. (self.owner == "" and "" or "\nOwner: " .. self.owner) .. text
|
2021-06-27 17:39:50 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- set infotext changes
|
|
|
|
if self.infotext ~= prop.infotext then
|
|
|
|
self.object:set_properties({infotext = self.infotext})
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- drop items
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:item_drop()
|
|
|
|
|
|
|
|
-- no drops if disabled by setting or mob is child
|
|
|
|
if not mobs_drop_items or self.child then return end
|
|
|
|
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
|
|
|
|
-- check for drops function
|
2024-12-19 12:55:40 +01:00
|
|
|
self.drops = type(self.drops) == "function" and self.drops(pos) or self.drops
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- check for nil or no drops
|
2024-12-19 12:55:40 +01:00
|
|
|
if not self.drops or #self.drops == 0 then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- was mob killed by player?
|
2024-12-19 12:55:40 +01:00
|
|
|
local death_by_player = self.cause_of_death and self.cause_of_death.puncher
|
|
|
|
and is_player(self.cause_of_death.puncher)
|
|
|
|
|
|
|
|
-- check for tool 'looting_level' under tool_capabilities as default, or use
|
|
|
|
-- meta string 'looting_level' if found (max looting level is 3).
|
|
|
|
local looting = 0
|
|
|
|
|
|
|
|
if death_by_player then
|
|
|
|
|
|
|
|
local wield_stack = self.cause_of_death.puncher:get_wielded_item()
|
|
|
|
local wield_name = wield_stack:get_name()
|
|
|
|
local wield_stack_meta = wield_stack:get_meta()
|
|
|
|
local item_def = minetest.registered_items[wield_name]
|
|
|
|
local item_looting = item_def and item_def.tool_capabilities and
|
|
|
|
item_def.tool_capabilities.looting_level or 0
|
|
|
|
|
|
|
|
looting = tonumber(wield_stack_meta:get_string("looting_level")) or item_looting
|
|
|
|
looting = min(looting, 3)
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local obj, item, num
|
|
|
|
|
|
|
|
for n = 1, #self.drops do
|
|
|
|
|
|
|
|
if random(self.drops[n].chance) == 1 then
|
|
|
|
|
|
|
|
num = random(self.drops[n].min or 0, self.drops[n].max or 1)
|
|
|
|
item = self.drops[n].name
|
|
|
|
|
|
|
|
-- cook items on a hot death
|
|
|
|
if self.cause_of_death.hot then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local output = minetest.get_craft_result(
|
|
|
|
{method = "cooking", width = 1, items = {item}})
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if output and output.item and not output.item:is_empty() then
|
|
|
|
item = output.item:get_name()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- only drop rare items (drops.min = 0) if killed by player
|
2021-06-27 17:39:50 +02:00
|
|
|
if death_by_player or self.drops[n].min ~= 0 then
|
2024-12-19 12:55:40 +01:00
|
|
|
obj = minetest.add_item(pos, ItemStack(item .. " " .. (num + looting)))
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
if obj and obj:get_luaentity() then
|
|
|
|
|
|
|
|
obj:set_velocity({
|
2024-12-19 12:55:40 +01:00
|
|
|
x = random(-10, 10) / 9, y = 6, z = random(-10, 10) / 9})
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
elseif obj then
|
|
|
|
obj:remove() -- item does not exist
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
self.drops = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
-- remove mob and descrease counter
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local function remove_mob(self, decrease)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:remove()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if decrease and active_limit and active_limit > 1 then
|
2020-10-26 17:38:53 +01:00
|
|
|
active_mobs = active_mobs - 1
|
|
|
|
--print("-- active mobs: " .. active_mobs .. " / " .. active_limit)
|
2024-12-19 12:55:40 +01:00
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
function mobs:remove(self, decrease)
|
|
|
|
remove_mob(self, decrease)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- check if mob is dead or only hurt
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:check_for_death(cmi_cause)
|
|
|
|
|
2020-12-07 17:01:24 +01:00
|
|
|
-- We dead already
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.state == "die" then return true end
|
2020-12-07 17:01:24 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- has health actually changed?
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.health == self.old_health and self.health > 0 then return false end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local damaged = self.health < self.old_health
|
2024-12-19 12:55:40 +01:00
|
|
|
local prop = self.object:get_properties()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.old_health = self.health
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if use_vh1 then VH1.update_bar(self.object, self.health) end
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
if self.health > 0 then
|
|
|
|
|
|
|
|
-- only play hurt sound if damaged
|
2024-12-19 12:55:40 +01:00
|
|
|
if damaged then self:mob_sound(self.sounds.damage) end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- make sure health isn't higher than max
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.health > prop.hp_max then self.health = prop.hp_max end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:update_tag()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
self.cause_of_death = cmi_cause
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- drop items and play death sound
|
2020-10-26 17:38:53 +01:00
|
|
|
self:item_drop()
|
|
|
|
self:mob_sound(self.sounds.death)
|
|
|
|
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- execute official engine on_death function if found
|
|
|
|
if self.on_death then
|
|
|
|
|
|
|
|
-- only return killer if punched by player
|
|
|
|
if cmi_cause.type == "punch" and is_player(cmi_cause.puncher) then
|
|
|
|
cmi_cause = cmi_cause.puncher
|
|
|
|
else
|
|
|
|
cmi_cause = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
self:on_death(cmi_cause)
|
|
|
|
|
|
|
|
remove_mob(self, true)
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- execute custom death function
|
2020-11-15 20:25:38 +01:00
|
|
|
if pos and self.on_die then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self:on_die(pos)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if use_cmi then cmi.notify_die(self.object, cmi_cause) end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
remove_mob(self, true)
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- reset vars and set state
|
|
|
|
self.attack = nil
|
|
|
|
self.following = nil
|
|
|
|
self.v_start = false
|
|
|
|
self.timer = 0
|
|
|
|
self.blinktimer = 0
|
|
|
|
self.passive = true
|
|
|
|
self.state = "die"
|
|
|
|
self.fly = false
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- check for custom death function and die animation
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.animation and self.animation.die_start and self.animation.die_end then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local frames = self.animation.die_end - self.animation.die_start
|
|
|
|
local speed = self.animation.die_speed or 15
|
2020-11-15 20:25:38 +01:00
|
|
|
local length = max((frames / speed), 0)
|
2020-12-07 17:01:24 +01:00
|
|
|
local rot = self.animation.die_rotate and 5
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2020-12-07 17:01:24 +01:00
|
|
|
self.object:set_properties({
|
|
|
|
pointable = false, collide_with_objects = false,
|
2021-01-10 18:57:32 +01:00
|
|
|
automatic_rotate = rot, static_save = false
|
2020-12-07 17:01:24 +01:00
|
|
|
})
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
self:set_velocity(0)
|
|
|
|
self:set_animation("die")
|
|
|
|
|
|
|
|
minetest.after(length, function(self)
|
|
|
|
|
2020-11-15 20:25:38 +01:00
|
|
|
if self.object:get_luaentity() then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if use_cmi then cmi.notify_die(self.object, cmi_cause) end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2020-11-15 20:25:38 +01:00
|
|
|
remove_mob(self, true)
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
end, self)
|
2020-11-15 20:25:38 +01:00
|
|
|
|
|
|
|
return true
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
elseif pos then -- otherwise remove mob and show particle effect
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if use_cmi then cmi.notify_die(self.object, cmi_cause) end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
remove_mob(self, true)
|
|
|
|
|
2020-11-15 20:25:38 +01:00
|
|
|
effect(pos, 20, "tnt_smoke.png")
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- get node but use fallback for nil or unknown
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local function node_ok(pos, fallback)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local node = minetest.get_node_or_nil(pos)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if node and minetest.registered_nodes[node.name] then return node end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
return minetest.registered_nodes[(fallback or mobs.fallback_node)]
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mobs:node_ok(pos, fallback)
|
|
|
|
return node_ok(pos, fallback)
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- Returns true if node can deal damage to self
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local function is_node_dangerous(self, nodename)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local def = minetest.registered_nodes[nodename]
|
2021-04-05 14:46:21 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if (self.water_damage and def.groups.water)
|
|
|
|
or (self.lava_damage and def.groups.lava)
|
|
|
|
or (self.fire_damage and def.groups.fire) then return true end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.node_damage and def.damage_per_second > 0 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- check for node immunity or special damage
|
|
|
|
local damage = def.damage_per_second
|
|
|
|
|
|
|
|
for n = 1, #self.immune_to do
|
|
|
|
|
|
|
|
if self.immune_to[n][1] == nodename then
|
|
|
|
damage = self.immune_to[n][2] or 0 ; break
|
|
|
|
end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if damage > 0 then return true end
|
|
|
|
end
|
2022-01-16 20:12:39 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mobs:is_node_dangerous(mob_object, nodename)
|
|
|
|
return is_node_dangerous(mob_object, nodename)
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- is mob facing a cliff
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:is_at_cliff()
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.driver or self.fear_height == 0 then -- 0 for no falling protection!
|
2020-10-26 17:38:53 +01:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- get yaw but if nil returned object no longer exists
|
2024-12-19 12:55:40 +01:00
|
|
|
local yaw = self.object:get_yaw() ; if not yaw then return false end
|
|
|
|
local prop = self.object:get_properties()
|
|
|
|
local dir_x = -sin(yaw) * (prop.collisionbox[4] + 0.5)
|
|
|
|
local dir_z = cos(yaw) * (prop.collisionbox[4] + 0.5)
|
2020-10-26 17:38:53 +01:00
|
|
|
local pos = self.object:get_pos()
|
2024-12-19 12:55:40 +01:00
|
|
|
local ypos = pos.y + prop.collisionbox[2] -- just above floor
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local free_fall, blocker = minetest.line_of_sight(
|
2024-12-19 12:55:40 +01:00
|
|
|
{x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
|
|
|
|
{x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z})
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- check for straight drop
|
2024-12-19 12:55:40 +01:00
|
|
|
if free_fall then return true end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local bnode = node_ok(blocker, "air")
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- will we drop onto dangerous node?
|
2024-12-19 12:55:40 +01:00
|
|
|
if is_node_dangerous(self, bnode.name) then return true end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local def = minetest.registered_nodes[bnode.name]
|
|
|
|
|
|
|
|
return (not def and def.walkable)
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- check for nodes or groups inside mob collision area
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mob_class:is_inside(itemtable)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local cb = self.object:get_properties().collisionbox
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
local nn = minetest.find_nodes_in_area(
|
|
|
|
vector.offset(pos, cb[1], cb[2], cb[3]),
|
|
|
|
vector.offset(pos, cb[4], cb[5], cb[6]), itemtable)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if nn and #nn > 0 then return true end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- environmental damage (water, lava, fire, light etc.)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mob_class:do_env_damage()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local pos = self.object:get_pos() ; if not pos then return end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:update_tag()
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
self.time_of_day = minetest.get_timeofday()
|
|
|
|
|
|
|
|
-- halt mob if standing inside ignore node
|
|
|
|
if self.standing_in == "ignore" then
|
|
|
|
|
|
|
|
self.object:set_velocity({x = 0, y = 0, z = 0})
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local prop = self.object:get_properties()
|
|
|
|
local py = {x = pos.x, y = pos.y + prop.collisionbox[5], z = pos.z}
|
2020-10-26 17:38:53 +01:00
|
|
|
local nodef = minetest.registered_nodes[self.standing_in]
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- water damage
|
2021-06-27 17:39:50 +02:00
|
|
|
if self.water_damage ~= 0 and nodef.groups.water then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-04-05 14:46:21 +02:00
|
|
|
self.health = self.health - self.water_damage
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-05-16 11:33:49 +02:00
|
|
|
effect(py, 5, "bubble.png", nil, nil, 1, nil)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-04-05 14:46:21 +02:00
|
|
|
if self:check_for_death({type = "environment",
|
2024-12-19 12:55:40 +01:00
|
|
|
pos = pos, node = self.standing_in}) then return true end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-04-05 14:46:21 +02:00
|
|
|
-- lava damage
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.lava_damage ~= 0 and nodef.groups.lava then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-04-05 14:46:21 +02:00
|
|
|
self.health = self.health - self.lava_damage
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-05-16 11:33:49 +02:00
|
|
|
effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-04-05 14:46:21 +02:00
|
|
|
if self:check_for_death({type = "environment", pos = pos,
|
2024-12-19 12:55:40 +01:00
|
|
|
node = self.standing_in, hot = true}) then return true end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-04-05 14:46:21 +02:00
|
|
|
-- fire damage
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.fire_damage ~= 0 and nodef.groups.fire then
|
2021-04-05 14:46:21 +02:00
|
|
|
|
|
|
|
self.health = self.health - self.fire_damage
|
|
|
|
|
2021-05-16 11:33:49 +02:00
|
|
|
effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
|
2021-04-05 14:46:21 +02:00
|
|
|
|
|
|
|
if self:check_for_death({type = "environment", pos = pos,
|
2024-12-19 12:55:40 +01:00
|
|
|
node = self.standing_in, hot = true}) then return true end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-04-05 14:46:21 +02:00
|
|
|
-- damage_per_second node check (not fire and lava)
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.node_damage and nodef.damage_per_second and nodef.damage_per_second ~= 0
|
2021-05-16 11:33:49 +02:00
|
|
|
and nodef.groups.lava == nil and nodef.groups.fire == nil then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local damage = nodef.damage_per_second
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- check for node immunity or special damage
|
|
|
|
for n = 1, #self.immune_to do
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.immune_to[n][1] == self.standing_in then
|
|
|
|
damage = self.immune_to[n][2] or 0 ; break
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
self.health = self.health - damage
|
|
|
|
|
|
|
|
if damage > 0 then effect(py, 5, "tnt_smoke.png") end
|
|
|
|
|
|
|
|
if self:check_for_death({type = "environment",
|
|
|
|
pos = pos, node = self.standing_in}) then return true end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2020-12-07 17:01:24 +01:00
|
|
|
-- air damage
|
|
|
|
if self.air_damage ~= 0 and self.standing_in == "air" then
|
|
|
|
|
|
|
|
self.health = self.health - self.air_damage
|
|
|
|
|
2021-05-16 11:33:49 +02:00
|
|
|
effect(py, 3, "bubble.png", 1, 1, 1, 0.2)
|
2020-12-07 17:01:24 +01:00
|
|
|
|
|
|
|
if self:check_for_death({type = "environment",
|
2024-12-19 12:55:40 +01:00
|
|
|
pos = pos, node = self.standing_in}) then return true end
|
2020-12-07 17:01:24 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- is mob light sensitive, or scared of the dark :P
|
2020-10-26 17:38:53 +01:00
|
|
|
if self.light_damage ~= 0 then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local light
|
|
|
|
|
|
|
|
-- if max set to 16 then only kill mob with natural sunlight
|
|
|
|
if self.light_damage_max == 16 then
|
|
|
|
light = minetest.get_natural_light(pos) or 0
|
|
|
|
else
|
|
|
|
light = minetest.get_node_light(pos) or 0
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if light >= self.light_damage_min
|
|
|
|
and light <= self.light_damage_max then
|
|
|
|
|
|
|
|
self.health = self.health - self.light_damage
|
|
|
|
|
2021-05-16 11:33:49 +02:00
|
|
|
effect(py, 5, "tnt_smoke.png")
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self:check_for_death({type = "light"}) then return true end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
--- suffocation inside solid node
|
|
|
|
if (self.suffocation and self.suffocation ~= 0)
|
2024-12-19 12:55:40 +01:00
|
|
|
and (nodef.walkable == nil or nodef.walkable)
|
2020-10-26 17:38:53 +01:00
|
|
|
and (nodef.collision_box == nil or nodef.collision_box.type == "regular")
|
|
|
|
and (nodef.node_box == nil or nodef.node_box.type == "regular")
|
|
|
|
and (nodef.groups.disable_suffocation ~= 1) then
|
|
|
|
|
|
|
|
local damage
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if type(self.suffocation) == "boolean" and self.suffocation then
|
2020-10-26 17:38:53 +01:00
|
|
|
damage = 2
|
|
|
|
else
|
2024-12-19 12:55:40 +01:00
|
|
|
damage = self.suffocation
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
self.health = self.health - damage
|
|
|
|
|
|
|
|
if self:check_for_death({type = "suffocation",
|
2024-12-19 12:55:40 +01:00
|
|
|
pos = pos, node = self.standing_in}) then return true end
|
|
|
|
|
|
|
|
-- try to jump out of block
|
|
|
|
self.object:set_velocity({x = 0, y = self.jump_height, z = 0})
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
return self:check_for_death({type = "unknown"})
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- jump if facing a solid node that isn't a fences or gate
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
function mob_class:do_jump()
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local vel = self.object:get_velocity() ; if not vel then return false end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- don't jump if ordered to stand or already in mid-air
|
|
|
|
if self.state == "stand" or vel.y ~= 0 then return false end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- we can only jump if standing on solid node
|
|
|
|
if minetest.registered_nodes[self.standing_on].walkable == false then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- is there anything stopping us from jumping up onto a block?
|
|
|
|
local blocked = minetest.registered_nodes[self.looking_above].walkable
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- if mob can leap then remove blockages and let them try
|
|
|
|
if self.can_leap then blocked = false ; self.facing_fence = false end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- jump if possible
|
|
|
|
if self.jump and self.jump_height > 0 and not self.fly and not self.child
|
|
|
|
and self.order ~= "stand"
|
|
|
|
and (self.walk_chance == 0 or minetest.registered_items[self.looking_at].walkable)
|
|
|
|
and not blocked
|
|
|
|
and not self.facing_fence
|
|
|
|
and self.looking_at ~= mobs.node_snow then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
vel.y = self.jump_height
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:set_animation("jump") -- only if defined
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:set_velocity(vel)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
-- when in air move forward
|
2024-12-19 12:55:40 +01:00
|
|
|
minetest.after(0.3, function(self, vel)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
if self.object:get_luaentity() then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:set_acceleration({x = vel.x * 2, y = 0, z = vel.z * 2})
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
2024-12-19 12:55:40 +01:00
|
|
|
end, self, vel)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
if self:get_velocity() > 0 then
|
|
|
|
self:mob_sound(self.sounds.jump)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
2021-06-27 17:39:50 +02:00
|
|
|
|
|
|
|
self.jump_count = 0
|
|
|
|
|
|
|
|
return true
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
-- if blocked for 3 counts then turn
|
|
|
|
if not self.following and (self.facing_fence or blocked) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.jump_count = (self.jump_count or 0) + 1
|
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
if self.jump_count > 2 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local yaw = self.object:get_yaw() or 0
|
|
|
|
local turn = random(0, 2) + 1.35
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:set_yaw(yaw + turn, 12)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.jump_count = 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- blast damage to entities nearby (modified from TNT mod)
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
local function entity_physics(pos, radius)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
radius = radius * 2
|
|
|
|
|
|
|
|
local objs = minetest.get_objects_inside_radius(pos, radius)
|
|
|
|
local obj_pos, dist
|
|
|
|
|
|
|
|
for n = 1, #objs do
|
|
|
|
|
|
|
|
obj_pos = objs[n]:get_pos()
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
dist = max(1, get_distance(pos, obj_pos))
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local damage = floor((4 / dist) * radius)
|
|
|
|
|
|
|
|
-- punches work on entities AND players
|
2024-12-19 12:55:40 +01:00
|
|
|
objs[n]:punch(objs[n], 1.0,
|
|
|
|
{full_punch_interval = 1.0, damage_groups = {fleshy = damage}}, pos)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-03-17 20:30:38 +01:00
|
|
|
-- can mob see player
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local function is_invisible(self, player_name)
|
|
|
|
|
|
|
|
if use_invisibility and not self.ignore_invisibility
|
|
|
|
and invisibility.is_visible and not invisibility.is_visible(player_name) then
|
2021-03-17 20:30:38 +01:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mobs:is_invisible(self, player_name)
|
|
|
|
return is_invisible(self, player_name)
|
|
|
|
end
|
2021-03-17 20:30:38 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- should mob follow what I'm holding ?
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:follow_holding(clicker)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if is_invisible(self, clicker:get_player_name()) then return false end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local item = clicker:get_wielded_item()
|
|
|
|
|
|
|
|
-- are we holding an item mob can follow ?
|
2024-12-19 12:55:40 +01:00
|
|
|
if check_for(item:get_name(), self.follow) then return true end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- find two animals of same type and breed if nearby and horny
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:breed()
|
|
|
|
|
2020-12-07 17:01:24 +01:00
|
|
|
-- child takes a long time before growing into adult
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.child then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.hornytimer = self.hornytimer + 1
|
|
|
|
|
2020-12-07 17:01:24 +01:00
|
|
|
if self.hornytimer > CHILD_GROW_TIME then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.child = false
|
|
|
|
self.hornytimer = 0
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- replace child texture with adult one
|
|
|
|
if self.mommy_tex then
|
|
|
|
self.base_texture = self.mommy_tex
|
|
|
|
self.mommy_tex = nil
|
|
|
|
end
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
self.object:set_properties({
|
|
|
|
textures = self.base_texture,
|
|
|
|
mesh = self.base_mesh,
|
|
|
|
visual_size = self.base_size,
|
|
|
|
collisionbox = self.base_colbox,
|
|
|
|
selectionbox = self.base_selbox
|
|
|
|
})
|
|
|
|
|
|
|
|
-- custom function when child grows up
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.on_grown then self.on_grown(self)
|
2020-10-26 17:38:53 +01:00
|
|
|
else
|
2021-02-08 17:57:40 +01:00
|
|
|
local pos = self.object:get_pos() ; if not pos then return end
|
2024-12-19 12:55:40 +01:00
|
|
|
local prop = self.object:get_properties()
|
2021-06-27 17:39:50 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
pos.y = pos.y + (prop.collisionbox[2] * -1) + 0.1
|
2021-06-27 17:39:50 +02:00
|
|
|
|
2021-02-08 17:57:40 +01:00
|
|
|
self.object:set_pos(pos)
|
2021-06-27 17:39:50 +02:00
|
|
|
|
|
|
|
-- jump slightly when fully grown so as not to fall into ground
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:set_velocity({x = 0, y = 2, z = 0 })
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2020-12-07 17:01:24 +01:00
|
|
|
-- horny animal can mate for HORNY_TIME seconds,
|
|
|
|
-- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.horny and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.hornytimer = self.hornytimer + 1
|
|
|
|
|
2020-12-07 17:01:24 +01:00
|
|
|
if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
|
2020-10-26 17:38:53 +01:00
|
|
|
self.hornytimer = 0
|
|
|
|
self.horny = false
|
|
|
|
end
|
2021-08-18 21:42:51 +02:00
|
|
|
|
|
|
|
self:update_tag()
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- find another same animal who is also horny and mate if nearby
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.horny and self.hornytimer <= HORNY_TIME then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local pos = self.object:get_pos()
|
2024-12-19 12:55:40 +01:00
|
|
|
local prop = self.object:get_properties().collisionbox
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
effect({x = pos.x, y = pos.y + prop[5], z = pos.z}, 8,
|
|
|
|
"heart.png", 3, 4, 1, 0.1, 1, true)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local objs = minetest.get_objects_inside_radius(pos, 3)
|
|
|
|
local ent
|
|
|
|
|
|
|
|
for n = 1, #objs do
|
|
|
|
|
|
|
|
ent = objs[n]:get_luaentity()
|
|
|
|
|
|
|
|
-- check for same animal with different colour
|
|
|
|
local canmate = false
|
|
|
|
|
|
|
|
if ent then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if ent.name == self.name then canmate = true
|
2020-10-26 17:38:53 +01:00
|
|
|
else
|
|
|
|
local entname = ent.name:split(":")
|
|
|
|
local selfname = self.name:split(":")
|
|
|
|
|
|
|
|
if entname[1] == selfname[1] then
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
entname = entname[2]:split("_")
|
|
|
|
selfname = selfname[2]:split("_")
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if entname[1] == selfname[1] then canmate = true end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-12-07 17:01:24 +01:00
|
|
|
-- found another similar horny animal that isn't self?
|
2024-12-19 12:55:40 +01:00
|
|
|
if ent and ent.object ~= self.object and canmate
|
|
|
|
and ent.horny and ent.hornytimer <= HORNY_TIME then
|
2020-12-07 17:01:24 +01:00
|
|
|
|
|
|
|
local pos2 = ent.object:get_pos()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2020-12-07 17:01:24 +01:00
|
|
|
-- Have mobs face one another
|
2024-12-19 12:55:40 +01:00
|
|
|
self:yaw_to_pos(pos2)
|
|
|
|
ent:yaw_to_pos(self.object:get_pos())
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2020-12-07 17:01:24 +01:00
|
|
|
self.hornytimer = HORNY_TIME + 1
|
|
|
|
ent.hornytimer = HORNY_TIME + 1
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-08-18 21:42:51 +02:00
|
|
|
self:update_tag()
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- have we reached active mob limit
|
2024-12-19 12:55:40 +01:00
|
|
|
if at_limit() then
|
|
|
|
|
|
|
|
minetest.chat_send_player(self.owner, S("Active Mob Limit Reached!")
|
|
|
|
.. " (" .. active_mobs .. " / " .. active_limit .. ")")
|
2020-10-26 17:38:53 +01:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- spawn baby
|
|
|
|
minetest.after(5, function(self, ent)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not self.object:get_luaentity() then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- custom breed function
|
|
|
|
if self.on_breed then
|
|
|
|
|
|
|
|
-- when false skip going any further
|
2024-12-19 12:55:40 +01:00
|
|
|
if self:on_breed(ent) == false then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- add baby
|
|
|
|
local ent2 = mobs:add_mob(pos, {
|
|
|
|
name = self.name,
|
|
|
|
child = true,
|
|
|
|
owner = self.owner,
|
|
|
|
ignore_count = true
|
|
|
|
})
|
|
|
|
|
|
|
|
-- set baby textures
|
|
|
|
if ent2 then
|
2021-02-08 17:57:40 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local textures = self.base_texture
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- using specific child texture (if found)
|
|
|
|
if self.child_texture then
|
|
|
|
textures = self.child_texture[1]
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
ent2.mommy_tex = self.base_texture -- when grown
|
|
|
|
ent2.object:set_properties({textures = textures})
|
|
|
|
ent2.base_texture = textures
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
end, self, ent)
|
|
|
|
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- find and replace what mob is looking for (grass, wheat etc.)
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:replace(pos)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local vel = self.object:get_velocity() ; if not vel then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not mobs_griefing or not self.replace_rate or not self.replace_what
|
|
|
|
or self.child or vel.y ~= 0 or random(self.replace_rate) > 1 then
|
2020-10-26 17:38:53 +01:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local what, with, y_offset, reach
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if type(self.replace_what[1]) == "table" then
|
|
|
|
|
|
|
|
local num = random(#self.replace_what)
|
|
|
|
|
|
|
|
what = self.replace_what[num][1] or ""
|
|
|
|
with = self.replace_what[num][2] or ""
|
|
|
|
y_offset = self.replace_what[num][3] or 0
|
2024-12-19 12:55:40 +01:00
|
|
|
reach = self.replace_what[num][4] or 0
|
2020-10-26 17:38:53 +01:00
|
|
|
else
|
|
|
|
what = self.replace_what
|
|
|
|
with = self.replace_with or ""
|
|
|
|
y_offset = self.replace_offset or 0
|
2024-12-19 12:55:40 +01:00
|
|
|
reach = 0
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
pos.y = pos.y + y_offset
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local found = minetest.find_nodes_in_area(
|
|
|
|
{x = pos.x - reach, y = pos.y, z = pos.z - reach},
|
|
|
|
{x = pos.x + reach, y = pos.y, z = pos.z + reach}, what)
|
|
|
|
|
|
|
|
if #found > 0 then
|
|
|
|
|
|
|
|
pos = found[random(#found)]
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- print("replace node = ".. minetest.get_node(pos).name, pos.y)
|
|
|
|
|
|
|
|
if self.on_replace then
|
|
|
|
|
|
|
|
local oldnode = what or ""
|
|
|
|
local newnode = with
|
|
|
|
|
|
|
|
-- pass actual node name when using table or groups
|
2024-12-19 12:55:40 +01:00
|
|
|
if type(oldnode) == "table" or oldnode:find("group:") then
|
2020-10-26 17:38:53 +01:00
|
|
|
oldnode = minetest.get_node(pos).name
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self:on_replace(pos, oldnode, newnode) == false then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
minetest.set_node(pos, {name = with})
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
self:mob_sound(self.sounds.replace)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- check if daytime and also if mob is docile during daylight hours
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mob_class:day_docile()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.docile_by_day
|
|
|
|
and self.time_of_day > 0.2 and self.time_of_day < 0.8 then return true end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- are we able to dig this node and add drops?
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local function can_dig_drop(pos)
|
2021-06-27 17:39:50 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if minetest.is_protected(pos, "") then return false end
|
2021-06-27 17:39:50 +02:00
|
|
|
|
|
|
|
local node = node_ok(pos, "air").name
|
|
|
|
local ndef = minetest.registered_nodes[node]
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if node ~= "ignore" and ndef and ndef.drawtype ~= "airlike"
|
|
|
|
and not ndef.groups.level and not ndef.groups.unbreakable
|
2021-06-27 17:39:50 +02:00
|
|
|
and not ndef.groups.liquid then
|
|
|
|
|
|
|
|
local drops = minetest.get_node_drops(node)
|
|
|
|
|
|
|
|
for _, item in ipairs(drops) do
|
|
|
|
|
|
|
|
minetest.add_item({
|
|
|
|
x = pos.x - 0.5 + random(),
|
|
|
|
y = pos.y - 0.5 + random(),
|
|
|
|
z = pos.z - 0.5 + random()
|
|
|
|
}, item)
|
|
|
|
end
|
|
|
|
|
|
|
|
minetest.remove_node(pos)
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- pathfinder mod check and settings
|
2021-07-24 12:22:10 +02:00
|
|
|
|
|
|
|
local pathfinder_mod = minetest.get_modpath("pathfinder")
|
2024-12-19 12:55:40 +01:00
|
|
|
local los_switcher = false
|
|
|
|
local height_switcher = false
|
|
|
|
|
|
|
|
-- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:smart_mobs(s, p, dist, dtime)
|
|
|
|
|
|
|
|
local s1 = self.path.lastpos
|
|
|
|
local target_pos = p
|
|
|
|
|
|
|
|
-- is it becoming stuck?
|
|
|
|
if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
|
|
|
|
self.path.stuck_timer = self.path.stuck_timer + dtime
|
|
|
|
else
|
|
|
|
self.path.stuck_timer = 0
|
|
|
|
end
|
|
|
|
|
|
|
|
self.path.lastpos = {x = s.x, y = s.y, z = s.z}
|
|
|
|
|
|
|
|
local use_pathfind = false
|
|
|
|
local has_lineofsight = minetest.line_of_sight(
|
|
|
|
{x = s.x, y = (s.y) + .5, z = s.z},
|
|
|
|
{x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2)
|
|
|
|
|
|
|
|
-- im stuck, search for path
|
|
|
|
if not has_lineofsight then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if los_switcher then
|
2020-10-26 17:38:53 +01:00
|
|
|
use_pathfind = true
|
|
|
|
los_switcher = false
|
|
|
|
end -- cannot see target!
|
|
|
|
else
|
2024-12-19 12:55:40 +01:00
|
|
|
if not los_switcher then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
los_switcher = true
|
|
|
|
use_pathfind = false
|
|
|
|
|
|
|
|
minetest.after(1, function(self)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not self.object:get_luaentity() then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if has_lineofsight then self.path.following = false end
|
2020-10-26 17:38:53 +01:00
|
|
|
end, self)
|
|
|
|
end -- can see target!
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.path.stuck_timer > pathfinding_stuck_timeout and not self.path.following then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
use_pathfind = true
|
|
|
|
self.path.stuck_timer = 0
|
|
|
|
|
|
|
|
minetest.after(1, function(self)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not self.object:get_luaentity() then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if has_lineofsight then self.path.following = false end
|
2020-10-26 17:38:53 +01:00
|
|
|
end, self)
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.path.stuck_timer > pathfinding_stuck_path_timeout and self.path.following then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
use_pathfind = true
|
|
|
|
self.path.stuck_timer = 0
|
|
|
|
|
|
|
|
minetest.after(1, function(self)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not self.object:get_luaentity() then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if has_lineofsight then self.path.following = false end
|
2020-10-26 17:38:53 +01:00
|
|
|
end, self)
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local prop = self.object:get_properties()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if abs(s.y - target_pos.y) > prop.stepheight then
|
|
|
|
|
|
|
|
if height_switcher then use_pathfind = true ; height_switcher = false end
|
2020-10-26 17:38:53 +01:00
|
|
|
else
|
2024-12-19 12:55:40 +01:00
|
|
|
if not height_switcher then use_pathfind = false ; height_switcher = true end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- lets try find a path, first take care of positions
|
|
|
|
-- since pathfinder is very sensitive
|
|
|
|
if use_pathfind then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- round position to center of node to avoid getting stuck in walls
|
2020-10-26 17:38:53 +01:00
|
|
|
s.x = floor(s.x + 0.5)
|
|
|
|
s.z = floor(s.z + 0.5)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local ssight, sground = minetest.line_of_sight(s,
|
|
|
|
{x = s.x, y = s.y - 4, z = s.z}, 1)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- determine node above ground (adjust height for player models)
|
|
|
|
if not ssight then s.y = sground.y + 1 end
|
|
|
|
|
|
|
|
local p1 = self.attack and self.attack:get_pos()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not p1 then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
p1.x = floor(p1.x + 0.5) ; p1.y = floor(p1.y + 0.5) ; p1.z = floor(p1.z + 0.5)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local dropheight = pathfinding_max_drop
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if self.fear_height ~= 0 then dropheight = self.fear_height end
|
|
|
|
|
|
|
|
local jumpheight = 0
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.jump and self.jump_height >= pathfinding_max_jump then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
jumpheight = min(ceil(
|
|
|
|
self.jump_height / pathfinding_max_jump), pathfinding_max_jump)
|
|
|
|
|
|
|
|
elseif prop.stepheight > 0.5 then jumpheight = 1 end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-07-24 12:22:10 +02:00
|
|
|
if pathfinder_mod then
|
|
|
|
self.path.way = pathfinder.find_path(s, p1, self, dtime)
|
|
|
|
else
|
2024-12-19 12:55:40 +01:00
|
|
|
self.path.way = minetest.find_path(s, p1, pathfinding_searchdistance,
|
|
|
|
jumpheight, dropheight, pathfinding_algorithm)
|
2021-07-24 12:22:10 +02:00
|
|
|
end
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
--[[ show path using particles
|
2020-10-26 17:38:53 +01:00
|
|
|
if self.path.way and #self.path.way > 0 then
|
2021-07-24 12:22:10 +02:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
print("-- path length:" .. tonumber(#self.path.way))
|
2021-07-24 12:22:10 +02:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
for _,pos in pairs(self.path.way) do
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
minetest.add_particle({
|
2024-12-19 12:55:40 +01:00
|
|
|
pos = pos,
|
|
|
|
velocity = {x = 0, y = 0, z = 0},
|
|
|
|
acceleration = {x = 0, y = 0, z = 0},
|
|
|
|
expirationtime = 1,
|
|
|
|
size = 4,
|
|
|
|
collisiondetection = false,
|
|
|
|
vertical = false,
|
|
|
|
texture = "heart.png",
|
2020-10-26 17:38:53 +01:00
|
|
|
})
|
|
|
|
end
|
2024-12-19 12:55:40 +01:00
|
|
|
end]]
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.state = ""
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.attack then self:do_attack(self.attack) end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- no path found, try something else
|
|
|
|
if not self.path.way then
|
|
|
|
|
|
|
|
self.path.following = false
|
|
|
|
|
|
|
|
-- lets make way by digging/building if not accessible
|
|
|
|
if self.pathfinding == 2 and mobs_griefing then
|
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
-- is player more than 1 block higher than mob?
|
|
|
|
if p1.y > (s.y + 1) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- build upwards
|
|
|
|
if not minetest.is_protected(s, "") then
|
|
|
|
|
|
|
|
local ndef1 = minetest.registered_nodes[self.standing_in]
|
|
|
|
|
|
|
|
if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
|
2021-06-27 17:39:50 +02:00
|
|
|
minetest.set_node(s, {name = mobs.fallback_node})
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local sheight = ceil(prop.collisionbox[5]) + 1
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- assume mob is 2 blocks high so it digs above its head
|
|
|
|
s.y = s.y + sheight
|
|
|
|
|
|
|
|
-- remove one block above to make room to jump
|
2021-06-27 17:39:50 +02:00
|
|
|
can_dig_drop(s)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
s.y = s.y - sheight
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
self.object:set_pos({x = s.x, y = s.y + 2, z = s.z})
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
-- is player more than 1 block lower than mob
|
|
|
|
elseif p1.y < (s.y - 1) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
-- dig down
|
2024-12-19 12:55:40 +01:00
|
|
|
s.y = s.y - prop.collisionbox[4] - 0.2
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
can_dig_drop(s)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
else -- dig 2 blocks to make door toward player direction
|
|
|
|
|
|
|
|
local yaw1 = self.object:get_yaw() + pi / 2
|
2024-12-19 12:55:40 +01:00
|
|
|
local p1 = {x = s.x + cos(yaw1), y = s.y, z = s.z + sin(yaw1)}
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
-- dig bottom node first incase of door
|
|
|
|
can_dig_drop(p1)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
p1.y = p1.y + 1
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
can_dig_drop(p1)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- will try again in 2 second
|
2024-12-19 12:55:40 +01:00
|
|
|
self.path.stuck_timer = pathfinding_stuck_timeout - 2
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
elseif s.y < p1.y and (not self.fly) then
|
|
|
|
self:do_jump() --add jump to pathfinding
|
|
|
|
self.path.following = true
|
|
|
|
else
|
|
|
|
-- yay i found path
|
|
|
|
if self.attack then
|
|
|
|
self:mob_sound(self.sounds.war_cry)
|
|
|
|
else
|
|
|
|
self:mob_sound(self.sounds.random)
|
|
|
|
end
|
|
|
|
|
|
|
|
self:set_velocity(self.walk_velocity)
|
|
|
|
|
|
|
|
-- follow path now that it has it
|
|
|
|
self.path.following = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- temporary entity for go_to() function
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
minetest.register_entity("mobs:_pos", {
|
|
|
|
initial_properties = {
|
|
|
|
visual = "sprite", texture = "", hp_max = 1, physical = false,
|
|
|
|
static_save = false, pointable = false, is_visible = false
|
|
|
|
}, health = 1, _cmi_is_mob = true,
|
2021-01-06 16:05:43 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
on_step = function(self, dtime)
|
2021-01-06 16:05:43 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.counter = (self.counter or 0) + dtime
|
2021-01-06 16:05:43 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.counter > 20 then self.object:remove() end
|
2021-01-06 16:05:43 +01:00
|
|
|
end
|
2024-12-19 12:55:40 +01:00
|
|
|
})
|
2021-01-06 16:05:43 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- add temp entity and make mob go to that entity position
|
|
|
|
|
|
|
|
function mob_class:go_to(pos)
|
|
|
|
|
|
|
|
local obj = minetest.add_entity(pos, "mobs:_pos")
|
|
|
|
|
|
|
|
if obj and obj:get_luaentity() then self:do_attack(obj, true) end
|
2021-01-06 16:05:43 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- peaceful player privilege support
|
|
|
|
|
|
|
|
local function is_peaceful_player(player)
|
|
|
|
|
|
|
|
-- main setting enabled
|
|
|
|
if peaceful_player_enabled then return true end
|
|
|
|
|
|
|
|
local player_name = player:get_player_name()
|
|
|
|
|
|
|
|
-- player priv enabled
|
|
|
|
if player_name and minetest.check_player_privs(player_name, "peaceful_player") then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
2021-01-06 16:05:43 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- general attack function for all mobs
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:general_attack()
|
|
|
|
|
|
|
|
-- return if already attacking, passive or docile during day
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.passive or self.state == "runaway" or self.state == "attack"
|
2020-10-26 17:38:53 +01:00
|
|
|
or self:day_docile() then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local s = self.object:get_pos() ; if not s then return end
|
|
|
|
local objs = minetest.get_objects_inside_radius(s, self.view_range)
|
|
|
|
|
|
|
|
-- remove entities we aren't interested in
|
|
|
|
for n = 1, #objs do
|
|
|
|
|
|
|
|
local ent = objs[n]:get_luaentity()
|
|
|
|
|
|
|
|
-- are we a player?
|
2024-12-19 12:55:40 +01:00
|
|
|
if is_player(objs[n]) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- if player invisible or mob cannot attack then remove from list
|
2024-12-19 12:55:40 +01:00
|
|
|
if not damage_enabled or self.attack_players == false
|
2020-10-26 17:38:53 +01:00
|
|
|
or (self.owner and self.type ~= "monster")
|
2021-03-17 20:30:38 +01:00
|
|
|
or is_invisible(self, objs[n]:get_player_name())
|
2024-12-19 12:55:40 +01:00
|
|
|
or (self.specific_attack and not check_for("player", self.specific_attack)) then
|
2020-10-26 17:38:53 +01:00
|
|
|
objs[n] = nil
|
|
|
|
--print("- pla", n)
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- are we a creatura mob?
|
|
|
|
elseif creatura and ent and ent._cmi_is_mob ~= true
|
|
|
|
and ent.hitbox and ent.stand_node then
|
|
|
|
|
|
|
|
-- monsters attack all creatura mobs, npc and animals will only attack
|
|
|
|
-- if the animal owner is currently being attacked by creatura mob
|
|
|
|
if self.name == ent.name
|
|
|
|
or (self.type ~= "monster"
|
|
|
|
and self.owner ~= (ent._target and ent._target:get_player_name() or "."))
|
|
|
|
or (self.specific_attack and not check_for(ent.name, self.specific_attack)) then
|
|
|
|
|
|
|
|
objs[n] = nil
|
|
|
|
--print("-- creatura", ent.name)
|
|
|
|
end
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- or are we a mob?
|
|
|
|
elseif ent and ent._cmi_is_mob then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- remove mobs to not attack
|
2020-10-26 17:38:53 +01:00
|
|
|
if self.name == ent.name
|
2024-12-19 12:55:40 +01:00
|
|
|
or check_for(ent.name, self.attack_ignore)
|
2020-10-26 17:38:53 +01:00
|
|
|
or (not self.attack_animals and ent.type == "animal")
|
|
|
|
or (not self.attack_monsters and ent.type == "monster")
|
|
|
|
or (not self.attack_npcs and ent.type == "npc")
|
2024-12-19 12:55:40 +01:00
|
|
|
or (self.specific_attack and not check_for(ent.name, self.specific_attack)) then
|
2020-10-26 17:38:53 +01:00
|
|
|
objs[n] = nil
|
|
|
|
--print("- mob", n, self.name, ent.name)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- remove all other entities
|
|
|
|
else
|
|
|
|
--print(" -obj", n)
|
|
|
|
objs[n] = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local p, sp, dist, min_player
|
|
|
|
local min_dist = self.view_range + 1
|
|
|
|
|
|
|
|
-- go through remaining entities and select closest
|
|
|
|
for _,player in pairs(objs) do
|
|
|
|
|
|
|
|
p = player:get_pos()
|
|
|
|
sp = s
|
|
|
|
|
|
|
|
dist = get_distance(p, s)
|
|
|
|
|
|
|
|
-- aim higher to make looking up hills more realistic
|
|
|
|
p.y = p.y + 1
|
|
|
|
sp.y = sp.y + 1
|
|
|
|
|
|
|
|
-- choose closest player to attack that isnt self
|
2024-12-19 12:55:40 +01:00
|
|
|
if dist ~= 0 and dist < min_dist
|
|
|
|
and self:line_of_sight(sp, p) and not is_peaceful_player(player) then
|
2020-10-26 17:38:53 +01:00
|
|
|
min_dist = dist
|
|
|
|
min_player = player
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- attack closest player or mob
|
|
|
|
if min_player and random(100) > self.attack_chance then
|
|
|
|
self:do_attack(min_player)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- find someone to runaway from
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:do_runaway_from()
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not self.runaway_from then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local s = self.object:get_pos() ; if not s then return end
|
|
|
|
local p, sp, dist, pname
|
|
|
|
local player, obj, min_player, name
|
|
|
|
local min_dist = self.view_range + 1
|
|
|
|
local objs = minetest.get_objects_inside_radius(s, self.view_range)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- loop through entities surrounding mob
|
2020-10-26 17:38:53 +01:00
|
|
|
for n = 1, #objs do
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if is_player(objs[n]) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
pname = objs[n]:get_player_name()
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if is_invisible(self, pname) or self.owner == pname then
|
2020-10-26 17:38:53 +01:00
|
|
|
name = ""
|
|
|
|
else
|
|
|
|
player = objs[n]
|
|
|
|
name = "player"
|
|
|
|
end
|
|
|
|
else
|
|
|
|
obj = objs[n]:get_luaentity()
|
|
|
|
|
|
|
|
if obj then
|
|
|
|
player = obj.object
|
|
|
|
name = obj.name or ""
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- find specific mob to runaway from
|
|
|
|
if name ~= "" and name ~= self.name
|
|
|
|
and (self.runaway_from and check_for(name, self.runaway_from)) then
|
|
|
|
|
|
|
|
sp = s
|
|
|
|
p = player and player:get_pos() or s
|
|
|
|
|
|
|
|
-- aim higher to make looking up hills more realistic
|
|
|
|
p.y = p.y + 1
|
|
|
|
sp.y = sp.y + 1
|
|
|
|
|
|
|
|
dist = get_distance(p, s)
|
|
|
|
|
|
|
|
-- choose closest player/mob to runaway from
|
2024-12-19 12:55:40 +01:00
|
|
|
if dist < min_dist and self:line_of_sight(sp, p) then
|
2020-10-26 17:38:53 +01:00
|
|
|
min_dist = dist
|
|
|
|
min_player = player
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if min_player then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:yaw_to_pos(min_player:get_pos(), 3)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.state = "runaway"
|
|
|
|
self.runaway_timer = 3
|
|
|
|
self.following = nil
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
return
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- check for nodes to runaway from
|
|
|
|
objs = minetest.find_node_near(s, self.view_range, self.runaway_from, true)
|
|
|
|
|
|
|
|
if objs then
|
|
|
|
|
|
|
|
self:yaw_to_pos(objs, 3)
|
|
|
|
|
|
|
|
self.state = "runaway"
|
|
|
|
self.runaway_timer = 3
|
|
|
|
self.following = nil
|
|
|
|
end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- follow player if owner or holding item, if fish outta water then flop
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:follow_flop()
|
|
|
|
|
|
|
|
-- find player to follow
|
2024-12-19 12:55:40 +01:00
|
|
|
if (self.follow ~= "" or self.order == "follow") and not self.following
|
|
|
|
and self.state ~= "attack" and self.state ~= "runaway" then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local s = self.object:get_pos() ; if not s then return end
|
|
|
|
local players = minetest.get_connected_players()
|
|
|
|
|
|
|
|
for n = 1, #players do
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if players[n] and not is_invisible(self, players[n]:get_player_name())
|
|
|
|
and get_distance(players[n]:get_pos(), s) < self.view_range then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.following = players[n] ; break
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.type == "npc" and self.order == "follow"
|
|
|
|
and self.state ~= "attack" and self.owner ~= "" then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- npc stop following player if not owner
|
|
|
|
if self.following
|
2024-12-19 12:55:40 +01:00
|
|
|
and self.owner and self.owner ~= self.following:get_player_name() then
|
2020-10-26 17:38:53 +01:00
|
|
|
self.following = nil
|
|
|
|
end
|
|
|
|
else
|
2020-12-07 17:01:24 +01:00
|
|
|
-- stop following player if not holding specific item or mob is horny
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.following and is_player(self.following)
|
|
|
|
and (self.horny or not self:follow_holding(self.following)) then
|
2020-10-26 17:38:53 +01:00
|
|
|
self.following = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- follow that thing
|
|
|
|
if self.following then
|
|
|
|
|
|
|
|
local s = self.object:get_pos()
|
|
|
|
local p
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if is_player(self.following) then p = self.following:get_pos()
|
|
|
|
elseif self.following.object then p = self.following.object:get_pos() end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if p then
|
|
|
|
|
|
|
|
local dist = get_distance(p, s)
|
|
|
|
|
|
|
|
-- dont follow if out of range
|
2024-12-19 12:55:40 +01:00
|
|
|
if dist > self.view_range then self.following = nil
|
2020-10-26 17:38:53 +01:00
|
|
|
else
|
2024-12-19 12:55:40 +01:00
|
|
|
self:yaw_to_pos(p)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- anyone but standing npc's can move along
|
2024-12-19 12:55:40 +01:00
|
|
|
if dist >= self.reach and self.order ~= "stand" then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self:set_velocity(self.walk_velocity)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.walk_chance ~= 0 then self:set_animation("walk") end
|
2020-10-26 17:38:53 +01:00
|
|
|
else
|
|
|
|
self:set_velocity(0)
|
|
|
|
self:set_animation("stand")
|
|
|
|
end
|
|
|
|
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- swimmers flop when out of their element, and swim again when back in
|
|
|
|
if self.fly then
|
|
|
|
|
|
|
|
if not self:attempt_flight_correction() then
|
|
|
|
|
|
|
|
self.state = "flop"
|
2020-12-07 17:01:24 +01:00
|
|
|
|
|
|
|
-- do we have a custom on_flop function?
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.on_flop and self:on_flop(self) then return end
|
2020-12-07 17:01:24 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
self.object:set_velocity({x = 0, y = -5, z = 0})
|
|
|
|
|
|
|
|
self:set_animation("stand")
|
|
|
|
|
|
|
|
return
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
elseif self.state == "flop" then self.state = "stand" end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- dogshoot attack switch and counter function
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:dogswitch(dtime)
|
|
|
|
|
|
|
|
-- switch mode not activated
|
2024-12-19 12:55:40 +01:00
|
|
|
if not self.dogshoot_switch or not dtime then return 0 end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.dogshoot_count = self.dogshoot_count + dtime
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if (self.dogshoot_switch == 1 and self.dogshoot_count > self.dogshoot_count_max)
|
|
|
|
or (self.dogshoot_switch == 2 and self.dogshoot_count > self.dogshoot_count2_max) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.dogshoot_count = 0
|
|
|
|
|
|
|
|
if self.dogshoot_switch == 1 then
|
|
|
|
self.dogshoot_switch = 2
|
|
|
|
else
|
|
|
|
self.dogshoot_switch = 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return self.dogshoot_switch
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- stop attack and reset vars
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mob_class:stop_attack()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.attack = nil
|
|
|
|
self.following = nil
|
|
|
|
self.v_start = false
|
|
|
|
self.timer = 0
|
|
|
|
self.blinktimer = 0
|
|
|
|
self.path.way = nil
|
|
|
|
self:set_velocity(0)
|
|
|
|
self.state = "stand"
|
|
|
|
self:set_animation("stand", true)
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- execute current state (stand, walk, run, attacks)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mob_class:do_states(dtime)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local yaw = self.object:get_yaw() ; if not yaw then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- are we standing in something that hurts ? Try to get out
|
|
|
|
if is_node_dangerous(self, self.standing_in) then -- and self.pause_timer <= 0 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local s = self.object:get_pos()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local lp = minetest.find_nodes_in_area_under_air(
|
|
|
|
{x = s.x - 7, y = s.y - 1.0, z = s.z - 7},
|
|
|
|
{x = s.x + 7, y = s.y + 1.0, z = s.z + 7},
|
|
|
|
{"group:cracky", "group:crumbly", "group:choppy", "group:solid"})
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- did we find land ? if so face random block to climb onto
|
|
|
|
if #lp > 0 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
yaw = self:yaw_to_pos( lp[random(#lp)] )
|
|
|
|
|
|
|
|
self.pause_timer = 3
|
|
|
|
self.following = nil
|
|
|
|
self:set_velocity(self.run_velocity)
|
2020-10-26 17:38:53 +01:00
|
|
|
self:set_animation("walk")
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.state == "stand" then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.randomly_turn and random(4) == 1 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local lp
|
|
|
|
local s = self.object:get_pos()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
for _,player in pairs(minetest.get_connected_players()) do
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local player_pos = player:get_pos()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if get_distance(player_pos, s) <= 3 then
|
|
|
|
lp = player_pos ; break
|
|
|
|
end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- look at any players nearby, otherwise turn randomly
|
|
|
|
if lp then
|
|
|
|
yaw = self:yaw_to_pos(lp)
|
|
|
|
else
|
|
|
|
yaw = yaw + random() - 0.5
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:set_yaw(yaw, 8)
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:set_velocity(0)
|
|
|
|
self:set_animation("stand")
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- mobs ordered to stand stay standing
|
|
|
|
if self.order ~= "stand" and self.walk_chance ~= 0
|
|
|
|
and self.facing_fence ~= true
|
|
|
|
and self.at_cliff == false
|
|
|
|
and random(100) <= self.walk_chance then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:set_velocity(self.walk_velocity)
|
|
|
|
self.state = "walk"
|
|
|
|
self:set_animation("walk")
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
elseif self.state == "walk" then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.randomly_turn and random(100) <= 30 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
yaw = yaw + random() - 0.5
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:set_yaw(yaw, 8)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- for flying/swimming mobs randomly move up and down also
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.fly_in and not self.following then
|
2020-10-26 17:38:53 +01:00
|
|
|
self:attempt_flight_correction(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- stand for great fall in front
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.facing_fence or self.at_cliff or random(100) <= self.stand_chance then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- don't stand if mob flies and keep_flying set
|
2024-12-19 12:55:40 +01:00
|
|
|
if (self.fly and not self.keep_flying) or not self.fly then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self:set_velocity(0)
|
|
|
|
self.state = "stand"
|
|
|
|
self:set_animation("stand", true)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
self:set_velocity(self.walk_velocity)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- figure out which animation to use while in motion
|
|
|
|
if self:flight_check() and self.animation
|
|
|
|
and self.animation.fly_start and self.animation.fly_end then
|
|
|
|
|
|
|
|
local on_ground = minetest.registered_nodes[self.standing_on].walkable
|
|
|
|
local in_water = minetest.registered_nodes[self.standing_in].groups.water
|
|
|
|
|
|
|
|
if on_ground and in_water then
|
|
|
|
self:set_animation("fly")
|
|
|
|
elseif on_ground then
|
|
|
|
self:set_animation("walk")
|
|
|
|
else
|
|
|
|
self:set_animation("fly")
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
else
|
|
|
|
self:set_animation("walk")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
elseif self.state == "runaway" then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.runaway_timer = self.runaway_timer - 1
|
|
|
|
|
|
|
|
-- stop when timer runs out or when at cliff
|
|
|
|
if self.runaway_timer <= 0 or self.at_cliff or self.order == "stand" then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self:set_velocity(0)
|
|
|
|
self.state = "stand"
|
|
|
|
self:set_animation("stand")
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
-- try to turn so we are not stuck
|
|
|
|
yaw = yaw + random(-1, 1) * 1.5
|
|
|
|
yaw = self:set_yaw(yaw, 4)
|
2020-10-26 17:38:53 +01:00
|
|
|
else
|
|
|
|
self:set_velocity(self.run_velocity)
|
|
|
|
self:set_animation("walk")
|
|
|
|
end
|
|
|
|
|
|
|
|
-- attack routines (explode, dogfight, shoot, dogshoot)
|
|
|
|
elseif self.state == "attack" then
|
|
|
|
|
|
|
|
-- get mob and enemy positions and distance between
|
|
|
|
local s = self.object:get_pos()
|
|
|
|
local p = self.attack and self.attack:get_pos()
|
|
|
|
local dist = p and get_distance(p, s) or 500
|
|
|
|
|
|
|
|
-- stop attacking if player out of range or invisible
|
|
|
|
if dist > self.view_range
|
2024-12-19 12:55:40 +01:00
|
|
|
or not self.attack or not self.attack:get_pos() or self.attack:get_hp() <= 0
|
|
|
|
or (is_player(self.attack)
|
2021-03-17 20:30:38 +01:00
|
|
|
and is_invisible(self, self.attack:get_player_name())) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
--print(" ** stop attacking **", self.name, self.health, dist, self.view_range)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:stop_attack() ; return
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- check enemy is in sight
|
|
|
|
local in_sight = self:line_of_sight(
|
|
|
|
{x = s.x, y = s.y + 0.5, z = s.z},
|
|
|
|
{x = p.x, y = p.y + 0.5, z = p.z})
|
|
|
|
|
|
|
|
-- stop attacking when enemy not seen for 11 seconds
|
|
|
|
if not in_sight then
|
|
|
|
|
|
|
|
self.target_time_lost = (self.target_time_lost or 0) + dtime
|
|
|
|
|
|
|
|
if self.target_time_lost > self.attack_patience then self:stop_attack() end
|
|
|
|
else
|
|
|
|
self.target_time_lost = 0
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
if self.attack_type == "explode" then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:yaw_to_pos(p)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local node_break_radius = self.explosion_radius or 1
|
|
|
|
local entity_damage_radius = self.explosion_damage_radius
|
|
|
|
or (node_break_radius * 2)
|
|
|
|
|
|
|
|
-- look a little higher to fix raycast
|
|
|
|
s.y = s.y + 0.5 ; p.y = p.y + 0.5
|
|
|
|
|
|
|
|
-- start timer when in reach and line of sight
|
2024-12-19 12:55:40 +01:00
|
|
|
if not self.v_start and dist <= self.reach and in_sight then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.v_start = true
|
|
|
|
self.timer = 0
|
|
|
|
self.blinktimer = 0
|
|
|
|
self:mob_sound(self.sounds.fuse)
|
|
|
|
|
|
|
|
--print("=== explosion timer started", self.explosion_timer)
|
|
|
|
|
|
|
|
-- stop timer if out of reach or direct line of sight
|
2024-12-19 12:55:40 +01:00
|
|
|
elseif self.allow_fuse_reset and self.v_start
|
|
|
|
and (dist > self.reach or not in_sight) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
--print("=== explosion timer stopped")
|
|
|
|
|
|
|
|
self.v_start = false
|
|
|
|
self.timer = 0
|
|
|
|
self.blinktimer = 0
|
|
|
|
self.blinkstatus = false
|
|
|
|
self.object:set_texture_mod("")
|
|
|
|
end
|
|
|
|
|
|
|
|
-- walk right up to player unless the timer is active
|
|
|
|
if self.v_start and (self.stop_to_explode or dist < 1.5) then
|
|
|
|
self:set_velocity(0)
|
|
|
|
else
|
|
|
|
self:set_velocity(self.run_velocity)
|
|
|
|
end
|
|
|
|
|
|
|
|
if self.animation and self.animation.run_start then
|
|
|
|
self:set_animation("run")
|
|
|
|
else
|
|
|
|
self:set_animation("walk")
|
|
|
|
end
|
|
|
|
|
|
|
|
if self.v_start then
|
|
|
|
|
|
|
|
self.timer = self.timer + dtime
|
|
|
|
self.blinktimer = (self.blinktimer or 0) + dtime
|
|
|
|
|
|
|
|
if self.blinktimer > 0.2 then
|
|
|
|
|
|
|
|
self.blinktimer = 0
|
|
|
|
|
|
|
|
if self.blinkstatus then
|
|
|
|
self.object:set_texture_mod(self.texture_mods)
|
|
|
|
else
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:set_texture_mod(self.texture_mods .. "^[brighten")
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
self.blinkstatus = not self.blinkstatus
|
|
|
|
end
|
|
|
|
|
|
|
|
--print("=== explosion timer", self.timer)
|
|
|
|
|
|
|
|
if self.timer > self.explosion_timer then
|
|
|
|
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
|
|
|
|
-- dont damage anything if area protected or next to water
|
|
|
|
if minetest.find_node_near(pos, 1, {"group:water"})
|
|
|
|
or minetest.is_protected(pos, "") then
|
|
|
|
node_break_radius = 1
|
|
|
|
end
|
|
|
|
|
|
|
|
remove_mob(self, true)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
mobs:boom(self, pos, node_break_radius, entity_damage_radius)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
elseif self.attack_type == "dogfight"
|
|
|
|
or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2)
|
|
|
|
or (self.attack_type == "dogshoot" and dist <= self.reach
|
|
|
|
and self:dogswitch() == 0) then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- if flying mobs are moving around inside proper medium
|
|
|
|
if self.fly and dist > self.reach and self:flight_check() then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local s_y = floor(s.y) -- self
|
|
|
|
local p_y = floor(p.y + 1) -- attacker
|
2020-10-26 17:38:53 +01:00
|
|
|
local v = self.object:get_velocity()
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- fly/swim up towards attacker
|
|
|
|
if s_y < p_y then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- if correct medium above then move up
|
|
|
|
if #minetest.find_nodes_in_area(
|
|
|
|
{x = s.x, y = s.y + 1, z = s.z},
|
|
|
|
{x = s.x, y = s.y + 1, z = s.z}, self.fly_in) > 0 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.object:set_velocity({
|
2024-12-19 12:55:40 +01:00
|
|
|
x = v.x, y = self.walk_velocity, z = v.z})
|
|
|
|
else
|
|
|
|
self.object:set_velocity({x = v.x, y = 0, z = v.z}) -- stop
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- fly/swim down towards attacker
|
|
|
|
elseif s_y > p_y then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- if correct medium below then move down
|
|
|
|
if #minetest.find_nodes_in_area(
|
|
|
|
{x = s.x, y = s.y - 1, z = s.z},
|
|
|
|
{x = s.x, y = s.y - 1, z = s.z}, self.fly_in) > 0 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.object:set_velocity({
|
2024-12-19 12:55:40 +01:00
|
|
|
x = v.x, y = -self.walk_velocity, z = v.z})
|
|
|
|
else
|
|
|
|
self.object:set_velocity({x = v.x, y = 0, z = v.z}) -- stop
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- rnd: new movement direction
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.path.following and self.path.way
|
2020-10-26 17:38:53 +01:00
|
|
|
and self.attack_type ~= "dogshoot" then
|
|
|
|
|
|
|
|
-- no paths longer than 50
|
2024-12-19 12:55:40 +01:00
|
|
|
if #self.path.way > 50 or dist < self.reach then
|
2020-10-26 17:38:53 +01:00
|
|
|
self.path.following = false
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local p1 = self.path.way[1]
|
|
|
|
|
|
|
|
if not p1 then
|
|
|
|
self.path.following = false
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if abs(p1.x - s.x) + abs(p1.z - s.z) < 0.6 then
|
|
|
|
-- reached waypoint, remove it from queue
|
|
|
|
table_remove(self.path.way, 1)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- set new temporary target
|
|
|
|
p = {x = p1.x, y = p1.y, z = p1.z}
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:yaw_to_pos(p)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- move towards enemy if beyond mob reach
|
2024-12-19 12:55:40 +01:00
|
|
|
if dist > (self.reach + (self.reach_ext or 0)) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- path finding by rnd (only when enabled in setting and mob)
|
|
|
|
if self.pathfinding and pathfinding_enable then
|
2020-10-26 17:38:53 +01:00
|
|
|
self:smart_mobs(s, p, dist, dtime)
|
|
|
|
end
|
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
-- distance padding to stop spinning mob
|
|
|
|
local pad = abs(p.x - s.x) + abs(p.z - s.z)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.reach_ext = 0 -- extended ready off by default
|
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
if self.at_cliff or pad < 0.2 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- when on top of player extend reach slightly so player can
|
|
|
|
-- still be attacked.
|
|
|
|
self.reach_ext = 0.8
|
2020-10-26 17:38:53 +01:00
|
|
|
self:set_velocity(0)
|
|
|
|
self:set_animation("stand")
|
|
|
|
else
|
|
|
|
|
|
|
|
if self.path.stuck then
|
|
|
|
self:set_velocity(self.walk_velocity)
|
|
|
|
else
|
|
|
|
self:set_velocity(self.run_velocity)
|
|
|
|
end
|
|
|
|
|
|
|
|
if self.animation and self.animation.run_start then
|
|
|
|
self:set_animation("run")
|
|
|
|
else
|
|
|
|
self:set_animation("walk")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else -- rnd: if inside reach range
|
|
|
|
|
|
|
|
self.path.stuck = false
|
|
|
|
self.path.stuck_timer = 0
|
|
|
|
self.path.following = false -- not stuck anymore
|
|
|
|
|
|
|
|
self:set_velocity(0)
|
|
|
|
|
|
|
|
if self.timer > 1 then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.timer = 0
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- no custom attack or custom attack returns true to continue
|
2024-12-19 12:55:40 +01:00
|
|
|
if not self.custom_attack or self:custom_attack(self, p) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self:set_animation("punch")
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local p2, s2 = p, s
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
p2.y = p2.y + .5
|
|
|
|
s2.y = s2.y + .5
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self:line_of_sight(p2, s2) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- play attack sound
|
|
|
|
self:mob_sound(self.sounds.attack)
|
|
|
|
|
|
|
|
-- punch player (or what player is attached to)
|
|
|
|
local attached = self.attack:get_attach()
|
|
|
|
|
|
|
|
if attached then
|
|
|
|
self.attack = attached
|
|
|
|
end
|
|
|
|
|
2021-05-16 11:33:49 +02:00
|
|
|
local dgroup = self.damage_group or "fleshy"
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
self.attack:punch(self.object, 1.0, {
|
|
|
|
full_punch_interval = 1.0,
|
2021-05-16 11:33:49 +02:00
|
|
|
damage_groups = {[dgroup] = self.damage}
|
2020-10-26 17:38:53 +01:00
|
|
|
}, nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
elseif self.attack_type == "shoot"
|
|
|
|
or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1)
|
|
|
|
or (self.attack_type == "dogshoot" and dist > self.reach and
|
2024-12-19 12:55:40 +01:00
|
|
|
self:dogswitch() == 0) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
p.y = p.y - .5
|
|
|
|
s.y = s.y + .5
|
|
|
|
|
|
|
|
local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z}
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:yaw_to_pos(p)
|
2020-10-26 17:38:53 +01:00
|
|
|
self:set_velocity(0)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.shoot_interval and self.timer > self.shoot_interval
|
2020-10-26 17:38:53 +01:00
|
|
|
and random(100) <= 60 then
|
|
|
|
|
|
|
|
self.timer = 0
|
|
|
|
self:set_animation("shoot")
|
|
|
|
|
|
|
|
-- play shoot attack sound
|
|
|
|
self:mob_sound(self.sounds.shoot_attack)
|
|
|
|
|
|
|
|
local p = self.object:get_pos()
|
2024-12-19 12:55:40 +01:00
|
|
|
local prop = self.object:get_properties()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
p.y = p.y + (prop.collisionbox[2] + prop.collisionbox[5]) / 2
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if minetest.registered_entities[self.arrow] then
|
|
|
|
|
|
|
|
local obj = minetest.add_entity(p, self.arrow)
|
|
|
|
local ent = obj:get_luaentity()
|
|
|
|
local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
|
2021-08-02 20:41:22 +02:00
|
|
|
|
|
|
|
-- check for custom override for arrow
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.arrow_override then self.arrow_override(ent) end
|
2021-08-02 20:41:22 +02:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
local v = ent.velocity or 1 -- or set to default
|
|
|
|
|
|
|
|
ent.owner_id = tostring(self.object) -- add unique owner id to arrow
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- setup homing arrow and target
|
|
|
|
if self.homing then
|
|
|
|
ent._homing_target = self.attack
|
|
|
|
end
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- offset makes shoot aim accurate
|
|
|
|
vec.y = vec.y + self.shoot_offset
|
|
|
|
vec.x = vec.x * (v / amount)
|
|
|
|
vec.y = vec.y * (v / amount)
|
|
|
|
vec.z = vec.z * (v / amount)
|
|
|
|
|
|
|
|
obj:set_velocity(vec)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- falling and fall damage
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:falling(pos)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.fly or self.disable_falling then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- floating in water (or falling)
|
2024-12-19 12:55:40 +01:00
|
|
|
local v = self.object:get_velocity() ; if not v then return end
|
2021-03-20 13:55:26 +01:00
|
|
|
local fall_speed = self.fall_speed
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- in water then use liquid viscosity for float/sink speed
|
2021-03-20 13:55:26 +01:00
|
|
|
if self.floats == 1 and self.standing_in
|
|
|
|
and minetest.registered_nodes[self.standing_in].groups.liquid then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local visc = min(
|
2021-03-20 13:55:26 +01:00
|
|
|
minetest.registered_nodes[self.standing_in].liquid_viscosity, 7) + 1
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-03-20 13:55:26 +01:00
|
|
|
self.object:set_velocity({x = v.x, y = 0.6, z = v.z})
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2021-03-20 13:55:26 +01:00
|
|
|
fall_speed = -1.2 / visc
|
2020-10-26 17:38:53 +01:00
|
|
|
else
|
|
|
|
|
|
|
|
-- fall damage onto solid ground
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.fall_damage and self.object:get_velocity().y == 0 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local d = (self.old_y or self.object:get_pos().y) - self.object:get_pos().y
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if d > 5 then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local damage = d - 5
|
|
|
|
local add = minetest.get_item_group(
|
|
|
|
self.standing_on, "fall_damage_add_percent")
|
|
|
|
|
|
|
|
if add ~= 0 then
|
|
|
|
damage = damage + damage * (add / 100)
|
|
|
|
end
|
|
|
|
|
|
|
|
self.health = self.health - floor(damage)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self:check_for_death({type = "fall"}) then return true end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
self.old_y = self.object:get_pos().y
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- fall at set speed
|
2021-06-27 17:39:50 +02:00
|
|
|
self.object:set_acceleration({x = 0, y = fall_speed, z = 0})
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- deal damage and effects when mob punched
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local dis_damage_kb = settings:get_bool("mobs_disable_damage_kb")
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
function mob_class:on_punch(hitter, tflp, tool_capabilities, dir, damage)
|
|
|
|
|
|
|
|
-- mob health check
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.health <= 0 then return true end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- custom punch function (if false returned, do not continue and return true)
|
|
|
|
if self.do_punch and self:do_punch(hitter, tflp, tool_capabilities, dir) == false then
|
2020-10-26 17:38:53 +01:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- error checking when mod profiling is enabled
|
|
|
|
if not tool_capabilities then
|
2021-06-27 17:39:50 +02:00
|
|
|
minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
|
2020-10-26 17:38:53 +01:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2021-04-05 14:46:21 +02:00
|
|
|
-- is mob protected
|
|
|
|
if self.protected then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-04-05 14:46:21 +02:00
|
|
|
-- did player hit mob and if so is it in protected area
|
2024-12-19 12:55:40 +01:00
|
|
|
if is_player(hitter) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-04-05 14:46:21 +02:00
|
|
|
local player_name = hitter:get_player_name()
|
|
|
|
|
|
|
|
if player_name ~= self.owner
|
|
|
|
and minetest.is_protected(self.object:get_pos(), player_name) then
|
|
|
|
|
|
|
|
minetest.chat_send_player(hitter:get_player_name(),
|
|
|
|
S("Mob has been protected!"))
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- if protection is on level 2 then dont let arrows harm mobs
|
|
|
|
elseif self.protected == 2 then
|
|
|
|
|
|
|
|
local ent = hitter and hitter:get_luaentity()
|
|
|
|
|
|
|
|
if ent and ent._is_arrow then
|
|
|
|
return true -- arrow entity
|
|
|
|
elseif not ent then
|
|
|
|
return true -- non entity
|
|
|
|
end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
local weapon = hitter:get_wielded_item()
|
|
|
|
local weapon_def = weapon:get_definition() or {}
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
--- check for ehchantments when using mineclonia/voxelibre
|
|
|
|
local enchants = {}
|
|
|
|
|
|
|
|
if use_mc2 then
|
|
|
|
|
|
|
|
enchants = minetest.deserialize(
|
|
|
|
weapon:get_meta():get_string("mcl_enchanting:enchantments")) or {}
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- calculate mob damage
|
|
|
|
local damage = 0
|
|
|
|
local armor = self.object:get_armor_groups() or {}
|
|
|
|
local tmp
|
|
|
|
|
|
|
|
-- quick error check incase it ends up 0 (serialize.h check test)
|
2024-12-19 12:55:40 +01:00
|
|
|
if tflp == 0 then tflp = 0.2 end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if use_cmi then
|
|
|
|
damage = cmi.calculate_damage(self.object, hitter, tflp, tool_capabilities, dir)
|
|
|
|
else
|
|
|
|
|
|
|
|
for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
|
|
|
|
|
|
|
|
tmp = tflp / (tool_capabilities.full_punch_interval or 1.4)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if tmp < 0 then tmp = 0.0 elseif tmp > 1 then tmp = 1.0 end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
damage = damage + (tool_capabilities.damage_groups[group] or 0)
|
2021-04-05 14:46:21 +02:00
|
|
|
* tmp * ((armor[group] or 0) / 100.0)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- check if hit by player item or entity
|
|
|
|
local hit_item = weapon_def.name
|
|
|
|
|
|
|
|
if not is_player(hitter) then
|
|
|
|
hit_item = hitter:get_luaentity().name
|
|
|
|
end
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- check for tool immunity or special damage
|
|
|
|
for n = 1, #self.immune_to do
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.immune_to[n][1] == hit_item then
|
2021-06-27 17:39:50 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
damage = self.immune_to[n][2] or 0 ; break
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- if "all" then no tools deal damage unless it's specified in list
|
|
|
|
elseif self.immune_to[n][1] == "all" then
|
|
|
|
damage = self.immune_to[n][2] or 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
--print("Mob Damage is", damage)
|
|
|
|
|
|
|
|
-- healing
|
|
|
|
if damage <= -1 then
|
2021-06-27 17:39:50 +02:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
self.health = self.health - floor(damage)
|
2021-06-27 17:39:50 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if use_vh1 then VH1.update_bar(self.object, self.health) end
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
if use_cmi
|
2021-06-27 17:39:50 +02:00
|
|
|
and cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) then
|
2020-10-26 17:38:53 +01:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- add weapon wear
|
2024-12-19 12:55:40 +01:00
|
|
|
local punch_interval = tool_capabilities.full_punch_interval or 1.4
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- toolrank support
|
|
|
|
local wear = floor((punch_interval / 75) * 9000)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- check for punch_attack_uses being 0 to negate wear
|
|
|
|
if tool_capabilities.punch_attack_uses == 0 then
|
|
|
|
wear = 0
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if mobs.is_creative(hitter:get_player_name()) then
|
|
|
|
wear = use_tr and 1 or 0
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if use_tr and weapon_def.original_description then
|
2021-06-27 17:39:50 +02:00
|
|
|
toolranks.new_afteruse(weapon, hitter, nil, {wear = wear})
|
2020-10-26 17:38:53 +01:00
|
|
|
else
|
|
|
|
weapon:add_wear(wear)
|
|
|
|
end
|
|
|
|
|
|
|
|
hitter:set_wielded_item(weapon)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- check for damage increasing enchantments
|
|
|
|
if use_mc2 and enchants.sharpness then
|
|
|
|
damage = damage + (0.5 * enchants.sharpness) + 0.5
|
|
|
|
end
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- only play hit sound and show blood effects if damage is 1 or over
|
|
|
|
if damage >= 1 then
|
|
|
|
|
2021-05-29 19:38:01 +02:00
|
|
|
-- select tool use sound if found, or fallback to default
|
2024-12-19 12:55:40 +01:00
|
|
|
local snd = weapon_def.sound and weapon_def.sound.use or "mobs_punch"
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2021-05-16 11:33:49 +02:00
|
|
|
minetest.sound_play(snd, {object = self.object, max_hear_distance = 8}, true)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local prop = self.object:get_properties()
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- blood_particles
|
|
|
|
if not disable_blood and self.blood_amount > 0 then
|
|
|
|
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
local blood = self.blood_texture
|
|
|
|
local amount = self.blood_amount
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
pos.y = pos.y + (-prop.collisionbox[2] + prop.collisionbox[5]) * .5
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- lots of damage = more blood :)
|
2024-12-19 12:55:40 +01:00
|
|
|
if damage > 10 then amount = self.blood_amount * 2 end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- do we have a single blood texture or multiple?
|
|
|
|
if type(self.blood_texture) == "table" then
|
|
|
|
blood = self.blood_texture[random(#self.blood_texture)]
|
|
|
|
end
|
|
|
|
|
|
|
|
effect(pos, amount, blood, 1, 2, 1.75, nil, nil, true)
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- add healthy afterglow when hit (can cause lag with larger textures)
|
|
|
|
if mob_hit_effect then
|
|
|
|
|
|
|
|
self.old_texture_mods = self.texture_mods
|
|
|
|
|
|
|
|
self.object:set_texture_mod(self.texture_mods .. prop.damage_texture_modifier)
|
|
|
|
|
|
|
|
minetest.after(0.3, function()
|
|
|
|
|
|
|
|
if self and self.object and self.object:get_pos() then
|
|
|
|
|
|
|
|
self.texture_mods = self.old_texture_mods
|
|
|
|
self.old_texture_mods = nil
|
|
|
|
self.object:set_texture_mod(self.texture_mods)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- check for friendly fire (arrows from same mob)
|
|
|
|
if self.friendly_fire then
|
|
|
|
self.health = self.health - floor(damage) -- do damage regardless
|
|
|
|
else
|
|
|
|
local entity = hitter and hitter:get_luaentity()
|
|
|
|
|
|
|
|
-- check if arrow from same mob, if so then do no damage
|
|
|
|
if (entity and entity.name ~= self.arrow) or is_player(hitter) then
|
|
|
|
self.health = self.health - floor(damage)
|
|
|
|
end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- exit here if dead, check for tools with fire damage
|
|
|
|
local hot = tool_capabilities and tool_capabilities.damage_groups
|
|
|
|
and tool_capabilities.damage_groups.fire
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- check for any fire enchants also
|
|
|
|
if use_mc2 and (enchants.flame or enchants.fire_aspect) then
|
|
|
|
hot = true
|
|
|
|
end
|
|
|
|
|
2021-05-16 11:33:49 +02:00
|
|
|
if self:check_for_death({type = "punch", puncher = hitter, hot = hot}) then
|
2020-10-26 17:38:53 +01:00
|
|
|
return true
|
|
|
|
end
|
2024-12-19 12:55:40 +01:00
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- knock back effect (only on full punch)
|
2021-05-16 11:33:49 +02:00
|
|
|
if self.knock_back and tflp >= punch_interval then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local v = self.object:get_velocity() ; if not v then return true end
|
|
|
|
local kb = dis_damage_kb and 1 or (damage or 1)
|
2020-10-26 17:38:53 +01:00
|
|
|
local up = 2
|
|
|
|
|
|
|
|
-- if already in air then dont go up anymore when hit
|
2024-12-19 12:55:40 +01:00
|
|
|
if v.y > 0 or self.fly then up = 0 end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- direction error check
|
|
|
|
dir = dir or {x = 0, y = 0, z = 0}
|
|
|
|
|
|
|
|
-- use tool knockback value or default
|
|
|
|
kb = tool_capabilities.damage_groups["knockback"] or kb
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- check for knockback enchantment
|
|
|
|
if use_mc2 and enchants.knockback then
|
|
|
|
kb = kb + (3 * enchants.knockback)
|
|
|
|
end
|
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
self.object:set_velocity({x = dir.x * kb, y = up, z = dir.z * kb})
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- turn mob on knockback and play run/walk animation
|
|
|
|
self:set_yaw((random(0, 360) - 180) / 180 * pi, 12)
|
|
|
|
|
|
|
|
if self.animation and self.animation.injured_end and damage >= 1 then
|
|
|
|
self:set_animation("injured")
|
|
|
|
else
|
|
|
|
self:set_animation("walk")
|
|
|
|
end
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
self.pause_timer = 0.25
|
|
|
|
end
|
|
|
|
|
|
|
|
-- if skittish then run away
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.runaway and self.order ~= "stand" then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local lp = hitter:get_pos()
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
self:yaw_to_pos(lp, 3)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.state = "runaway"
|
2024-12-19 12:55:40 +01:00
|
|
|
self.runaway_timer = 3
|
2020-10-26 17:38:53 +01:00
|
|
|
self.following = nil
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local hitter_name = hitter:get_player_name() or ""
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- attack puncher and call other mobs for help
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.passive == false and self.state ~= "flop"
|
|
|
|
and not self.child and self.attack_players
|
|
|
|
and not (is_player(hitter) and hitter_name == self.owner)
|
|
|
|
and not is_invisible(self, hitter_name)
|
2020-10-26 17:38:53 +01:00
|
|
|
and self.object ~= hitter then
|
|
|
|
|
|
|
|
-- attack whoever punched mob
|
|
|
|
self.state = ""
|
|
|
|
self:do_attack(hitter)
|
|
|
|
|
|
|
|
-- alert others to the attack
|
2024-12-19 12:55:40 +01:00
|
|
|
local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
|
|
|
|
local ent
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
for n = 1, #objs do
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
ent = objs[n] and objs[n]:get_luaentity()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if ent and ent._cmi_is_mob then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- only alert members of same mob and assigned helper
|
2024-12-19 12:55:40 +01:00
|
|
|
if ent.group_attack and ent.state ~= "attack"
|
|
|
|
and not (is_player(hitter) and ent.owner == hitter_name)
|
|
|
|
and (ent.name == self.name or ent.name == self.group_helper) then
|
|
|
|
ent:do_attack(hitter)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- have owned mobs attack player threat
|
2024-12-19 12:55:40 +01:00
|
|
|
if is_player(hitter) and ent.owner == hitter_name and ent.owner_loyal then
|
|
|
|
ent:do_attack(self.object)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
return true
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- helper function to clean mob staticdata
|
|
|
|
|
|
|
|
local function clean_staticdata(self)
|
|
|
|
|
|
|
|
local tmp, t = {}
|
|
|
|
|
|
|
|
for _,stat in pairs(self) do
|
|
|
|
|
|
|
|
t = type(stat)
|
|
|
|
|
|
|
|
if t ~= "function" and t ~= "nil" and t ~= "userdata"
|
|
|
|
and _ ~= "object" and _ ~= "_cmi_components" then
|
|
|
|
tmp[_] = self[_]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return tmp
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- get entity staticdata
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:mob_staticdata()
|
|
|
|
|
|
|
|
-- this handles mob count for mobs activated, unloaded, reloaded
|
|
|
|
if active_limit > 0 and self.active_toggle then
|
|
|
|
active_mobs = active_mobs + self.active_toggle
|
|
|
|
self.active_toggle = -self.active_toggle
|
|
|
|
--print("-- staticdata", active_mobs, active_limit, self.active_toggle)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- remove mob when out of range unless tamed
|
2024-12-19 12:55:40 +01:00
|
|
|
if remove_far and self.remove_ok
|
|
|
|
and self.type ~= "npc" and self.state ~= "attack"
|
|
|
|
and not self.tamed and self.lifetimer < 20000 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
--print("REMOVED " .. self.name)
|
|
|
|
|
|
|
|
remove_mob(self, true)
|
|
|
|
|
|
|
|
return minetest.serialize({remove_ok = true, static_save = true})
|
|
|
|
end
|
|
|
|
|
|
|
|
self.remove_ok = true
|
|
|
|
self.attack = nil
|
|
|
|
self.following = nil
|
|
|
|
self.state = "stand"
|
|
|
|
|
|
|
|
-- used to rotate older mobs
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.drawtype and self.drawtype == "side" then self.rotate = rad(90) end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if use_cmi then
|
2024-12-19 12:55:40 +01:00
|
|
|
self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
2024-12-19 12:55:40 +01:00
|
|
|
--[[
|
|
|
|
-- move existing variables to new table for future compatibility
|
|
|
|
-- using self.initial_properties lost some variables when backing up?!?
|
|
|
|
if not self.backup_properties then
|
|
|
|
|
|
|
|
self.backup_properties = {
|
|
|
|
hp_max = self.hp_max,
|
|
|
|
physical = self.physical,
|
|
|
|
collisionbox = self.collisionbox,
|
|
|
|
selectionbox = self.selectionbox,
|
|
|
|
visual = self.visual,
|
|
|
|
visual_size = self.visual_size,
|
|
|
|
mesh = self.mesh,
|
|
|
|
textures = self.textures,
|
|
|
|
make_footstep_sound = self.make_footstep_sound,
|
|
|
|
stepheight = self.stepheight,
|
|
|
|
glow = self.glow,
|
|
|
|
-- nametag = self.nametag,
|
|
|
|
damage_texture_modifier = self.damage_texture_modifier,
|
|
|
|
-- infotext = self.infotext
|
|
|
|
}
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
2024-12-19 12:55:40 +01:00
|
|
|
]]
|
|
|
|
return minetest.serialize(clean_staticdata(self))
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- list of items used in initial_properties
|
|
|
|
|
|
|
|
local is_property_name = {
|
|
|
|
hp_max = true, physical = true, collide_with_objects = true, collisionbox = true,
|
|
|
|
selectionbox = true, pointable = true, visual_size = true, textures = true,
|
|
|
|
is_visible = true, stepheight = true, glow = true, show_on_minimap = true
|
|
|
|
}
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- activate mob and reload settings
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:mob_activate(staticdata, def, dtime)
|
|
|
|
|
|
|
|
-- if dtime == 0 then entity has just been created
|
|
|
|
-- anything higher means it is respawning (thanks SorceryKid)
|
2024-12-19 12:55:40 +01:00
|
|
|
if dtime == 0 and active_limit > 0 then self.active_toggle = 1 end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- remove mob if not tamed and mob total reached
|
2024-12-19 12:55:40 +01:00
|
|
|
if at_limit() and not self.tamed then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
remove_mob(self)
|
|
|
|
--print("-- mob limit reached, removing " .. self.name)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- remove monsters in peaceful mode
|
|
|
|
if self.type == "monster" and peaceful_only then
|
2024-12-19 12:55:40 +01:00
|
|
|
remove_mob(self, true) ; return
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- load entity variables from staticdata into self.
|
2020-10-26 17:38:53 +01:00
|
|
|
local tmp = minetest.deserialize(staticdata)
|
|
|
|
|
|
|
|
if tmp then
|
|
|
|
|
|
|
|
local t
|
|
|
|
|
|
|
|
for _,stat in pairs(tmp) do
|
|
|
|
|
|
|
|
t = type(stat)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if t ~= "function" and t ~= "nil" and t ~= "userdata" then
|
|
|
|
|
|
|
|
if is_property_name[_] then
|
|
|
|
|
|
|
|
self.object:set_properties({[_] = stat})
|
|
|
|
else
|
|
|
|
self[_] = stat
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local prop = self.object:get_properties()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- select random texture, set model and size
|
|
|
|
if not self.base_texture then
|
|
|
|
|
|
|
|
-- compatiblity with old simple mobs textures
|
|
|
|
if def.textures and type(def.textures[1]) == "string" then
|
|
|
|
def.textures = {def.textures}
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- backup a few base settings
|
2021-04-05 14:46:21 +02:00
|
|
|
self.base_texture = def.textures and def.textures[random(#def.textures)]
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- get texture, model and size
|
2020-10-26 17:38:53 +01:00
|
|
|
local textures = self.base_texture
|
|
|
|
local mesh = self.base_mesh
|
|
|
|
local vis_size = self.base_size
|
|
|
|
local colbox = self.base_colbox
|
|
|
|
local selbox = self.base_selbox
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- is there a specific texture if gotten
|
|
|
|
if self.gotten and def.gotten_texture then textures = def.gotten_texture end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- specific mesh if gotten
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.gotten and def.gotten_mesh then mesh = def.gotten_mesh end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- set child objects to half size
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.child then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
vis_size = {x = self.base_size.x * .5, y = self.base_size.y * .5}
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if def.child_texture then textures = def.child_texture[1] end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
colbox = {
|
|
|
|
self.base_colbox[1] * .5, self.base_colbox[2] * .5,
|
|
|
|
self.base_colbox[3] * .5, self.base_colbox[4] * .5,
|
|
|
|
self.base_colbox[5] * .5, self.base_colbox[6] * .5}
|
|
|
|
|
|
|
|
selbox = {
|
|
|
|
self.base_selbox[1] * .5, self.base_selbox[2] * .5,
|
|
|
|
self.base_selbox[3] * .5, self.base_selbox[4] * .5,
|
|
|
|
self.base_selbox[5] * .5, self.base_selbox[6] * .5}
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- set mob size and textures
|
|
|
|
self.object:set_properties({textures = textures, visual_size = vis_size,
|
|
|
|
collisionbox = colbox, selectionbox = selbox})
|
|
|
|
|
|
|
|
if self.health == 0 then self.health = random(self.hp_min, prop.hp_max) end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- pathfinding init
|
|
|
|
self.path = {}
|
|
|
|
self.path.way = {} -- path to follow, table of positions
|
|
|
|
self.path.lastpos = {x = 0, y = 0, z = 0}
|
|
|
|
self.path.stuck = false
|
|
|
|
self.path.following = false -- currently following path?
|
|
|
|
self.path.stuck_timer = 0 -- if stuck for too long search for path
|
|
|
|
|
|
|
|
-- Armor groups (immortal = 1 for custom damage handling)
|
|
|
|
local armor
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
if type(self.armor) == "table" then
|
|
|
|
armor = table_copy(self.armor)
|
|
|
|
else
|
2024-12-19 12:55:40 +01:00
|
|
|
armor = {fleshy = self.armor, immortal = 1}
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
self.object:set_armor_groups(armor)
|
|
|
|
|
|
|
|
-- mob defaults
|
|
|
|
self.old_y = self.object:get_pos().y
|
|
|
|
self.old_health = self.health
|
|
|
|
self.textures = textures
|
|
|
|
self.standing_in = "air"
|
|
|
|
self.standing_on = "air"
|
2024-12-19 12:55:40 +01:00
|
|
|
self.state = self.state or "stand"
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- set anything changed above
|
|
|
|
self:set_yaw((random(0, 360) - 180) / 180 * pi, 6)
|
|
|
|
self:set_animation("stand")
|
|
|
|
|
|
|
|
-- apply any texture mods
|
|
|
|
self.object:set_texture_mod(self.texture_mods)
|
|
|
|
|
|
|
|
-- set 5.x flag to remove monsters when map area unloaded
|
2021-07-31 22:19:53 +02:00
|
|
|
if remove_far and self.type == "monster" and not self.tamed then
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:set_properties({static_save = false})
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- run on_spawn function if found
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.on_spawn and not self.on_spawn_run and self.on_spawn(self) then
|
|
|
|
self.on_spawn_run = true -- if true, set flag to run once only
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- run after_activate
|
|
|
|
if def.after_activate then
|
|
|
|
def.after_activate(self, staticdata, def, dtime)
|
|
|
|
end
|
|
|
|
|
|
|
|
if use_cmi then
|
2024-12-19 12:55:40 +01:00
|
|
|
self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
|
2020-10-26 17:38:53 +01:00
|
|
|
cmi.notify_activate(self.object, dtime)
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if use_vh1 then VH1.update_bar(self.object, self.health) end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- handle mob lifetimer and expiration
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:mob_expire(pos, dtime)
|
|
|
|
|
|
|
|
-- when lifetimer expires remove mob (except npc and tamed)
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.type ~= "npc" and not self.tamed and self.state ~= "attack"
|
|
|
|
and remove_far ~= true and self.lifetimer < 20000 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.lifetimer = self.lifetimer - dtime
|
|
|
|
|
|
|
|
if self.lifetimer <= 0 then
|
|
|
|
|
|
|
|
-- only despawn away from player
|
2024-12-19 12:55:40 +01:00
|
|
|
for _,player in pairs(minetest.get_connected_players()) do
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if get_distance(player:get_pos(), pos) <= 15 then
|
2020-10-26 17:38:53 +01:00
|
|
|
self.lifetimer = 20
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- minetest.log("action", "lifetimer expired, removed " .. self.name)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
remove_mob(self, true) ; return
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- get nodes mob is standing on, in, facing, facing above
|
|
|
|
|
|
|
|
function mob_class:get_nodes()
|
|
|
|
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
local yaw = self.object:get_yaw()
|
|
|
|
local prop = self.object:get_properties()
|
|
|
|
|
|
|
|
-- child mobs have a lower y_level
|
|
|
|
local y_level = self.child and prop.collisionbox[2] * 0.5 or prop.collisionbox[2]
|
|
|
|
|
|
|
|
self.standing_in = node_ok(
|
|
|
|
{x = pos.x, y = pos.y + y_level + 0.25, z = pos.z}, "air").name
|
|
|
|
|
|
|
|
self.standing_on = node_ok(
|
|
|
|
{x = pos.x, y = pos.y + y_level - 0.25, z = pos.z}, "air").name
|
|
|
|
|
|
|
|
-- find front position
|
|
|
|
local dir_x = -sin(yaw) * (prop.collisionbox[4] + 0.5)
|
|
|
|
local dir_z = cos(yaw) * (prop.collisionbox[4] + 0.5)
|
|
|
|
|
|
|
|
-- nodes in front of mob and front/above
|
|
|
|
self.looking_at = node_ok(
|
|
|
|
{x = pos.x + dir_x, y = pos.y + y_level + 0.25, z = pos.z + dir_z}).name
|
|
|
|
|
|
|
|
self.looking_above = node_ok(
|
|
|
|
{x = pos.x + dir_x, y = pos.y + y_level + 1.25, z = pos.z + dir_z}).name
|
|
|
|
|
|
|
|
-- are we facing a fence or wall
|
|
|
|
if self.looking_at:find("fence") or self.looking_at:find("gate")
|
|
|
|
or self.looking_at:find("wall") then
|
|
|
|
self.facing_fence = true
|
|
|
|
else
|
|
|
|
self.facing_fence = nil
|
|
|
|
end
|
|
|
|
--[[
|
|
|
|
print("on: " .. self.standing_on
|
|
|
|
.. ", front: " .. self.looking_at
|
|
|
|
.. ", front above: " .. self.looking_above
|
|
|
|
.. ", fence: " .. (self.facing_fence and "yes" or "no"))
|
|
|
|
]]
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- main mob function
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:on_step(dtime, moveresult)
|
|
|
|
|
2021-06-27 17:39:50 +02:00
|
|
|
if self.state == "die" then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if use_cmi then cmi.notify_step(self.object, dtime) end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
local yaw = self.object:get_yaw()
|
|
|
|
|
|
|
|
-- early warning check, if no yaw then no entity, skip rest of function
|
|
|
|
if not yaw then return end
|
|
|
|
|
|
|
|
self.node_timer = (self.node_timer or 0) + dtime
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- get nodes every 1/4 second
|
|
|
|
if self.node_timer > node_timer_interval then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- get nodes above, below, in front and front-above
|
|
|
|
self:get_nodes()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.node_timer = 0
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- check and stop if standing at cliff and fear of heights
|
|
|
|
self.at_cliff = self:is_at_cliff()
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.pause_timer <= 0 and self.at_cliff then self:set_velocity(0) end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- has mob expired (0.25 instead of dtime since were in a timer)
|
2024-12-19 12:55:40 +01:00
|
|
|
self:mob_expire(pos, node_timer_interval)
|
|
|
|
|
|
|
|
-- check if mob can jump or is blocked facing fence/gate etc.
|
|
|
|
self:do_jump()
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- check if falling, flying, floating and return if player died
|
2024-12-19 12:55:40 +01:00
|
|
|
if self:falling(pos) then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- smooth rotation by ThomasMonroe314
|
|
|
|
if self.delay and self.delay > 0 then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.delay == 1 then yaw = self.target_yaw
|
2020-10-26 17:38:53 +01:00
|
|
|
else
|
|
|
|
local dif = abs(yaw - self.target_yaw)
|
|
|
|
|
|
|
|
if yaw > self.target_yaw then
|
|
|
|
|
|
|
|
if dif > pi then
|
2024-12-19 12:55:40 +01:00
|
|
|
dif = 2 * pi - dif
|
|
|
|
yaw = yaw + dif / self.delay -- need to add
|
2020-10-26 17:38:53 +01:00
|
|
|
else
|
|
|
|
yaw = yaw - dif / self.delay -- need to subtract
|
|
|
|
end
|
|
|
|
|
|
|
|
elseif yaw < self.target_yaw then
|
|
|
|
|
|
|
|
if dif > pi then
|
|
|
|
dif = 2 * pi - dif
|
|
|
|
yaw = yaw - dif / self.delay -- need to subtract
|
|
|
|
else
|
|
|
|
yaw = yaw + dif / self.delay -- need to add
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if yaw > (pi * 2) then yaw = yaw - (pi * 2) end
|
|
|
|
if yaw < 0 then yaw = yaw + (pi * 2) end
|
|
|
|
end
|
|
|
|
|
|
|
|
self.delay = self.delay - 1
|
|
|
|
self.object:set_yaw(yaw)
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- environmental damage timer (every 1 second)
|
|
|
|
self.env_damage_timer = self.env_damage_timer + dtime
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.env_damage_timer > 1 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.env_damage_timer = 0
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- check for environmental damage (water, fire, lava etc.)
|
|
|
|
if self:do_env_damage() then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- node replace check (cow eats grass etc.)
|
|
|
|
self:replace(pos)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- knockback timer
|
|
|
|
if self.pause_timer > 0 then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.pause_timer = self.pause_timer - dtime
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.pause_timer <= 0 and (self.order == "stand" or self.state == "stand") then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.pause_timer = 0
|
|
|
|
self:set_velocity(0)
|
|
|
|
self:set_animation("stand", true)
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
return
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- run custom function (defined in mob lua file) - when false skip going any further
|
|
|
|
if self.do_custom and self:do_custom(dtime, moveresult) == false then
|
|
|
|
return
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.timer = self.timer + dtime
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- never go over 100
|
|
|
|
if self.timer > 100 then self.timer = 1 end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- when attacking call do_states live (return if dead)
|
|
|
|
if self.state == "attack" and self:do_states(dtime) then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- one second timed calls
|
|
|
|
self.timer1 = (self.timer1 or 0) + dtime
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.timer1 >= main_timer_interval then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- mob plays random sound at times
|
|
|
|
if random(100) == 1 then self:mob_sound(self.sounds.random) end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:general_attack()
|
|
|
|
self:breed()
|
|
|
|
self:follow_flop()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- when not attacking call do_states every second (return if dead)
|
|
|
|
if self.state ~= "attack" then
|
|
|
|
if self:do_states(main_timer_interval) then return end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:do_runaway_from(self)
|
|
|
|
self:do_stay_near()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.timer1 = 0
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- default function when mobs are blown up with TNT
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mob_class:on_blast(damage)
|
|
|
|
|
|
|
|
--print("-- blast damage", damage)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:punch(self.object, 1.0,
|
|
|
|
{full_punch_interval = 1.0, damage_groups = {fleshy = damage}}, nil)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- return no damage, no knockback, no item drops, mob api handles all
|
|
|
|
return false, false, {}
|
|
|
|
end
|
|
|
|
|
|
|
|
-- register mob entity
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mobs:register_mob(name, def)
|
|
|
|
|
|
|
|
mobs.spawning_mobs[name] = {}
|
|
|
|
|
2021-06-12 20:20:04 +02:00
|
|
|
local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25}
|
|
|
|
|
|
|
|
-- quick fix to stop mobs glitching through nodes if too small
|
2024-12-19 12:55:40 +01:00
|
|
|
if mob_height_fix and -collisionbox[2] + collisionbox[5] < 1.01 then
|
2021-06-12 20:20:04 +02:00
|
|
|
collisionbox[5] = collisionbox[2] + 0.99
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
minetest.register_entity(":" .. name, setmetatable({
|
|
|
|
|
|
|
|
initial_properties = {
|
|
|
|
hp_max = max(1, (def.hp_max or 10) * difficulty),
|
|
|
|
physical = true,
|
|
|
|
collisionbox = collisionbox,
|
|
|
|
selectionbox = def.selectionbox or collisionbox,
|
|
|
|
visual = def.visual,
|
|
|
|
visual_size = def.visual_size or {x = 1, y = 1},
|
|
|
|
mesh = def.mesh,
|
|
|
|
textures = "",
|
|
|
|
makes_footstep_sound = def.makes_footstep_sound,
|
|
|
|
stepheight = def.stepheight or 1.1,
|
|
|
|
glow = def.glow,
|
|
|
|
damage_texture_modifier = def.damage_texture_modifier or "^[colorize:#c9900070",
|
|
|
|
},
|
|
|
|
|
|
|
|
name = name,
|
|
|
|
type = def.type,
|
|
|
|
_nametag = def.nametag,
|
|
|
|
attack_type = def.attack_type,
|
|
|
|
fly = def.fly,
|
|
|
|
fly_in = def.fly_in,
|
|
|
|
keep_flying = def.keep_flying,
|
|
|
|
owner = def.owner,
|
|
|
|
order = def.order,
|
|
|
|
jump_height = def.jump_height,
|
|
|
|
can_leap = def.can_leap,
|
|
|
|
drawtype = def.drawtype, -- DEPRECATED, use rotate instead
|
|
|
|
rotate = rad(def.rotate or 0), -- 0=front 90=side 180=back 270=side2
|
|
|
|
lifetimer = def.lifetimer,
|
|
|
|
hp_min = max(1, (def.hp_min or 5) * difficulty),
|
|
|
|
|
|
|
|
-- backup entity model and size
|
|
|
|
base_mesh = def.mesh,
|
|
|
|
base_colbox = collisionbox,
|
|
|
|
base_selbox = def.selectionbox or collisionbox,
|
|
|
|
base_size = def.visual_size or {x = 1, y = 1},
|
|
|
|
|
|
|
|
view_range = def.view_range,
|
|
|
|
walk_velocity = def.walk_velocity,
|
|
|
|
run_velocity = def.run_velocity,
|
|
|
|
damage = max(0, (def.damage or 0) * difficulty),
|
|
|
|
damage_group = def.damage_group,
|
|
|
|
light_damage = def.light_damage,
|
|
|
|
light_damage_min = def.light_damage_min,
|
|
|
|
light_damage_max = def.light_damage_max,
|
|
|
|
water_damage = def.water_damage,
|
|
|
|
lava_damage = def.lava_damage,
|
|
|
|
fire_damage = def.fire_damage,
|
|
|
|
air_damage = def.air_damage,
|
|
|
|
node_damage = def.node_damage,
|
|
|
|
suffocation = def.suffocation,
|
|
|
|
fall_damage = def.fall_damage,
|
|
|
|
fall_speed = def.fall_speed,
|
|
|
|
drops = def.drops,
|
|
|
|
armor = def.armor,
|
|
|
|
arrow = def.arrow,
|
|
|
|
arrow_override = def.arrow_override,
|
|
|
|
shoot_interval = def.shoot_interval,
|
|
|
|
homing = def.homing,
|
|
|
|
sounds = def.sounds,
|
|
|
|
animation = def.animation,
|
|
|
|
follow = def.follow,
|
|
|
|
jump = def.jump,
|
|
|
|
walk_chance = def.walk_chance,
|
|
|
|
stand_chance = def.stand_chance,
|
|
|
|
attack_chance = def.attack_chance,
|
|
|
|
attack_patience = def.attack_patience,
|
|
|
|
passive = def.passive,
|
|
|
|
knock_back = def.knock_back,
|
|
|
|
blood_amount = def.blood_amount,
|
|
|
|
blood_texture = def.blood_texture,
|
|
|
|
shoot_offset = def.shoot_offset,
|
|
|
|
floats = def.floats,
|
|
|
|
replace_rate = def.replace_rate,
|
|
|
|
replace_what = def.replace_what,
|
|
|
|
replace_with = def.replace_with,
|
|
|
|
replace_offset = def.replace_offset,
|
|
|
|
reach = def.reach,
|
|
|
|
texture_list = def.textures,
|
|
|
|
texture_mods = def.texture_mods or "",
|
|
|
|
child_texture = def.child_texture,
|
|
|
|
docile_by_day = def.docile_by_day,
|
|
|
|
fear_height = def.fear_height,
|
|
|
|
runaway = def.runaway,
|
|
|
|
pathfinding = def.pathfinding,
|
|
|
|
immune_to = def.immune_to,
|
|
|
|
explosion_radius = def.explosion_radius,
|
|
|
|
explosion_damage_radius = def.explosion_damage_radius,
|
|
|
|
explosion_timer = def.explosion_timer,
|
|
|
|
allow_fuse_reset = def.allow_fuse_reset,
|
|
|
|
stop_to_explode = def.stop_to_explode,
|
|
|
|
double_melee_attack = def.double_melee_attack,
|
|
|
|
dogshoot_switch = def.dogshoot_switch,
|
|
|
|
dogshoot_count_max = def.dogshoot_count_max,
|
|
|
|
dogshoot_count2_max = def.dogshoot_count2_max or def.dogshoot_count_max,
|
|
|
|
group_attack = def.group_attack,
|
|
|
|
group_helper = def.group_helper,
|
|
|
|
attack_monsters = def.attacks_monsters or def.attack_monsters,
|
|
|
|
attack_animals = def.attack_animals,
|
|
|
|
attack_players = def.attack_players,
|
|
|
|
attack_npcs = def.attack_npcs,
|
|
|
|
attack_ignore = def.attack_ignore,
|
|
|
|
specific_attack = def.specific_attack,
|
|
|
|
friendly_fire = def.friendly_fire,
|
|
|
|
runaway_from = def.runaway_from,
|
|
|
|
owner_loyal = def.owner_loyal,
|
|
|
|
pushable = def.pushable,
|
|
|
|
stay_near = def.stay_near,
|
|
|
|
randomly_turn = def.randomly_turn ~= false,
|
|
|
|
ignore_invisibility = def.ignore_invisibility,
|
|
|
|
messages = def.messages,
|
|
|
|
|
|
|
|
on_rightclick = def.on_rightclick,
|
|
|
|
on_die = def.on_die,
|
|
|
|
on_death = def.on_death, -- engine function for entity death
|
|
|
|
on_flop = def.on_flop,
|
|
|
|
do_custom = def.do_custom,
|
|
|
|
on_replace = def.on_replace,
|
|
|
|
custom_attack = def.custom_attack,
|
|
|
|
on_spawn = def.on_spawn,
|
|
|
|
on_blast = def.on_blast, -- class redifinition
|
|
|
|
do_punch = def.do_punch,
|
|
|
|
on_breed = def.on_breed,
|
|
|
|
on_grown = def.on_grown,
|
|
|
|
on_sound = def.on_sound,
|
|
|
|
|
|
|
|
is_mob = true, _hittable_by_projectile = true, -- mineclone thing
|
|
|
|
|
|
|
|
on_activate = function(self, staticdata, dtime)
|
|
|
|
return self:mob_activate(staticdata, def, dtime)
|
|
|
|
end,
|
|
|
|
|
|
|
|
get_staticdata = function(self)
|
|
|
|
return self:mob_staticdata(self)
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
}, mob_class_meta))
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- count how many mobs of one type are inside an area
|
|
|
|
-- will also return true for second value if player is inside area
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
local function count_mobs(pos, type)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local total = 0
|
|
|
|
local objs = minetest.get_objects_inside_radius(pos, aoc_range * 2)
|
2024-12-19 12:55:40 +01:00
|
|
|
local ent, players
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
for n = 1, #objs do
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not is_player(objs[n]) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
ent = objs[n]:get_luaentity()
|
|
|
|
|
|
|
|
-- count mob type and add to total also
|
2024-12-19 12:55:40 +01:00
|
|
|
if ent and ent.name and ent.name == type then total = total + 1 end
|
2020-10-26 17:38:53 +01:00
|
|
|
else
|
|
|
|
players = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return total, players
|
|
|
|
end
|
|
|
|
|
2020-10-29 20:21:53 +01:00
|
|
|
-- do we have enough space to spawn mob? (thanks wuzzy)
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
local function can_spawn(pos, name)
|
2020-10-29 20:21:53 +01:00
|
|
|
|
|
|
|
local ent = minetest.registered_entities[name]
|
2024-12-19 12:55:40 +01:00
|
|
|
local width_x = max(1, ceil(ent.base_colbox[4] - ent.base_colbox[1]))
|
2020-10-29 20:21:53 +01:00
|
|
|
local min_x, max_x
|
|
|
|
|
|
|
|
if width_x % 2 == 0 then
|
2024-12-19 12:55:40 +01:00
|
|
|
max_x = floor(width_x / 2) ; min_x = -(max_x - 1)
|
2020-10-29 20:21:53 +01:00
|
|
|
else
|
2024-12-19 12:55:40 +01:00
|
|
|
max_x = floor(width_x / 2) ; min_x = -max_x
|
2020-10-29 20:21:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local width_z = max(1, ceil(ent.base_colbox[6] - ent.base_colbox[3]))
|
2020-10-29 20:21:53 +01:00
|
|
|
local min_z, max_z
|
|
|
|
|
|
|
|
if width_z % 2 == 0 then
|
2024-12-19 12:55:40 +01:00
|
|
|
max_z = floor(width_z / 2) ; min_z = -(max_z - 1)
|
2020-10-29 20:21:53 +01:00
|
|
|
else
|
2024-12-19 12:55:40 +01:00
|
|
|
max_z = floor(width_z / 2) ; min_z = -max_z
|
2020-10-29 20:21:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local max_y = max(0, ceil(ent.base_colbox[5] - ent.base_colbox[2]) - 1)
|
2020-10-29 20:21:53 +01:00
|
|
|
local pos2
|
|
|
|
|
|
|
|
for y = 0, max_y do
|
|
|
|
for x = min_x, max_x do
|
|
|
|
for z = min_z, max_z do
|
|
|
|
|
|
|
|
pos2 = {x = pos.x + x, y = pos.y + y, z = pos.z + z}
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if minetest.registered_nodes[node_ok(pos2).name].walkable then
|
2020-10-29 20:21:53 +01:00
|
|
|
return nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- tweak X/Z spawn pos
|
2024-12-19 12:55:40 +01:00
|
|
|
if width_x % 2 == 0 then pos.x = pos.x + 0.5 end
|
2020-10-29 20:21:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if width_z % 2 == 0 then pos.z = pos.z + 0.5 end
|
2020-10-29 20:21:53 +01:00
|
|
|
|
|
|
|
return pos
|
|
|
|
end
|
|
|
|
|
|
|
|
function mobs:can_spawn(pos, name)
|
|
|
|
return can_spawn(pos, name)
|
|
|
|
end
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- global functions
|
|
|
|
|
|
|
|
function mobs:add_mob(pos, def)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- nil check
|
|
|
|
if not pos or not def then
|
|
|
|
--print("--- no position or definition given")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- is mob actually registered?
|
2024-12-19 12:55:40 +01:00
|
|
|
if not mobs.spawning_mobs[def.name] or not minetest.registered_entities[def.name] then
|
2020-10-26 17:38:53 +01:00
|
|
|
--print("--- mob doesn't exist", def.name)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- are we over active mob limit
|
2024-12-19 12:55:40 +01:00
|
|
|
if at_limit() then
|
2020-10-26 17:38:53 +01:00
|
|
|
--print("--- active mob limit reached", active_mobs, active_limit)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- get total number of this mob in area
|
|
|
|
local num_mob, is_pla = count_mobs(pos, def.name)
|
|
|
|
|
|
|
|
if not is_pla then
|
|
|
|
--print("--- no players within active area, will not spawn " .. def.name)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local aoc = mobs.spawning_mobs[def.name] and mobs.spawning_mobs[def.name].aoc or 1
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if def.ignore_count ~= true and num_mob >= aoc then
|
|
|
|
--print("--- too many " .. def.name .. " in area", num_mob .. "/" .. aoc)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local mob = minetest.add_entity(pos, def.name)
|
|
|
|
|
|
|
|
--print("[mobs] Spawned " .. def.name .. " at " .. minetest.pos_to_string(pos))
|
|
|
|
|
|
|
|
local ent = mob:get_luaentity()
|
|
|
|
|
|
|
|
if not ent then
|
|
|
|
--print("[mobs] entity not found " .. def.name)
|
|
|
|
return false
|
2024-12-19 12:55:40 +01:00
|
|
|
else
|
|
|
|
effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- use new texture if found
|
|
|
|
local new_texture = def.texture or ent.base_texture
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
if def.child then
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
ent.mommy_tex = new_texture -- how baby looks when grown
|
|
|
|
ent.base_texture = new_texture
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- using specific child texture (if found)
|
|
|
|
if ent.child_texture then
|
2024-12-19 12:55:40 +01:00
|
|
|
new_texture = ent.child_texture[1]
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- and resize to half height (multiplication is faster than division)
|
2020-10-26 17:38:53 +01:00
|
|
|
mob:set_properties({
|
2024-12-19 12:55:40 +01:00
|
|
|
textures = new_texture,
|
2020-10-26 17:38:53 +01:00
|
|
|
visual_size = {
|
2024-12-19 12:55:40 +01:00
|
|
|
x = ent.base_size.x * .5, y = ent.base_size.y * .5
|
2020-10-26 17:38:53 +01:00
|
|
|
},
|
|
|
|
collisionbox = {
|
2024-12-19 12:55:40 +01:00
|
|
|
ent.base_colbox[1] * .5, ent.base_colbox[2] * .5,
|
|
|
|
ent.base_colbox[3] * .5, ent.base_colbox[4] * .5,
|
|
|
|
ent.base_colbox[5] * .5, ent.base_colbox[6] * .5
|
2020-10-26 17:38:53 +01:00
|
|
|
},
|
|
|
|
selectionbox = {
|
2024-12-19 12:55:40 +01:00
|
|
|
ent.base_selbox[1] * .5, ent.base_selbox[2] * .5,
|
|
|
|
ent.base_selbox[3] * .5, ent.base_selbox[4] * .5,
|
|
|
|
ent.base_selbox[5] * .5, ent.base_selbox[6] * .5
|
|
|
|
}
|
2020-10-26 17:38:53 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
ent.child = true
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
-- if not child set new texture
|
|
|
|
elseif def.texture then
|
|
|
|
|
|
|
|
ent.base_texture = new_texture
|
|
|
|
|
|
|
|
mob:set_properties({textures = new_texture})
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
if def.owner then
|
|
|
|
ent.tamed = true
|
|
|
|
ent.owner = def.owner
|
|
|
|
end
|
|
|
|
|
|
|
|
if def.nametag then
|
|
|
|
|
|
|
|
-- limit name entered to 64 characters long
|
|
|
|
if def.nametag:len() > 64 then
|
|
|
|
def.nametag = def.nametag:sub(1, 64)
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
ent:update_tag(def.nametag)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
return ent
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- global function to add additional spawn checks
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
function mobs:spawn_abm_check(pos, node, name)
|
|
|
|
-- return true to stop spawning mob
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- older spawning function
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval,
|
|
|
|
chance, aoc, min_height, max_height, day_toggle, on_spawn, map_load)
|
|
|
|
|
|
|
|
-- Do mobs spawn at all?
|
|
|
|
if not mobs_spawn or not mobs.spawning_mobs[name] then
|
|
|
|
--print ("--- spawning not registered for " .. name)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- chance/spawn number override in minetest.conf for registered mob
|
|
|
|
local numbers = settings:get(name)
|
|
|
|
|
|
|
|
if numbers then
|
2021-06-27 17:39:50 +02:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
numbers = numbers:split(",")
|
|
|
|
chance = tonumber(numbers[1]) or chance
|
|
|
|
aoc = tonumber(numbers[2]) or aoc
|
|
|
|
|
|
|
|
if chance == 0 then
|
2021-06-27 17:39:50 +02:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
minetest.log("warning",
|
|
|
|
string.format("[mobs] %s has spawning disabled", name))
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
minetest.log("action", string.format(
|
|
|
|
"[mobs] Chance setting for %s changed to %s (total: %s)",
|
|
|
|
name, chance, aoc))
|
|
|
|
end
|
|
|
|
|
|
|
|
mobs.spawning_mobs[name].aoc = aoc
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local function spawn_action(pos, node, active_object_count, active_object_count_wider)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- do not spawn if too many entities in area
|
|
|
|
if active_object_count_wider and active_object_count_wider >= max_per_block then
|
|
|
|
--print("--- too many entities in area", active_object_count_wider)
|
2020-10-26 17:38:53 +01:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- when using lbm manually check for chance and neighbor
|
|
|
|
if map_load and (random(max(1, (chance * mob_chance_multiplier))) > 1
|
|
|
|
or not minetest.find_node_near(pos, 1, neighbors)) then
|
|
|
|
--print("-- lbm no chance or neighbor not found")
|
2020-10-26 17:38:53 +01:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local ent = minetest.registered_entities[name]
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
-- is mob actually registered?
|
2024-12-19 12:55:40 +01:00
|
|
|
if not mobs.spawning_mobs[name] or not ent then
|
2020-10-26 17:38:53 +01:00
|
|
|
--print("--- mob doesn't exist", name)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- are we over set mob limit
|
|
|
|
if at_limit() then
|
2020-10-26 17:38:53 +01:00
|
|
|
--print("--- active mob limit reached", active_mobs, active_limit)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- additional custom checks for mob spawning
|
|
|
|
if mobs:spawn_abm_check(pos, node, name) then
|
2020-10-26 17:38:53 +01:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- get total number of this mob in area
|
|
|
|
local num_mob, is_pla = count_mobs(pos, name)
|
|
|
|
|
|
|
|
if not is_pla then
|
|
|
|
--print("--- no players within active area, will not spawn " .. name)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if num_mob >= aoc then
|
|
|
|
--print("--- too many " .. name .. " in area", num_mob .. "/" .. aoc)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- if toggle set to nil then ignore day/night check
|
|
|
|
if day_toggle ~= nil then
|
|
|
|
|
|
|
|
local tod = (minetest.get_timeofday() or 0) * 24000
|
|
|
|
|
|
|
|
if tod > 4500 and tod < 19500 then
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
if day_toggle == false then -- daylight, but mob wants night
|
2020-10-26 17:38:53 +01:00
|
|
|
--print("--- mob needs night", name)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
else
|
2024-12-19 12:55:40 +01:00
|
|
|
if day_toggle then -- night time but mob wants day
|
2020-10-26 17:38:53 +01:00
|
|
|
--print("--- mob needs day", name)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- change position to node above
|
2020-10-26 17:38:53 +01:00
|
|
|
pos.y = pos.y + 1
|
|
|
|
|
|
|
|
-- are we spawning within height limits?
|
2024-12-19 12:55:40 +01:00
|
|
|
if pos.y > max_height or pos.y < min_height then
|
2020-10-26 17:38:53 +01:00
|
|
|
--print("--- height limits not met", name, pos.y)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- are light levels ok?
|
|
|
|
local light = minetest.get_node_light(pos)
|
2024-12-19 12:55:40 +01:00
|
|
|
if not light or light > max_light or light < min_light then
|
2020-10-26 17:38:53 +01:00
|
|
|
--print("--- light limits not met", name, light)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2021-07-24 12:22:10 +02:00
|
|
|
-- check if mob can spawn inside protected areas
|
|
|
|
if (spawn_protected == false
|
|
|
|
or (spawn_monster_protected == false
|
|
|
|
and minetest.registered_entities[name].type == "monster"))
|
2020-10-26 17:38:53 +01:00
|
|
|
and minetest.is_protected(pos, "") then
|
|
|
|
--print("--- inside protected area", name)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- only spawn a set distance away from player
|
2024-12-19 12:55:40 +01:00
|
|
|
for _,player in pairs(minetest.get_connected_players()) do
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if get_distance(player:get_pos(), pos) <= mob_nospawn_range then
|
2020-10-26 17:38:53 +01:00
|
|
|
--print("--- player too close", name)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-12-02 17:45:37 +01:00
|
|
|
-- should we check mob area for obstructions ?
|
|
|
|
if mob_area_spawn ~= true then
|
|
|
|
|
|
|
|
-- do we have enough height clearance to spawn mob?
|
2024-12-19 12:55:40 +01:00
|
|
|
local height = max(0, ent.base_colbox[5] - ent.base_colbox[2])
|
2020-12-02 17:45:37 +01:00
|
|
|
|
2021-01-16 20:08:32 +01:00
|
|
|
for n = 0, floor(height) do
|
2020-12-02 17:45:37 +01:00
|
|
|
|
|
|
|
local pos2 = {x = pos.x, y = pos.y + n, z = pos.z}
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if minetest.registered_nodes[node_ok(pos2).name].walkable then
|
2020-12-02 17:45:37 +01:00
|
|
|
--print ("--- inside block", name, node_ok(pos2).name)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- returns position if we have enough space to spawn mob
|
|
|
|
pos = can_spawn(pos, name)
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2020-10-29 20:21:53 +01:00
|
|
|
if pos then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- get mob collisionbox and determine y_offset when spawning
|
|
|
|
local _prop = ent and ent.initial_properties or {}
|
|
|
|
local _y = _prop.collisionbox and -_prop.collisionbox[2] or 1
|
|
|
|
|
|
|
|
pos.y = pos.y + _y
|
2021-01-16 20:08:32 +01:00
|
|
|
|
2020-10-29 20:21:53 +01:00
|
|
|
local mob = minetest.add_entity(pos, name)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if mob_log_spawn then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local pos_string = pos and minetest.pos_to_string(pos) or ""
|
|
|
|
|
|
|
|
minetest.log("[MOBS] Spawned " .. (name or "") .. " at " .. pos_string)
|
2020-10-29 20:21:53 +01:00
|
|
|
end
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
if on_spawn and mob then on_spawn(mob:get_luaentity(), pos) end
|
2020-10-29 20:21:53 +01:00
|
|
|
else
|
|
|
|
--print("--- not enough space to spawn", name)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- are we registering an abm or lbm?
|
2024-12-19 12:55:40 +01:00
|
|
|
if map_load then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
minetest.register_lbm({
|
|
|
|
name = name .. "_spawning",
|
|
|
|
label = name .. " spawning",
|
|
|
|
nodenames = nodes,
|
|
|
|
run_at_every_load = false,
|
2024-12-19 12:55:40 +01:00
|
|
|
min_y = min_height, max_y = max_height,
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
action = function(pos, node)
|
|
|
|
spawn_action(pos, node)
|
|
|
|
end
|
|
|
|
})
|
|
|
|
else
|
|
|
|
minetest.register_abm({
|
|
|
|
label = name .. " spawning",
|
|
|
|
nodenames = nodes,
|
|
|
|
neighbors = neighbors,
|
|
|
|
interval = interval,
|
|
|
|
chance = max(1, (chance * mob_chance_multiplier)),
|
|
|
|
catch_up = false,
|
2024-12-19 12:55:40 +01:00
|
|
|
min_y = min_height, max_y = max_height,
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
action = function(pos, node, active_object_count, active_object_count_wider)
|
|
|
|
spawn_action(pos, node, active_object_count, active_object_count_wider)
|
|
|
|
end
|
|
|
|
})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- compatibility with older mob registration [DEPRECATED]
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
function mobs:register_spawn(name, nodes, max_light, min_light, chance,
|
|
|
|
active_object_count, max_height, day_toggle)
|
|
|
|
|
|
|
|
mobs:spawn_specific(name, nodes, {"air"}, min_light, max_light, 30,
|
|
|
|
chance, active_object_count, -31000, max_height, day_toggle)
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- MarkBu's newer spawn function (USE this one please modders)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
function mobs:spawn(def)
|
|
|
|
|
|
|
|
mobs:spawn_specific(
|
|
|
|
def.name,
|
|
|
|
def.nodes or {"group:soil", "group:stone"},
|
|
|
|
def.neighbors or {"air"},
|
|
|
|
def.min_light or 0,
|
|
|
|
def.max_light or 15,
|
|
|
|
def.interval or 30,
|
|
|
|
def.chance or 5000,
|
|
|
|
def.active_object_count or 1,
|
|
|
|
def.min_height or -31000,
|
|
|
|
def.max_height or 31000,
|
|
|
|
def.day_toggle,
|
|
|
|
def.on_spawn,
|
|
|
|
def.on_map_load)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- register arrow for shoot attack
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mobs:register_arrow(name, def)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not name or not def then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
minetest.register_entity(":" .. name, {
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
initial_properties = {
|
|
|
|
physical = def.physical,
|
|
|
|
collide_with_objects = def.collide_with_objects or false,
|
|
|
|
static_save = false,
|
|
|
|
visual = def.visual,
|
|
|
|
visual_size = def.visual_size,
|
|
|
|
textures = def.textures,
|
|
|
|
collisionbox = def.collisionbox or {-.1, -.1, -.1, .1, .1, .1},
|
|
|
|
glow = def.glow,
|
|
|
|
automatic_face_movement_dir = def.rotate
|
|
|
|
and (def.rotate - (pi / 180)) or false,
|
|
|
|
},
|
2021-05-16 11:33:49 +02:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
velocity = def.velocity,
|
|
|
|
hit_player = def.hit_player,
|
|
|
|
hit_node = def.hit_node,
|
|
|
|
hit_mob = def.hit_mob,
|
|
|
|
hit_object = def.hit_object,
|
2024-12-19 12:55:40 +01:00
|
|
|
drop = def.drop, -- chance of dropping arrow as registered item
|
2020-10-26 17:38:53 +01:00
|
|
|
timer = 0,
|
|
|
|
lifetime = def.lifetime or 4.5,
|
|
|
|
owner_id = def.owner_id,
|
|
|
|
rotate = def.rotate,
|
|
|
|
|
|
|
|
on_activate = def.on_activate,
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
on_punch = def.on_punch or function(self, hitter, tflp, tool_capabilities, dir)
|
2020-10-26 17:38:53 +01:00
|
|
|
end,
|
|
|
|
|
|
|
|
on_step = def.on_step or function(self, dtime)
|
|
|
|
|
|
|
|
self.timer = self.timer + dtime
|
|
|
|
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.timer > self.lifetime then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.object:remove() ; -- print("removed arrow")
|
|
|
|
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- does arrow have a tail (fireball)
|
|
|
|
if def.tail and def.tail == 1 and def.tail_texture then
|
|
|
|
|
|
|
|
minetest.add_particle({
|
|
|
|
pos = pos,
|
|
|
|
velocity = {x = 0, y = 0, z = 0},
|
|
|
|
acceleration = {x = 0, y = 0, z = 0},
|
|
|
|
expirationtime = def.expire or 0.25,
|
|
|
|
collisiondetection = false,
|
|
|
|
texture = def.tail_texture,
|
|
|
|
size = def.tail_size or 5,
|
|
|
|
glow = def.glow or 0
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- make homing arrows follow target if seen
|
|
|
|
if self._homing_target then
|
|
|
|
|
|
|
|
local p = self._homing_target:get_pos()
|
|
|
|
|
|
|
|
if p then
|
|
|
|
|
|
|
|
if minetest.line_of_sight(self.object:get_pos(), p) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:set_velocity(
|
|
|
|
vector.direction(self.object:get_pos(), p) * self.velocity)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
self._homing_target = nil
|
|
|
|
end
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- raycasting
|
|
|
|
self.lastpos = self.lastpos or pos
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local cast = minetest.raycast(self.lastpos, pos, true, true)
|
|
|
|
local thing = cast:next()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- loop through things
|
|
|
|
while thing do
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- if inside object that isn't arrow
|
|
|
|
if thing.type == "object" and thing.ref ~= self.object
|
|
|
|
and tostring(thing.ref) ~= self.owner_id then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.hit_player and is_player(thing.ref) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:hit_player(thing.ref)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:remove()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
--print("hit player", thing.ref:get_player_name())
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
return
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
local entity = thing.ref:get_luaentity()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if entity and self.hit_mob and entity._cmi_is_mob then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:hit_mob(thing.ref)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:remove()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
--print("hit mob", entity.name)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if entity and self.hit_object and (not entity._cmi_is_mob) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:hit_object(thing.ref)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self.object:remove()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
--print("hit object", entity.name)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- if inside node
|
|
|
|
if thing.type == "node" and self.hit_node then
|
|
|
|
|
|
|
|
local node = minetest.get_node(pos)
|
|
|
|
local def = minetest.registered_nodes[node.name]
|
|
|
|
|
|
|
|
if def and def.walkable then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:hit_node(pos, node)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if (type(self.drop) == "boolean" and self.drop)
|
|
|
|
or (type(self.drop) == "number" and random(self.drop) == 1) then
|
|
|
|
|
|
|
|
pos.y = pos.y + 1
|
|
|
|
|
|
|
|
minetest.add_item(self.lastpos,
|
|
|
|
self.object:get_luaentity().name)
|
|
|
|
end
|
|
|
|
|
|
|
|
self.object:remove()
|
|
|
|
|
|
|
|
--print("hit node", node.name)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
thing = cast:next()
|
|
|
|
|
|
|
|
end -- end thing loop
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.lastpos = pos
|
|
|
|
end
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
-- no damage to nodes explosion
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
function mobs:safe_boom(self, pos, radius, texture)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", {
|
|
|
|
pos = pos,
|
|
|
|
gain = 1.0,
|
2024-12-19 12:55:40 +01:00
|
|
|
max_hear_distance = (self.sounds and self.sounds.distance) or 32
|
2020-10-26 17:38:53 +01:00
|
|
|
}, true)
|
|
|
|
|
|
|
|
entity_physics(pos, radius)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
effect(pos, 32, texture, radius * 3, radius * 5, radius, 1, 0)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- make explosion with protection and tnt mod check
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mobs:boom(self, pos, node_damage_radius, entity_radius, texture)
|
|
|
|
|
|
|
|
texture = texture or "tnt_smoke.png"
|
|
|
|
|
|
|
|
if mobs_griefing and minetest.get_modpath("tnt") and tnt and tnt.boom
|
2020-10-26 17:38:53 +01:00
|
|
|
and not minetest.is_protected(pos, "") then
|
|
|
|
|
|
|
|
tnt.boom(pos, {
|
2024-12-19 12:55:40 +01:00
|
|
|
radius = node_damage_radius,
|
|
|
|
damage_radius = entity_radius,
|
2020-10-26 17:38:53 +01:00
|
|
|
sound = self.sounds and self.sounds.explode,
|
2024-12-19 12:55:40 +01:00
|
|
|
explode_center = true,
|
|
|
|
tiles = texture
|
2020-10-26 17:38:53 +01:00
|
|
|
})
|
|
|
|
else
|
2024-12-19 12:55:40 +01:00
|
|
|
mobs:safe_boom(self, pos, node_damage_radius, texture)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mobs:explosion(pos, radius) -- [deprecated] compatibility function
|
|
|
|
mobs:boom({sounds = {explode = "tnt_explode"}}, pos, radius, radius, "tnt_smoke.png")
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- Register spawn eggs - This also introduces the “spawn_egg” group:
|
|
|
|
-- * spawn_egg=1: generic mob, no metadata
|
|
|
|
-- * spawn_egg=2: captured/tamed mob, metadata
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
function mobs:register_egg(mob, desc, background, addegg, no_creative)
|
|
|
|
|
|
|
|
local grp = {spawn_egg = 1}
|
|
|
|
|
|
|
|
-- do NOT add this egg to creative inventory (e.g. dungeon master)
|
2024-12-19 12:55:40 +01:00
|
|
|
if no_creative then grp.not_in_creative_inventory = 1 end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local invimg = background
|
|
|
|
|
|
|
|
if addegg == 1 then
|
2024-12-19 12:55:40 +01:00
|
|
|
invimg = "mobs_chicken_egg.png^(" .. invimg .. "^[mask:mobs_chicken_egg_overlay.png)"
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- does mob/entity exist
|
|
|
|
local is_mob = minetest.registered_entities[mob]
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not is_mob then
|
|
|
|
print("[Mobs Redo] Spawn Egg cannot be created for " .. mob)
|
|
|
|
return
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- get mob collisionbox and determine y_offset when spawning
|
|
|
|
local _prop = is_mob and is_mob.initial_properties or {}
|
|
|
|
local _y = _prop.collisionbox and -_prop.collisionbox[2] or 1
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- register new spawn egg containing mob information (cannot be stacked)
|
|
|
|
-- these are only created for animals and npc's, not monsters
|
|
|
|
if is_mob.type ~= "monster" then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
minetest.register_craftitem(":" .. mob .. "_set", {
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
description = S("@1 (Tamed)", desc),
|
|
|
|
inventory_image = invimg,
|
|
|
|
groups = {spawn_egg = 2, not_in_creative_inventory = 1},
|
|
|
|
stack_max = 1,
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
on_place = function(itemstack, placer, pointed_thing)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local pos = pointed_thing.above
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- does existing on_rightclick function exist?
|
|
|
|
local under = minetest.get_node(pointed_thing.under)
|
|
|
|
local def = minetest.registered_nodes[under.name]
|
|
|
|
|
|
|
|
if def and def.on_rightclick then
|
|
|
|
|
|
|
|
return def.on_rightclick(
|
|
|
|
pointed_thing.under, under, placer, itemstack, pointed_thing)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if pos and not minetest.is_protected(pos, placer:get_player_name()) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- have we reached active mob limit
|
|
|
|
if at_limit() then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
minetest.chat_send_player(placer:get_player_name(),
|
|
|
|
S("Active Mob Limit Reached!")
|
|
|
|
.. " (" .. active_mobs
|
|
|
|
.. " / " .. active_limit .. ")")
|
|
|
|
return
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
pos.y = pos.y + _y
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local data = itemstack:get_metadata()
|
|
|
|
local smob = minetest.add_entity(pos, mob, data)
|
|
|
|
local ent = smob and smob:get_luaentity()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not ent then return end -- sanity check
|
|
|
|
|
|
|
|
-- set owner if not a monster
|
|
|
|
if ent.type ~= "monster" then
|
|
|
|
ent.owner = placer:get_player_name()
|
|
|
|
ent.tamed = true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- since mob is unique we remove egg once spawned
|
|
|
|
itemstack:take_item()
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
return itemstack
|
|
|
|
end
|
|
|
|
})
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- register old stackable mob egg
|
2024-12-19 12:55:40 +01:00
|
|
|
minetest.register_craftitem(":" .. mob, {
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
description = desc,
|
|
|
|
inventory_image = invimg,
|
|
|
|
groups = grp,
|
|
|
|
|
|
|
|
on_place = function(itemstack, placer, pointed_thing)
|
|
|
|
|
|
|
|
local pos = pointed_thing.above
|
|
|
|
|
|
|
|
-- does existing on_rightclick function exist?
|
|
|
|
local under = minetest.get_node(pointed_thing.under)
|
|
|
|
local def = minetest.registered_nodes[under.name]
|
|
|
|
|
|
|
|
if def and def.on_rightclick then
|
|
|
|
|
|
|
|
return def.on_rightclick(
|
2021-12-05 14:17:16 +01:00
|
|
|
pointed_thing.under, under, placer, itemstack, pointed_thing)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if pos and not minetest.is_protected(pos, placer:get_player_name()) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- have we reached active mob limit
|
2024-12-19 12:55:40 +01:00
|
|
|
if at_limit() then
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
minetest.chat_send_player(placer:get_player_name(),
|
|
|
|
S("Active Mob Limit Reached!")
|
|
|
|
.. " (" .. active_mobs
|
|
|
|
.. " / " .. active_limit .. ")")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
pos.y = pos.y + _y
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local smob = minetest.add_entity(pos, mob)
|
|
|
|
local ent = smob and smob:get_luaentity()
|
|
|
|
|
|
|
|
if not ent then return end -- sanity check
|
|
|
|
|
|
|
|
-- don't set owner if monster or sneak pressed
|
2024-12-19 12:55:40 +01:00
|
|
|
if ent.type ~= "monster" and not placer:get_player_control().sneak then
|
2020-10-26 17:38:53 +01:00
|
|
|
ent.owner = placer:get_player_name()
|
|
|
|
ent.tamed = true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- if not in creative then take item
|
|
|
|
if not mobs.is_creative(placer:get_player_name()) then
|
|
|
|
itemstack:take_item()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return itemstack
|
2024-12-19 12:55:40 +01:00
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
-- force capture a mob if space available in inventory, or drop as spawn egg
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mobs:force_capture(self, clicker)
|
|
|
|
|
|
|
|
-- add special mob egg with all mob information
|
|
|
|
local new_stack = ItemStack(self.name .. "_set")
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local data_str = minetest.serialize(clean_staticdata(self))
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
new_stack:set_metadata(data_str)
|
|
|
|
|
|
|
|
local inv = clicker:get_inventory()
|
|
|
|
|
|
|
|
if inv:room_for_item("main", new_stack) then
|
|
|
|
inv:add_item("main", new_stack)
|
|
|
|
else
|
|
|
|
minetest.add_item(clicker:get_pos(), new_stack)
|
|
|
|
end
|
|
|
|
|
|
|
|
self:mob_sound("default_place_node_hard")
|
|
|
|
|
|
|
|
remove_mob(self, true)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- capture critter (thanks to blert2112 for idea)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
function mobs:capture_mob(
|
|
|
|
self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith)
|
|
|
|
|
|
|
|
if not self or not is_player(clicker) or not clicker:get_inventory() then
|
2020-10-26 17:38:53 +01:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- get name of clicked mob
|
|
|
|
local mobname = self.name
|
|
|
|
|
|
|
|
-- if not nil change what will be added to inventory
|
2024-12-19 12:55:40 +01:00
|
|
|
if replacewith then mobname = replacewith end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local name = clicker:get_player_name()
|
|
|
|
local tool = clicker:get_wielded_item()
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if tool:get_name() ~= "" -- hand
|
|
|
|
and tool:get_name() ~= "mobs:net" and tool:get_name() ~= "mobs:lasso" then
|
2020-10-26 17:38:53 +01:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- is mob tamed?
|
|
|
|
if self.tamed == false and force_take == false then
|
|
|
|
|
|
|
|
minetest.chat_send_player(name, S("Not tamed!"))
|
|
|
|
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2021-04-10 09:51:05 +02:00
|
|
|
-- cannot pick up if not owner (unless player has protection_bypass priv)
|
|
|
|
if not minetest.check_player_privs(name, "protection_bypass")
|
|
|
|
and self.owner ~= name and force_take == false then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
minetest.chat_send_player(name, S("@1 is owner!", self.owner))
|
|
|
|
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
if clicker:get_inventory():room_for_item("main", mobname) then
|
|
|
|
|
|
|
|
-- was mob clicked with hand, net, or lasso?
|
|
|
|
local chance = 0
|
|
|
|
|
|
|
|
if tool:get_name() == "" then
|
|
|
|
chance = chance_hand
|
|
|
|
|
|
|
|
elseif tool:get_name() == "mobs:net" then
|
|
|
|
|
|
|
|
chance = chance_net
|
|
|
|
|
|
|
|
tool:add_wear(4000) -- 17 uses
|
|
|
|
|
|
|
|
clicker:set_wielded_item(tool)
|
|
|
|
|
|
|
|
elseif tool:get_name() == "mobs:lasso" then
|
|
|
|
|
|
|
|
chance = chance_lasso
|
|
|
|
|
|
|
|
tool:add_wear(650) -- 100 uses
|
|
|
|
|
|
|
|
clicker:set_wielded_item(tool)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- calculate chance.. add to inventory if successful?
|
|
|
|
if chance and chance > 0 and random(100) <= chance then
|
|
|
|
|
|
|
|
-- default mob egg
|
|
|
|
local new_stack = ItemStack(mobname)
|
|
|
|
|
|
|
|
-- add special mob egg with all mob information
|
|
|
|
-- unless 'replacewith' contains new item to use
|
|
|
|
if not replacewith then
|
|
|
|
|
|
|
|
new_stack = ItemStack(mobname .. "_set")
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local data_str = minetest.serialize(clean_staticdata(self))
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
new_stack:set_metadata(data_str)
|
|
|
|
end
|
|
|
|
|
|
|
|
local inv = clicker:get_inventory()
|
|
|
|
|
|
|
|
if inv:room_for_item("main", new_stack) then
|
|
|
|
inv:add_item("main", new_stack)
|
|
|
|
else
|
|
|
|
minetest.add_item(clicker:get_pos(), new_stack)
|
|
|
|
end
|
|
|
|
|
|
|
|
self:mob_sound("default_place_node_hard")
|
|
|
|
|
2021-01-02 22:12:17 +01:00
|
|
|
remove_mob(self, true)
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
return new_stack
|
|
|
|
|
|
|
|
-- when chance above fails or set to 0, miss!
|
|
|
|
elseif chance and chance ~= 0 then
|
|
|
|
|
|
|
|
minetest.chat_send_player(name, S("Missed!"))
|
|
|
|
|
|
|
|
self:mob_sound("mobs_swing")
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
-- when chance is nil always return a miss (used for npc walk/follow)
|
|
|
|
elseif not chance then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- protect tamed mob with rune item
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
function mobs:protect(self, clicker)
|
|
|
|
|
|
|
|
local name = clicker:get_player_name()
|
|
|
|
local tool = clicker:get_wielded_item()
|
2021-04-05 14:46:21 +02:00
|
|
|
local tool_name = tool:get_name()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if tool_name ~= "mobs:protector" and tool_name ~= "mobs:protector2" then
|
2020-10-26 17:38:53 +01:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2021-04-05 14:46:21 +02:00
|
|
|
if not self.tamed then
|
2024-12-19 12:55:40 +01:00
|
|
|
minetest.chat_send_player(name, S("Not tamed!")) ; return true
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
2021-05-16 11:33:49 +02:00
|
|
|
if (self.protected and tool_name == "mobs:protector")
|
|
|
|
or (self.protected == 2 and tool_name == "mobs:protector2") then
|
2024-12-19 12:55:40 +01:00
|
|
|
minetest.chat_send_player(name, S("Already protected!")) ; return true
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
if not mobs.is_creative(clicker:get_player_name()) then
|
|
|
|
tool:take_item() -- take 1 protection rune
|
|
|
|
clicker:set_wielded_item(tool)
|
|
|
|
end
|
|
|
|
|
2021-04-05 14:46:21 +02:00
|
|
|
-- set protection level
|
|
|
|
if tool_name == "mobs:protector" then
|
|
|
|
self.protected = true
|
|
|
|
else
|
|
|
|
self.protected = 2 ; self.fire_damage = 0
|
|
|
|
end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local pos = self.object:get_pos()
|
2024-12-19 12:55:40 +01:00
|
|
|
local prop = self.object:get_properties()
|
2021-04-05 14:46:21 +02:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
pos.y = pos.y + prop.collisionbox[5]
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
effect(pos, 25, "mobs_protect_particle.png", 0.5, 4, 2, 15)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self:mob_sound("mobs_spell")
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- feeding, taming, breeding and naming (thanks blert2112)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local mob_obj, mob_sta = {}, {}
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
function mobs:feed_tame(self, clicker, feed_count, breed, tame)
|
|
|
|
|
|
|
|
-- can eat/tame with item in hand
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.follow and self:follow_holding(clicker) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- if not in creative then take item
|
|
|
|
if not mobs.is_creative(clicker:get_player_name()) then
|
|
|
|
|
|
|
|
local item = clicker:get_wielded_item()
|
|
|
|
|
|
|
|
item:take_item()
|
|
|
|
|
|
|
|
clicker:set_wielded_item(item)
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local prop = self.object:get_properties()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- increase health
|
|
|
|
self.health = min(self.health + 4, prop.hp_max)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
self.object:set_hp(self.health)
|
|
|
|
|
|
|
|
-- make children grow quicker
|
2024-12-19 12:55:40 +01:00
|
|
|
if self.child then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2020-12-07 17:01:24 +01:00
|
|
|
-- deduct 10% of the time to adulthood
|
2024-12-19 12:55:40 +01:00
|
|
|
self.hornytimer = floor(self.hornytimer + (
|
2021-08-18 21:42:51 +02:00
|
|
|
(CHILD_GROW_TIME - self.hornytimer) * 0.1))
|
2021-03-17 20:30:38 +01:00
|
|
|
--print ("====", self.hornytimer)
|
2020-10-26 17:38:53 +01:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- feed and tame
|
|
|
|
self.food = (self.food or 0) + 1
|
2024-12-19 12:55:40 +01:00
|
|
|
self._breed_countdown = breed and (feed_count - self.food)
|
|
|
|
self._tame_countdown = not self.tamed and tame and (feed_count - self.food)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if self.food >= feed_count then
|
|
|
|
|
|
|
|
self.food = 0
|
2021-08-18 21:42:51 +02:00
|
|
|
self._breed_countdown = nil
|
2020-10-26 17:38:53 +01:00
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if breed and self.hornytimer == 0 then self.horny = true end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if tame then
|
|
|
|
|
|
|
|
if self.tamed == false then
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
minetest.chat_send_player(clicker:get_player_name(),
|
|
|
|
S("@1 has been tamed!",
|
|
|
|
self.name:split(":")[2]))
|
|
|
|
end
|
|
|
|
|
|
|
|
self.tamed = true
|
2021-07-31 22:19:53 +02:00
|
|
|
self.static_save = true
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
if not self.owner or self.owner == "" then
|
|
|
|
self.owner = clicker:get_player_name()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- make sound when fed so many times
|
|
|
|
self:mob_sound(self.sounds.random)
|
|
|
|
end
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
self:update_tag() ; return true
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
local item = clicker:get_wielded_item()
|
2021-04-10 09:51:05 +02:00
|
|
|
local name = clicker:get_player_name()
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- if mob has been tamed you can name it with a nametag
|
|
|
|
if item:get_name() == "mobs:nametag"
|
2024-12-19 12:55:40 +01:00
|
|
|
and (name == self.owner or minetest.check_player_privs(name, "protection_bypass")) then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- store mob and nametag stack in external variables
|
|
|
|
mob_obj[name] = self
|
|
|
|
mob_sta[name] = item
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
local prop = self.object:get_properties()
|
|
|
|
local tag = self._nametag or ""
|
2020-10-26 17:38:53 +01:00
|
|
|
local esc = minetest.formspec_escape
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
minetest.show_formspec(name, "mobs_nametag", "size[8,4]"
|
|
|
|
.. "field[0.5,1;7.5,0;name;" .. esc(FS("Enter name:")) .. ";" .. esc(tag) .. "]"
|
|
|
|
.. "button_exit[2.5,3.5;3,1;mob_rename;" .. esc(FS("Rename")) .. "]")
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2021-08-18 21:42:51 +02:00
|
|
|
-- if mob follows items and user right clicks while holding sneak it shows info
|
|
|
|
if self.follow then
|
|
|
|
|
|
|
|
if clicker:get_player_control().sneak then
|
|
|
|
|
2021-09-05 16:06:04 +02:00
|
|
|
if type(self.follow) == "string" then
|
|
|
|
self.follow = {self.follow}
|
|
|
|
end
|
|
|
|
|
2021-08-18 21:42:51 +02:00
|
|
|
minetest.chat_send_player(clicker:get_player_name(),
|
2024-12-19 12:55:40 +01:00
|
|
|
S("@1 follows:",
|
|
|
|
self.name:split(":")[2]) .. "\n- " ..
|
|
|
|
table.concat(self.follow, "\n- "))
|
2021-08-18 21:42:51 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- inspired by blockmen's nametag mod
|
2024-12-19 12:55:40 +01:00
|
|
|
|
2020-10-26 17:38:53 +01:00
|
|
|
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
|
|
|
|
|
|
|
-- right-clicked with nametag and name entered?
|
2024-12-19 12:55:40 +01:00
|
|
|
if formname == "mobs_nametag" and fields.name and fields.name ~= "" then
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
local name = player:get_player_name()
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if not mob_obj[name] or not mob_obj[name].object then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- make sure nametag is being used to name mob
|
|
|
|
local item = player:get_wielded_item()
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
if item:get_name() ~= "mobs:nametag" then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- limit name entered to 64 characters long
|
2024-12-19 12:55:40 +01:00
|
|
|
if fields.name:len() > 64 then fields.name = fields.name:sub(1, 64) end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- update nametag
|
2024-12-19 12:55:40 +01:00
|
|
|
mob_obj[name]:update_tag(fields.name)
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- if not in creative then take item
|
|
|
|
if not mobs.is_creative(name) then
|
|
|
|
|
|
|
|
mob_sta[name]:take_item()
|
|
|
|
|
|
|
|
player:set_wielded_item(mob_sta[name])
|
|
|
|
end
|
|
|
|
|
|
|
|
-- reset external variables
|
|
|
|
mob_obj[name] = nil
|
|
|
|
mob_sta[name] = nil
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
-- compatibility function for old mobs entities to new mobs_redo modpack
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
function mobs:alias_mob(old_name, new_name)
|
|
|
|
|
|
|
|
-- check old_name entity doesnt already exist
|
2024-12-19 12:55:40 +01:00
|
|
|
if minetest.registered_entities[old_name] then return end
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
-- spawn egg
|
|
|
|
minetest.register_alias(old_name, new_name)
|
|
|
|
|
|
|
|
-- entity
|
|
|
|
minetest.register_entity(":" .. old_name, {
|
|
|
|
|
2024-12-19 12:55:40 +01:00
|
|
|
initial_properties = {
|
|
|
|
physical = false,
|
|
|
|
static_save = false,
|
|
|
|
},
|
2020-10-26 17:38:53 +01:00
|
|
|
|
|
|
|
on_activate = function(self, staticdata)
|
|
|
|
|
|
|
|
if minetest.registered_entities[new_name] then
|
2021-05-16 11:33:49 +02:00
|
|
|
minetest.add_entity(self.object:get_pos(), new_name, staticdata)
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
remove_mob(self)
|
|
|
|
end,
|
|
|
|
|
|
|
|
get_staticdata = function(self)
|
2024-12-19 12:55:40 +01:00
|
|
|
return minetest.serialize(clean_staticdata(self))
|
2020-10-26 17:38:53 +01:00
|
|
|
end
|
|
|
|
})
|
|
|
|
end
|
2024-12-19 12:55:40 +01:00
|
|
|
|
|
|
|
-- admin command to remove untamed mobs around players
|
|
|
|
|
|
|
|
minetest.register_chatcommand("clear_mobs", {
|
|
|
|
params = "<text>",
|
|
|
|
description = "Remove untamed mobs from around players.",
|
|
|
|
privs = {server = true},
|
|
|
|
|
|
|
|
func = function (name, param)
|
|
|
|
|
|
|
|
local count = 0
|
|
|
|
|
|
|
|
for _, player in pairs(minetest.get_connected_players()) do
|
|
|
|
|
|
|
|
if player then
|
|
|
|
|
|
|
|
local pos = player:get_pos()
|
|
|
|
local objs = minetest.get_objects_inside_radius(pos, 28)
|
|
|
|
|
|
|
|
for _, obj in pairs(objs) do
|
|
|
|
|
|
|
|
if obj then
|
|
|
|
|
|
|
|
local ent = obj:get_luaentity()
|
|
|
|
|
|
|
|
-- only remove mobs redo mobs that are not tamed
|
|
|
|
if ent and ent._cmi_is_mob and ent.tamed ~= true then
|
|
|
|
|
|
|
|
remove_mob(ent, true)
|
|
|
|
|
|
|
|
count = count + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
minetest.chat_send_player(name, S("@1 mobs removed.", count))
|
|
|
|
end
|
|
|
|
})
|
|
|
|
|
|
|
|
-- Is mob hearing enabled, if so override minetest.sound_play with custom function
|
|
|
|
|
|
|
|
if settings:get_bool("mobs_can_hear") ~= false then
|
|
|
|
|
|
|
|
local node_hear = settings:get_bool("mobs_can_hear_node")
|
|
|
|
local old_sound_play = minetest.sound_play
|
|
|
|
|
|
|
|
minetest.sound_play = function(spec, param, eph)
|
|
|
|
|
|
|
|
local def = {} ; param = param or {}
|
|
|
|
|
|
|
|
-- store sound position (ignore player and object positioning as background)
|
|
|
|
if param.pos then
|
|
|
|
def.pos = param.pos
|
|
|
|
-- elseif param.object then
|
|
|
|
-- def.pos = param.object:get_pos()
|
|
|
|
-- def.object = param.object
|
|
|
|
-- elseif param.to_player then
|
|
|
|
-- local player = minetest.get_player_by_name(param.to_player)
|
|
|
|
-- def.pos = player and player:get_pos()
|
|
|
|
-- def.player = param.to_player
|
|
|
|
end
|
|
|
|
|
|
|
|
-- if no position found use default function
|
|
|
|
if not def.pos then
|
|
|
|
return old_sound_play(spec, param, eph)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- store sound name and gain
|
|
|
|
if type(spec) == "string" then
|
|
|
|
def.sound = spec
|
|
|
|
def.gain = param.gain or 1.0
|
|
|
|
elseif type(spec) == "table" then
|
|
|
|
def.sound = spec.name
|
|
|
|
def.gain = spec.gain or param.gain or 1.0
|
|
|
|
end
|
|
|
|
|
|
|
|
--print("==", def.sound)
|
|
|
|
|
|
|
|
def.gain = def.gain or 1.0
|
|
|
|
def.max_hear_distance = param.max_hear_distance or 32
|
|
|
|
|
|
|
|
-- find mobs within sounds hearing range
|
|
|
|
local objs = minetest.get_objects_inside_radius(def.pos, def.max_hear_distance)
|
|
|
|
local bit = def.gain / def.max_hear_distance
|
|
|
|
|
|
|
|
for n = 1, #objs do
|
|
|
|
|
|
|
|
local obj = objs[n]
|
|
|
|
|
|
|
|
if not obj:is_player() then
|
|
|
|
|
|
|
|
local ent = obj:get_luaentity()
|
|
|
|
|
|
|
|
if ent and ent._cmi_is_mob and ent.on_sound then
|
|
|
|
|
|
|
|
-- calculate loudness of sound to mob
|
|
|
|
def.distance = get_distance(def.pos, obj:get_pos())
|
|
|
|
def.loudness = def.gain - (bit * def.distance)
|
|
|
|
|
|
|
|
-- run custom on_sound function if heard
|
|
|
|
if def.loudness > 0 then ent.on_sound(ent, def) end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- find nodes that can hear up to 8 blocks away
|
|
|
|
if node_hear then
|
|
|
|
|
|
|
|
local dist = min(def.max_hear_distance, 8)
|
|
|
|
local ps = minetest.find_nodes_in_area(
|
|
|
|
vector.subtract(def.pos, dist),
|
|
|
|
vector.add(def.pos, dist), {"group:on_sound"})
|
|
|
|
|
|
|
|
if #ps > 0 then
|
|
|
|
|
|
|
|
for n = 1, #ps do
|
|
|
|
|
|
|
|
local ndef = minetest.registered_nodes[minetest.get_node(ps[n]).name]
|
|
|
|
|
|
|
|
def.distance = get_distance(def.pos, ps[n])
|
|
|
|
def.loudness = def.gain - (bit * def.distance)
|
|
|
|
|
|
|
|
if def.loudness > 0 and ndef and ndef.on_sound then
|
|
|
|
ndef.on_sound(ps[n], def)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return old_sound_play(spec, param, eph)
|
|
|
|
end
|
|
|
|
end
|