Compare commits

...

12 Commits

10 changed files with 228 additions and 124 deletions

View File

@ -128,7 +128,7 @@ functions needed for the mob to work properly which contains the following:
arrow/fireball appears on mob. arrow/fireball appears on mob.
'specific_attack' has a table of entity names that mob can also attack 'specific_attack' has a table of entity names that mob can also attack
e.g. {"player", "mobs_animal:chicken"}. e.g. {"player", "mobs_animal:chicken"}.
'runaway_from' contains a table with mob names to run away from, add 'runaway_from' contains a table with mob/node names to run away from, add
"player" to list to runaway from player also. "player" to list to runaway from player also.
'pathfinding' set to 1 for mobs to use pathfinder feature to locate 'pathfinding' set to 1 for mobs to use pathfinder feature to locate
player, set to 2 so they can build/break also (only player, set to 2 so they can build/break also (only
@ -262,7 +262,10 @@ functions needed for the mob to work properly which contains the following:
'custom_visual_size' will not reset visual_size from the base class on reload 'custom_visual_size' will not reset visual_size from the base class on reload
'noyaw' If true this mob will not automatically change yaw 'noyaw' If true this mob will not automatically change yaw
'particlespawners' Table of particlespawners attached to the mob. This is implemented in a coord safe manner i.e. spawners are only sent to players within the player_transfer_distance (and automatically removed). This enables infinitely lived particlespawners. 'particlespawners' Table of particlespawners attached to the mob. This is implemented in a coord safe manner i.e. spawners are only sent to players within the player_transfer_distance (and automatically removed). This enables infinitely lived particlespawners.
'attack_frequency' Attack frequency in seconds. If unset, this defaults to 1. Implemented for melee only atm. 'attack_frequency' Attack frequency in seconds. If unset, this defaults to 1. Implemented for melee only atm.
'avoid_from' contains a table with mob/node names to avoid from, add
"player" to list to avoid from player also.
mobs:gopath(self,target,callback_arrived) pathfind a way to target and run callback on arrival mobs:gopath(self,target,callback_arrived) pathfind a way to target and run callback on arrival
@ -449,6 +452,16 @@ Create death particles at pos with the given collisionbox.
mcl_mobs.spawn(pos,name/entity name) mcl_mobs.spawn(pos,name/entity name)
mcl_mobs:is_object_in_view(object_list, object_range, node_range, turn_around)
Returns 'true' if an object (mob or node) is in the field of view.
'object_list' list of mob and/or node names
'object_range' maximum distance to a mob from object_list
'node_range' maximum distance to a node from object_list
'turn_around' true or false
Making Arrows Making Arrows
------------- -------------

View File

@ -35,6 +35,34 @@ function mob_class:day_docile()
end end
end end
local function name(obj)
if obj:is_player() then
return "player:" .. obj:get_player_name()
else
local e = obj:get_luaentity()
return e.name
end
end
function mob_class:log2(msg, object)
--# local o = object:get_luaentity()
--# local o2 = object.objectget_luaentity()
--minetest.log("do_attack SRC " .. dump(self) ) -- { object = userdata: 0x49d9a1a0, is_mob = true, health = 20
-- minetest.log("do_attack SRC " .. dump(self.object) ) -- { object = userdata: 0x49d9a1a0, ...
-- minetest.log("do_attack DST " .. dump(object) ) -- userdata metatable: { is_player(), get_player_name()
-- minetest.log("do_attack SRC " .. name(self.object) )
-- minetest.log("do_attack DST " .. name(object) )
local src = self.object
if object then
local dst = object
minetest.log("# " .. msg .. " " .. name(src) .. " > " .. name(dst) .. " dist:" .. vector.distance(src:get_pos(), dst:get_pos()) .. " self:" .. dump(src == dst))
else
minetest.log("# " .. msg .. " " .. name(src))
end
end
-- get this mob to attack the object -- get this mob to attack the object
function mob_class:do_attack(object) function mob_class:do_attack(object)
@ -50,6 +78,7 @@ function mob_class:do_attack(object)
self.attack = object self.attack = object
self.state = "attack" self.state = "attack"
self:log2("do_attack", object)
-- TODO: Implement war_cry sound without being annoying -- TODO: Implement war_cry sound without being annoying
--if random(0, 100) < 90 then --if random(0, 100) < 90 then
--self:mob_sound("war_cry", true) --self:mob_sound("war_cry", true)
@ -204,6 +233,7 @@ function mob_class:smart_mobs(s, p, dist, dtime)
self.path.way = minetest.find_path(s, p1, 16, jumpheight, dropheight, "A*_noprefetch") self.path.way = minetest.find_path(s, p1, 16, jumpheight, dropheight, "A*_noprefetch")
self.state = "" self.state = ""
self:log2("smart_mobs -> do_attack", self.attack)
self:do_attack(self.attack) self:do_attack(self.attack)
-- no path found, try something else -- no path found, try something else
@ -360,7 +390,8 @@ function mob_class:monster_attack()
player = obj.object player = obj.object
name = obj.name or "" name = obj.name or ""
end end
if obj and obj.type == self.type and obj.passive == false and obj.state == "attack" and obj.attack then if obj and obj.type == self.type and obj.passive == false and obj.state == "attack" and obj.attack and self.object ~= obj.attack then
self:log2("add blacklist",obj.attack)
table.insert(blacklist_attack, obj.attack) table.insert(blacklist_attack, obj.attack)
end end
end end
@ -386,8 +417,10 @@ function mob_class:monster_attack()
end end
end end
-- find specific mob to attack, failing that attack player/npc/animal -- find specific mob to attack, failing that attack player/npc/animal
if specific_attack(self.specific_attack, name) if specific_attack(self.specific_attack, name)
and self.object ~= player
and (type == "player" or ( type == "npc" and self.attack_npcs ) and (type == "player" or ( type == "npc" and self.attack_npcs )
or (type == "animal" and self.attack_animals == true) or (type == "animal" and self.attack_animals == true)
or (self.extra_hostile and not self.attack_exception(player))) then or (self.extra_hostile and not self.attack_exception(player))) then
@ -416,11 +449,17 @@ function mob_class:monster_attack()
end end
end end
if not min_player and #blacklist_attack > 0 then if not min_player and #blacklist_attack > 0 then
self:log2("pre OOPS monster_attack -> do_attack", min_player)
min_player=blacklist_attack[math.random(#blacklist_attack)] min_player=blacklist_attack[math.random(#blacklist_attack)]
end end
-- attack player -- attack player
if min_player then if min_player then
self:do_attack(min_player) if self.object == min_player then
self:log2("OOPS monster_attack -> do_attack", min_player)
else
self:log2("monster_attack -> do_attack", min_player)
self:do_attack(min_player)
end
end end
end end
@ -460,6 +499,7 @@ function mob_class:npc_attack()
end end
if min_player then if min_player then
self:log2("npc_attack -> do_attack", min_player)
self:do_attack(min_player) self:do_attack(min_player)
end end
end end
@ -544,6 +584,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
return return
end end
self:log2("on_punch", hitter)
-- custom punch function -- custom punch function
if self.do_punch then if self.do_punch then
@ -788,6 +829,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
if not die then if not die then
-- attack whoever punched mob -- attack whoever punched mob
self.state = "" self.state = ""
self:log2("on_punch[-1] -> do_attack", hitter)
self:do_attack(hitter) self:do_attack(hitter)
self._aggro= true self._aggro= true
end end
@ -807,11 +849,15 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
and obj.state ~= "attack" and obj.state ~= "attack"
and obj.owner ~= name then and obj.owner ~= name then
if obj.name == self.name then if obj.name == self.name then
--minetest.log("on_punch0 -> do_attack")
obj:log2("group on_punch[0] -> do_attack", hitter)
obj:do_attack(hitter) obj:do_attack(hitter)
elseif type(obj.group_attack) == "table" then elseif type(obj.group_attack) == "table" then
for i=1, #obj.group_attack do for i=1, #obj.group_attack do
if obj.group_attack[i] == self.name then if obj.group_attack[i] == self.name then
obj._aggro = true obj._aggro = true
--minetest.log("on_punch1 -> do_attack")
obj:log2("group on_punch[1] -> do_attack", hitter)
obj:do_attack(hitter) obj:do_attack(hitter)
break break
end end
@ -821,6 +867,8 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
-- have owned mobs attack player threat -- have owned mobs attack player threat
if obj.owner == name and obj.owner_loyal then if obj.owner == name and obj.owner_loyal then
--minetest.log("on_punch2 -> do_attack")
obj:log2("on_punch[2] -> do_attack", self.object)
obj:do_attack(self.object) obj:do_attack(self.object)
end end
end end
@ -990,7 +1038,7 @@ function mob_class:do_states_attack (dtime)
end end
elseif self.attack_type == "dogfight" elseif self.attack_type == "dogfight"
or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2) and (dist >= self.avoid_distance or not self.shooter_avoid_enemy) or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2 and (dist >= self.avoid_distance or not self.shooter_avoid_enemy))
or (self.attack_type == "dogshoot" and dist <= self.reach and self:dogswitch() == 0) then or (self.attack_type == "dogshoot" and dist <= self.reach and self:dogswitch() == 0) then
if self.fly if self.fly
@ -1130,6 +1178,11 @@ function mob_class:do_states_attack (dtime)
s2.y = s2.y + .5 s2.y = s2.y + .5
if self:line_of_sight( p2, s2) == true then if self:line_of_sight( p2, s2) == true then
minetest.log("dogfight2 " .. dist .. "<" .. self.reach
.. " sae:".. dump(self.shooter_avoid_enemy)
.. " sa:" .. dump(self.shoot_arrow and true or false))
self:mob_sound("attack") self:mob_sound("attack")
-- punch player (or what player is attached to) -- punch player (or what player is attached to)
@ -1137,6 +1190,7 @@ function mob_class:do_states_attack (dtime)
if attached then if attached then
self.attack = attached self.attack = attached
end end
self:log2("do_state_attack", self.attack)
self.attack:punch(self.object, 1.0, { self.attack:punch(self.object, 1.0, {
full_punch_interval = 1.0, full_punch_interval = 1.0,
damage_groups = {fleshy = self.damage} damage_groups = {fleshy = self.damage}
@ -1152,14 +1206,17 @@ function mob_class:do_states_attack (dtime)
end end
end end
end end
elseif dist <= self.shoot_reach and (self.attack_type == "shoot"
elseif self.attack_type == "shoot"
or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1) or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1)
or (self.attack_type == "dogshoot" and (dist > self.reach or dist < self.avoid_distance and self.shooter_avoid_enemy) and self:dogswitch() == 0) then or (self.attack_type == "dogshoot" and ((dist > self.reach or dist < self.avoid_distance) and self.shooter_avoid_enemy) and self:dogswitch() == 0)) then
p.y = p.y - .5 p.y = p.y - .5
s.y = s.y + .5 s.y = s.y + .5
--#self:log2("shoot 3", object)
minetest.log("shoot3 " .. dist .. "<=" .. self.shoot_reach
.. " sae:".. dump(self.shooter_avoid_enemy)
.. " sa:" .. dump(self.shoot_arrow and true or false))
local dist = vector.distance(p, s) local dist = vector.distance(p, s)
local vec = { local vec = {
x = p.x - s.x, x = p.x - s.x,

View File

@ -229,6 +229,7 @@ function mcl_mobs.register_mob(name, def)
health = 0, health = 0,
frame_speed_multiplier = 1, frame_speed_multiplier = 1,
reach = def.reach or 3, reach = def.reach or 3,
shoot_reach = def.shoot_reach or def.view_range or 16,
htimer = 0, htimer = 0,
texture_list = def.textures, texture_list = def.textures,
child_texture = def.child_texture, child_texture = def.child_texture,
@ -289,6 +290,7 @@ function mcl_mobs.register_mob(name, def)
noyaw = def.noyaw or false, noyaw = def.noyaw or false,
particlespawners = def.particlespawners, particlespawners = def.particlespawners,
spawn_check = def.spawn_check, spawn_check = def.spawn_check,
avoid_from = def.avoid_from,
-- End of MCL2 extensions -- End of MCL2 extensions
on_spawn = def.on_spawn, on_spawn = def.on_spawn,
on_blast = def.on_blast or function(self,damage) on_blast = def.on_blast or function(self,damage)

View File

@ -509,6 +509,81 @@ function mob_class:do_jump()
return false return false
end end
local function in_list(list, what)
return type(list) == "table" and table.indexof(list, what) ~= -1
end
function mob_class:is_object_in_view(object_list, object_range, node_range, turn_around)
local s = self.object:get_pos()
local min_dist = object_range + 1
local objs = minetest.get_objects_inside_radius(s, object_range)
local object_pos = nil
for n = 1, #objs do
local name = ""
local object = objs[n]
if object:is_player() then
if not (mcl_mobs.invis[ object:get_player_name() ]
or self.owner == object:get_player_name()
or (not self:object_in_range(object))) then
name = "player"
if not (name ~= self.name
and in_list(object_list, name)) then
local item = object:get_wielded_item()
name = item:get_name() or ""
end
end
else
local obj = object:get_luaentity()
if obj then
object = obj.object
name = obj.name or ""
end
end
-- find specific mob to avoid or runaway from
if name ~= "" and name ~= self.name
and in_list(object_list, name) then
local p = object:get_pos()
local dist = vector.distance(p, s)
-- choose closest player/mob to avoid or runaway from
if dist < min_dist
-- aim higher to make looking up hills more realistic
and self:line_of_sight(vector.offset(s, 0,1,0), vector.offset(p, 0,1,0)) == true then
min_dist = dist
object_pos = p
end
end
end
if not object_pos then
-- find specific node to avoid or runaway from
local p = minetest.find_node_near(s, node_range, object_list, true)
local dist = p and vector.distance(p, s)
if dist and dist < min_dist
and self:line_of_sight(s, p) == true then
min_dist = dist
object_pos = p
end
end
if object_pos and turn_around then
local vec = vector.subtract(object_pos, s)
local yaw = (atan(vec.z / vec.x) + 3 *math.pi/ 2) - self.rotate
if object_pos.x > s.x then yaw = yaw + math.pi end
yaw = self:set_yaw(yaw, 4)
end
return object_pos ~= nil
end
-- should mob follow what I'm holding ? -- should mob follow what I'm holding ?
function mob_class:follow_holding(clicker) function mob_class:follow_holding(clicker)
if self.nofollow then return false end if self.nofollow then return false end
@ -526,14 +601,8 @@ function mob_class:follow_holding(clicker)
return true return true
-- multiple items -- multiple items
elseif t == "table" then elseif t == "table" and in_list(self.follow, item:get_name()) then
return true
for no = 1, #self.follow do
if self.follow[no] == item:get_name() then
return true
end
end
end end
return false return false
@ -593,104 +662,14 @@ function mob_class:replace_node(pos)
end end
end end
-- specific runaway
local specific_runaway = function(list, what)
if type(list) ~= "table" then
list = {}
end
-- no list so do not run
if list == nil then
return false
end
-- found entity on list to attack?
for no = 1, #list do
if list[no] == what then
return true
end
end
return false
end
-- find someone to runaway from -- find someone to runaway from
function mob_class:check_runaway_from() function mob_class:check_runaway_from()
if not self.runaway_from and self.state ~= "flop" then if not self.runaway_from and self.state ~= "flop" then
return return
end end
local s = self.object:get_pos() if self:is_object_in_view(self.runaway_from, self.view_range, self.view_range / 2, true) then
local p, sp, dist self.shaking = self.shaking or 5
local player, obj, min_player
local type, name = "", ""
local min_dist = self.view_range + 1
local objs = minetest.get_objects_inside_radius(s, self.view_range)
for n = 1, #objs do
if objs[n]:is_player() then
if mcl_mobs.invis[ objs[n]:get_player_name() ]
or self.owner == objs[n]:get_player_name()
or (not self:object_in_range(objs[n])) then
type = ""
else
player = objs[n]
type = "player"
name = "player"
end
else
obj = objs[n]:get_luaentity()
if obj then
player = obj.object
type = obj.type
name = obj.name or ""
end
end
-- find specific mob to runaway from
if name ~= "" and name ~= self.name
and specific_runaway(self.runaway_from, name) then
p = player:get_pos()
sp = s
-- aim higher to make looking up hills more realistic
p.y = p.y + 1
sp.y = sp.y + 1
dist = vector.distance(p, s)
-- choose closest player/mpb to runaway from
if dist < min_dist
and self:line_of_sight(sp, p, 2) == true then
min_dist = dist
min_player = player
end
end
end
if min_player then
local lp = player:get_pos()
local vec = {
x = lp.x - s.x,
y = lp.y - s.y,
z = lp.z - s.z
}
local yaw = (atan(vec.z / vec.x) + 3 *math.pi/ 2) - self.rotate
if lp.x > s.x then
yaw = yaw + math.pi
end
yaw = self:set_yaw( yaw, 4)
self.state = "runaway" self.state = "runaway"
self.runaway_timer = 3 self.runaway_timer = 3
self.following = nil self.following = nil
@ -914,23 +893,20 @@ function mob_class:do_states_walk()
end end
end end
-- A danger is near but mob is not inside
else
-- Randomly turn
if math.random(1, 100) <= 30 then
yaw = yaw + math.random(-0.5, 0.5)
yaw = self:set_yaw( yaw, 8)
end
end end
end
yaw = self:set_yaw( yaw, 8) if not is_in_danger then
local distance = self.avoid_distance or self.view_range / 2
-- find specific node to avoid
if self:is_object_in_view(self.avoid_from, distance, distance, true) then
self.shaking = self.shaking or 2
self:set_velocity(self.walk_velocity)
-- otherwise randomly turn -- otherwise randomly turn
elseif math.random(1, 100) <= 30 then elseif math.random(1, 100) <= 30 then
yaw = yaw + math.random(-0.5, 0.5) yaw = yaw + math.random(-0.5, 0.5)
yaw = self:set_yaw( yaw, 8) yaw = self:set_yaw(yaw, 8)
end
end end
-- stand for great fall or danger or fence in front -- stand for great fall or danger or fence in front
@ -938,6 +914,7 @@ function mob_class:do_states_walk()
if is_in_danger then if is_in_danger then
cliff_or_danger = self:is_at_cliff_or_danger() cliff_or_danger = self:is_at_cliff_or_danger()
end end
if self.facing_fence == true if self.facing_fence == true
or cliff_or_danger or cliff_or_danger
or math.random(1, 100) <= 30 then or math.random(1, 100) <= 30 then
@ -1086,6 +1063,12 @@ function mob_class:check_smooth_rotation(dtime)
self.delay = self.delay - 1 self.delay = self.delay - 1
if self.shaking then if self.shaking then
yaw = yaw + (math.random() * 2 - 1) * 5 * dtime yaw = yaw + (math.random() * 2 - 1) * 5 * dtime
if type(self.shaking) == "number" then
self.shaking = self.shaking - dtime
if self.shaking <= 0 then
self.shaking = nil
end
end
end end
self.object:set_yaw(yaw) self.object:set_yaw(yaw)
--self:update_roll() --self:update_roll()

View File

@ -40,6 +40,17 @@ local hoglin = {
makes_footstep_sound = true, makes_footstep_sound = true,
walk_velocity = 1, walk_velocity = 1,
run_velocity = 2.8, run_velocity = 2.8,
avoid_from = {
"mcl_crimson:warped_fungus",
"mcl_flowerpots:flower_pot_warped_fungus",
"mcl_portals:portal",
"mcl_beds:respawn_anchor",
"mcl_beds:respawn_anchor_charged_1",
"mcl_beds:respawn_anchor_charged_2",
"mcl_beds:respawn_anchor_charged_3",
"mcl_beds:respawn_anchor_charged_4",
},
follow = {"mcl_crimson:crimson_fungus"},
drops = { drops = {
{name = "mobs_mcitems:leather", {name = "mobs_mcitems:leather",
chance = 1, chance = 1,
@ -87,9 +98,26 @@ local hoglin = {
attack_animals = true, attack_animals = true,
} }
mcl_mobs.register_mob("mobs_mc:hoglin", hoglin)
local zoglin = table.copy(hoglin) local zoglin = table.copy(hoglin)
hoglin.on_rightclick = function(self, clicker)
-- local item = clicker:get_wielded_item()
if self:feed_tame(clicker, 1, true, false) then return end
-- if mcl_mobs:protect(self, clicker) then return end
end
hoglin.on_breed = function(parent1, parent2)
local pos = parent1.object:get_pos()
local child = mcl_mobs.spawn_child(pos, parent1.name)
if child then
local ent_c = child:get_luaentity()
-- ent_c.tamed = true
ent_c.owner = parent1.owner
return false
end
end,
mcl_mobs.register_mob("mobs_mc:hoglin", hoglin)
zoglin.description = S("Zoglin") zoglin.description = S("Zoglin")
zoglin.fire_resistant = 1 zoglin.fire_resistant = 1
zoglin.textures = {"extra_mobs_zoglin.png"} zoglin.textures = {"extra_mobs_zoglin.png"}

View File

@ -53,6 +53,7 @@ mcl_mobs.register_mob("mobs_mc:llama", {
spawn_class = "passive", spawn_class = "passive",
passive = false, passive = false,
attack_type = "shoot", attack_type = "shoot",
--attack_type = "dogfight",
shoot_interval = 5.5, shoot_interval = 5.5,
arrow = "mobs_mc:llamaspit", arrow = "mobs_mc:llamaspit",
shoot_offset = 1, --3.5 *would* be a good value visually but it somehow messes with the projectiles trajectory shoot_offset = 1, --3.5 *would* be a good value visually but it somehow messes with the projectiles trajectory
@ -112,6 +113,12 @@ mcl_mobs.register_mob("mobs_mc:llama", {
}, },
follow = { "mcl_farming:wheat_item", "mcl_farming:hay_block" }, follow = { "mcl_farming:wheat_item", "mcl_farming:hay_block" },
view_range = 16, view_range = 16,
attack_animals = true,
damage = 1,
shoot_reach = 5,
--avoid_distance = 5, -- default 9
--shooter_avoid_enemy = true,
specific_attack = { "mobs_mc:wolf" },
do_custom = function(self, dtime) do_custom = function(self, dtime)
-- set needed values if not already present -- set needed values if not already present

View File

@ -42,6 +42,7 @@ local rabbit = {
makes_footstep_sound = false, makes_footstep_sound = false,
walk_velocity = 1, walk_velocity = 1,
run_velocity = 3.7, run_velocity = 3.7,
avoid_from = {"mobs_mc:wolf"},
follow_velocity = 1.1, follow_velocity = 1.1,
floats = 1, floats = 1,
runaway = true, runaway = true,

View File

@ -46,6 +46,7 @@ local skeleton = {
}, },
walk_velocity = 1.2, walk_velocity = 1.2,
run_velocity = 2.0, run_velocity = 2.0,
runaway_from = {"mobs_mc:wolf"},
damage = 2, damage = 2,
reach = 2, reach = 2,
drops = { drops = {

View File

@ -44,6 +44,7 @@ mcl_mobs.register_mob("mobs_mc:witherskeleton", {
}, },
walk_velocity = 1.2, walk_velocity = 1.2,
run_velocity = 2.0, run_velocity = 2.0,
runaway_from = {"mobs_mc:wolf"},
damage = 7, damage = 7,
reach = 2, reach = 2,
drops = { drops = {

View File

@ -46,6 +46,8 @@ local wolf = {
walk_chance = default_walk_chance, walk_chance = default_walk_chance,
walk_velocity = 2, walk_velocity = 2,
run_velocity = 3, run_velocity = 3,
runaway = true,
runaway_from = { "mobs_mc:llama" },
damage = 4, damage = 4,
reach = 2, reach = 2,
attack_type = "dogfight", attack_type = "dogfight",
@ -97,7 +99,16 @@ local wolf = {
jump = true, jump = true,
attacks_monsters = true, attacks_monsters = true,
attack_animals = true, attack_animals = true,
specific_attack = { "player", "mobs_mc:sheep" }, specific_attack = {
--FIXME: "player",
"mobs_mc:sheep",
"mobs_mc:rabbit",
-- TODO: "mobs_mc:fox",
"mobs_mc:skeleton",
"mobs_mc:stray",
"mobs_mc:witherskeleton",
},
avoid_from = { "mobs_mc:llama" },
} }
mcl_mobs.register_mob("mobs_mc:wolf", wolf) mcl_mobs.register_mob("mobs_mc:wolf", wolf)