WIP -- squash later

This commit is contained in:
Po Lu 2024-10-06 14:28:51 +08:00 committed by cora
parent e7745c2659
commit ce6f198155
No known key found for this signature in database
39 changed files with 1596 additions and 704 deletions

@ -89,7 +89,9 @@ function mob_class:get_staticdata()
and tag ~= "pathfinding_context"
and tag ~= "waypoints"
and tag ~= "_cmi_components"
and tag ~= "_targets_visible" then
and tag ~= "_targets_visible"
and tag ~= "_leader"
and tag ~= "_school" then
tmp[tag] = self[tag]
end
end
@ -173,6 +175,9 @@ function mob_class:scale_size(scale, force)
self.base_selbox[6] * scale,
},
})
-- This presumes that the collision box does not extend much
-- beneath the mob origin.
self.head_eye_height = self.head_eye_height * scale
self.scaled = true
end
@ -336,7 +341,7 @@ end
function mob_class:on_step(dtime, moveresult)
local pos = self.object:get_pos()
if not mcl_mobs.check_vector(pos) or self.removed or not moveresult then
if not mcl_mobs.check_vector(pos) or self.removed then
self:safe_remove()
return
end
@ -347,7 +352,19 @@ function mob_class:on_step(dtime, moveresult)
self:update_tag()
if not (moveresult and moveresult.touching_ground) and self:falling(pos) then return end
-- Objects which are attached don't receive moveresults.
-- Create a placeholder object to prevent crashes further
-- down the line.
if not moveresult then
moveresult = {
touching_ground = false,
collides = false,
standing_on_object = true,
collisions = { },
}
end
if not moveresult.touching_ground and self:falling (pos) then return end
-- Get nodes early for use in other functions
local cbox = self.collisionbox
@ -362,7 +379,8 @@ function mob_class:on_step(dtime, moveresult)
self.standing_in = mcl_mobs.node_ok (feet, "air").name
self.standing_on = self.standing_in
end
local pos_head = vector.offset(pos, 0, cbox[5] - 0.5, 0)
local head_y = cbox[2] + (cbox[5] - cbox[2]) * 0.75
local pos_head = vector.offset (pos, 0, head_y, 0)
self.head_in = mcl_mobs.node_ok(pos_head, "air").name
self:falling (pos)

@ -25,13 +25,25 @@ function mob_class:_on_dispense(dropitem)
end
end
function mob_class:feed_tame(clicker, heal, breed, tame, notake)
function mob_class:feed_tame(clicker, heal, breed, tame, notake, tamechance)
local consume_food = false
local tamechance = tamechance or 1.0
if clicker and tame and not self.child then
if not self.owner or self.owner == "" then
self.tamed = true
self.owner = clicker:get_player_name()
local pos = self.object:get_pos ()
local x, z = pos.x, pos.z
if math.random () <= tamechance then
self.tamed = true
self.owner = clicker:get_player_name()
mcl_mobs.effect ({x = x, y = pos.y + 0.7, z = z},
5, "heart.png", 2, 4, 2.0, 0.1)
else
mcl_mobs.effect ({x = x, y = pos.y + 0.7, z = z},
math.random (7),
"mcl_particles_mob_death.png^[colorize:#000000:255",
2, 4, 2.0, 0.1)
end
consume_food = true
end
end
@ -68,6 +80,7 @@ function mob_class:feed_tame(clicker, heal, breed, tame, notake)
self:mob_sound("random", true)
end
return consume_food
end
@ -132,7 +145,9 @@ function mob_class:tick_breeding ()
if self.on_grown then
self.on_grown(self)
else
self.order = "jump"
-- Jump when fully grown so as not to
-- fall into the ground.
self._jump = true
end
self.animation = nil
local anim = self._current_animation
@ -278,6 +293,7 @@ function mob_class:check_breeding (pos)
local entity = object:get_luaentity ()
self.mate = object
entity.mate = self.object
entity:replace_activity ("mate")
self.begetting = false
self.horny = false
-- Prevent duplicate calls to
@ -285,12 +301,7 @@ function mob_class:check_breeding (pos)
entity.begetting = true
-- Taken, sorry!
entity.horny = false
-- Interrupt other activities.
self.herd_following = nil
self.pacing = nil
self.following = nil
return true
return "mate"
end
end
end
@ -311,7 +322,8 @@ function mob_class:follow_herd (pos)
end
local target_pos = self.herd_following:get_pos ()
if vector.distance (target_pos, pos) < 9 then
if vector.distance (target_pos, pos) < 9
or self:navigation_finished () then
self.herd_following = nil
return false
end
@ -320,6 +332,7 @@ function mob_class:follow_herd (pos)
self:gopath (target_pos, nil, true,
self.movement_speed * self.follow_bonus)
end
return true
elseif self.child and self:check_timer ("check_herd", 0.5) then
-- Locate nearby adults to decide whether the entire
-- herd is further than 9 blocks away.
@ -330,78 +343,35 @@ function mob_class:follow_herd (pos)
bx = self.collisionbox[4]
by = self.collisionbox[5]
bz = self.collisionbox[6]
local aa = { x = pos.x + ax - 8, y = pos.y + ay - 4, z = pos.z + az - 8 }
local bb = { x = pos.x + bx + 8, y = pos.y + by + 4, z = pos.z + bz + 8 }
local aa = { x = pos.x + ax - 9, y = pos.y + ay - 5, z = pos.z + az - 9 }
local bb = { x = pos.x + bx + 9, y = pos.y + by + 5, z = pos.z + bz + 9 }
local objects = minetest.get_objects_in_area (aa, bb)
local distmax, selected = 5000
local distmin, selected = 5000
for _, object in ipairs (objects) do
local obj_pos = object:get_pos ()
local dist = vector.distance (pos, obj_pos)
if dist < distmax then
if dist < distmin then
local entity = object:get_luaentity ()
if entity and entity.is_mob and not entity.child
and self:same_species (entity) then
distmax = dist
distmin = dist
selected = object
end
end
-- There's no need to move towards the rest of
-- the herd.
if distmax < 9 then
return false
end
end
-- Interrupt other activities.
self.pacing = false
-- There's no need to move towards the rest of
-- the herd.
if not selected or distmin < 3.0 then
return false
end
self:gopath (selected:get_pos (), nil, true,
self.movement_speed * self.follow_bonus)
self.herd_following = selected
return "herd_following"
end
end
function mob_class:stay()
self.order = "sit"
self.jump = false
if self.animation.sit_start then
self:set_animation("sit")
else
self:set_animation("stand")
end
end
function mob_class:roam()
self.order = "roam"
self.jump = true
self:set_animation("stand")
end
function mob_class:toggle_sit(clicker,p)
if not self.tamed or self.child or self.owner ~= clicker:get_player_name() then
return
end
local pos = self.object:get_pos()
local particle
if not self.order or self.order == "" or self.order == "sit" then
particle = "mobs_mc_wolf_icon_roam.png"
self:roam()
else
particle = "mobs_mc_wolf_icon_sit.png"
self:stay()
end
local pp = vector.new(0,1.4,0)
if p then pp = vector.offset(pp,0,p,0) end
-- Display icon to show current order (sit or roam)
minetest.add_particle({
pos = vector.add(pos, pp),
velocity = {x=0,y=0.2,z=0},
expirationtime = 1,
size = 4,
texture = particle,
playername = self.owner,
glow = minetest.LIGHT_MAX,
})
end
function mob_class:break_in(player)
self.temper = self.temper or (math.random(100))
if not self.tamed then
@ -436,3 +406,157 @@ function mob_class:break_in(player)
return true
end
end
----------------------------------------------------------------------------------
-- Tamable mob interaction with owners. FIXME: why is this in breeding.lua?
----------------------------------------------------------------------------------
function mob_class:stay ()
self.order = "sit"
end
function mob_class:toggle_sit(clicker,p)
if not self.tamed or self.child or self.owner ~= clicker:get_player_name() then
return
end
local pos = self.object:get_pos()
local particle
if not self.order or self.order == "" or self.order == "sit" then
particle = "mobs_mc_wolf_icon_roam.png"
self.order = ""
else
particle = "mobs_mc_wolf_icon_sit.png"
self:stay ()
end
local pp = vector.new(0,1.4,0)
if p then pp = vector.offset(pp,0,p,0) end
-- Display icon to show current order (sit or roam)
minetest.add_particle({
pos = vector.add(pos, pp),
velocity = {x=0,y=0.2,z=0},
expirationtime = 1,
size = 4,
texture = particle,
playername = self.owner,
glow = minetest.LIGHT_MAX,
})
end
function mob_class:is_not_owner (object)
return not object:is_player ()
or object:get_player_name () ~= self.owner
end
function mob_class:sit_if_ordered (self_pos, dtime)
if self.order == "sit" and self.owner then
if minetest.get_item_group (self.standing_in, "water") ~= 0 then
return false
end
-- If recently damaged and owner is nearby, don't
-- activate either.
if self._recent_attacker
and self:is_not_owner (self._recent_attacker) then
local player = minetest.get_player_by_name (self.owner)
if player
and vector.distance (self_pos, player:get_pos ()) < 12 then
return false
end
end
if self.animation.sit_start then
self:set_animation ("sit")
end
self:halt_in_tracks ()
self:cancel_navigation ()
-- This field doesn't really exist; it serves to
-- indicate that the active task has changed.
return "sit_if_ordered"
end
return false
end
local function teleport_to_owner (self, owner, owner_pos)
-- Search for a walkable platform from among 10 random
-- positions around the owner's position. Reject leaves
-- unless this mob be airborne.
for _ = 1, 10 do
local x = math.random (-3, 3)
local y = math.random (-1, 1)
local z = math.random (-3, 3)
owner_pos.x = math.floor (owner_pos.x + 0.5)
owner_pos.y = math.floor (owner_pos.y + 0.5)
owner_pos.z = math.floor (owner_pos.z + 0.5)
local pos = vector.offset (owner_pos, x, y, z)
if self:gwp_classify_for_movement (pos) == "WALKABLE" then
pos.y = pos.y - 1
local node = minetest.get_node (pos)
local def = minetest.registered_nodes [node.name]
if def and (not def.groups.leaves or self.airborne) then
pos.y = pos.y + 1
self.object:set_pos (pos)
return true
end
end
end
return false
end
function mob_class:check_travel_to_owner (self_pos, dtime)
if self.traveling_to_owner then
if not self.owner then
self.traveling_to_owner = nil
return false
end
local owner = minetest.get_player_by_name (self.owner)
if not owner then
self.traveling_to_owner = nil
return false
end
local owner_pos = owner:get_pos ()
local distance = vector.distance (self_pos, owner_pos)
if distance <= self.stop_chasing_distance
or self:navigation_finished () then
self:cancel_navigation ()
self:halt_in_tracks ()
self.traveling_to_owner = nil
return false
end
if self:check_timer ("pathfind_to_owner", 0.5) then
-- Teleport if the owner is at a distance.
if distance > 12 then
if teleport_to_owner (self, owner, owner_pos) then
self.traveling_to_owner = nil
return false
end
else
self:gopath (owner_pos)
end
end
return true
else
local owner = self.owner and minetest.get_player_by_name (self.owner)
if not owner or self.object:get_attach () or self.order == "sit" then
return false
end
local owner_pos = owner:get_pos ()
local distance = vector.distance (self_pos, owner_pos)
-- Teleport if the owner is at a distance.
if distance > 12 then
if teleport_to_owner (self, owner, owner_pos) then
self.traveling_to_owner = nil
return false
end
self.traveling_to_owner = true
return "traveling_to_owner"
elseif distance > self.chase_owner_distance then
self:gopath (owner_pos)
self.traveling_to_owner = true
return "traveling_to_owner"
end
end
return false
end

@ -18,14 +18,6 @@ function mob_class:do_attack(obj)
if self.dead or obj == self.obj or obj == self.attack then
return
end
-- No longer idle. Interrupt other
-- activities.
self.avoiding = false
self.avoiding_sunlight = false
self.mate = nil
self.following = nil
self.herd_following = nil
self.pacing = false
-- Attack!!!
self.attack = obj
@ -133,10 +125,14 @@ function mob_class:register_damage (cmi_reason)
-- Attack puncher if necessary.
if ( self.passive == false or self.retaliates )
and self.state ~= "flop"
and (self.child == false or self.type == "monster") then
self:do_attack (source)
end
if source then
self._recent_attacker = source
self._recent_attacker_age = 0
end
end
-- deal damage and effects when mob punched
@ -291,13 +287,6 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
kb=kb*2
end
-- if already in air then dont go up anymore when hit
if math.abs(v.y) > 0.1
or self.fly then
up = 0
end
-- check if tool already has specific knockback value
if tool_capabilities.damage_groups["knockback"] then
kb = tool_capabilities.damage_groups["knockback"]
@ -335,13 +324,13 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
end
-- if skittish then run away
if hitter and hitter:get_pos() and not die and self.runaway == true and self.state ~= "flop" then
self:do_runaway(hitter)
if hitter and hitter:get_pos ()
and not die and self.runaway == true then
self:do_runaway (hitter)
end
-- attack puncher
if ( self.passive == false or self.retaliates )
and self.state ~= "flop"
and (self.child == false or self.type == "monster")
and hitter_playername ~= self.owner
and not mcl_mobs.invis[ hitter_playername or ""] then
@ -359,7 +348,6 @@ end
function mob_class:do_runaway ()
self.runaway_timer = 5
self.following = nil
end
function mob_class:call_group_attack(hitter)
@ -469,6 +457,7 @@ function mob_class:attack_bowshoot (self_pos, dtime, target_pos, line_of_sight)
-- Stop if the target is in range and has been for a second.
if dist < 15 and vistime >= 1 then
self:cancel_navigation ()
self:halt_in_tracks ()
self._strafe_time = self._strafe_time + dtime
else
if self:check_timer ("bowshoot_pathfind", 0.5) then
@ -731,6 +720,9 @@ function mob_class:attack_ranged (self_pos, dtime, target_pos, line_of_sight)
end
function mob_class:check_attack (self_pos, dtime)
if not self.attack_type then
return false
end
if not self.attack then
if not self:check_timer ("seek_target", 0.5) then
return false
@ -764,7 +756,7 @@ function mob_class:check_attack (self_pos, dtime)
if target then
self:do_attack (target)
return true
return "attack"
end
end
else
@ -803,7 +795,7 @@ function mob_class:check_attack (self_pos, dtime)
end
local attack_type = self.attack_type
if not attack_type then
if attack_type == "null" then
return true
end
if attack_type == "bowshoot" then

@ -58,7 +58,6 @@ mcl_mobs.mob_class = {
armor = 100,
sounds = {},
animation = {},
jump = true,
attacks_monsters = false,
group_attack = false,
passive = false,
@ -114,6 +113,9 @@ mcl_mobs.mob_class = {
movement_speed = 14, -- https://minecraft.wiki/w/Attribute#movementSpeed
run_bonus = 1.25,
follow_bonus = 1.2,
runaway_bonus_near = 1.25,
runaway_bonus_far = 1.0,
runaway_view_range = 16,
follow_distance = 6.0,
-- Distance at which targets will be relinquished.
tracking_distance = 32.0,
@ -143,6 +145,13 @@ mcl_mobs.mob_class = {
grounded_speed_factor = 0.10,
pace_interval = 5,
pace_height = 7,
pace_width = 10,
flops = false,
initialize_group = nil,
_hovers = false,
airborne_speed = 8.0,
chase_owner_distance = 10.0,
stop_chasing_distance = 2.0,
_mcl_fishing_hookable = true,
_mcl_fishing_reelable = true,
@ -189,12 +198,35 @@ dofile(path .. "/combat.lua")
-- the enity functions themselves
dofile(path .. "/api.lua")
--utility functions
dofile(path .. "/breeding.lua")
dofile(path .. "/spawning.lua")
dofile(path .. "/mount.lua")
-- AI functions. This list must be created after the defaults are
-- initialized above.
-- Default AI functions. Each function should accept a minimum of
-- three arguments, POS, DTIME. If such a function returns non-nil,
-- subsequent functions are skipped and its value, if otherwise than
-- `true', is saved into a list of outstanding activities; this value
-- is expected to be a field to be cleared when an activity of a
-- higher priority is activated. Functions earlier in the list take
-- priority over those which appear later.
local mob_class = mcl_mobs.mob_class
mob_class.ai_functions = {
mob_class.sit_if_ordered,
mob_class.check_travel_to_owner,
mob_class.check_avoid_sunlight,
mob_class.check_avoid,
mob_class.check_frightened,
mob_class.check_attack,
mob_class.check_breeding,
mob_class.check_following,
mob_class.follow_herd,
mob_class.check_pace,
}
function mcl_mobs.mob_class:set_nametag(name)
if name ~= "" then
if string.len(name) > self.max_name_length then

@ -140,7 +140,6 @@ function mob_class:expel_underwater_drivers ()
if headin.groups.water then
force_detach (self.driver)
self:roam ()
return
end
end
@ -210,7 +209,6 @@ function mob_class:drive_follow (moving_anim, stand_anim, dtime, moveresult)
if headin.groups.water then
force_detach (self.driver)
self:roam ()
return
end
@ -392,7 +390,6 @@ function mob_class:drive_controls(moving_anim, stand_anim, can_fly, dtime)
local cbox = self.object:get_properties().collisionbox
if minetest.registered_nodes[mcl_mobs.node_ok(vector.offset(p,0,cbox[5] -0.25,0)).name].groups.water then
force_detach(self.driver)
self:roam()
end
if ni == "lava" and self.lava_damage ~= 0 then
self.lava_counter = (self.lava_counter or 0) + dtime

@ -17,18 +17,21 @@ function mob_class:target_visible(origin, target)
local target_pos = target:get_pos()
if not target_pos then return end
local origin_eye_pos = vector.offset(origin, 0, self.head_eye_height, 0)
local origin_eye_pos = vector.offset (origin, 0, self.head_eye_height, 0)
local eye
local targ_head_height
local cbox = self.object:get_properties().collisionbox
if target:is_player () then
local eye = target:get_properties ().eye_height
targ_head_height = vector.offset(target_pos, 0, eye, 0)
eye = target:get_properties ().eye_height
else
targ_head_height = vector.offset(target_pos, 0, cbox[5], 0)
local entity = target:get_luaentity ()
eye = 0
if entity and entity.is_mob then
eye = entity.head_eye_height
end
end
target_pos.y = target_pos.y + eye
if self:line_of_sight (origin_eye_pos, targ_head_height) then
if self:line_of_sight (origin_eye_pos, target_pos) then
self._targets_visible[target] = true
return true
end
@ -436,7 +439,7 @@ function mob_class:do_go_pos (dtime, moveresult)
if self:check_jump (pos, moveresult) then
if self.should_jump and self.should_jump > 2 then
self.order = "jump"
self._jump = true
self.should_jump = 0
else
-- Jump again if the collision remains after
@ -575,8 +578,9 @@ function mob_class:halt_in_tracks (immediate)
self.acc_dir.x = 0
self.acc_speed = 0
self._acc_movement_speed = 0
self._acc_y_fixed = nil
self.movement_goal = nil
self:cancel_navigation ()
self._acc_no_gravity = false
if self._current_animation == "walk"
or self._current_animation == "run" then
@ -641,6 +645,7 @@ function mob_class:cancel_navigation ()
self.pathfinding_context = nil
self.waypoints = nil
self.stupid_target = nil
self.movement_goal = nil
end
function mob_class:go_to_stupidly (pos, velocity)
@ -691,9 +696,9 @@ function mob_class:random_node_direction (limx, limy, direction, range)
local y = math.random (2 * limy + 1) - limy
if math.abs (x) <= limx and math.abs (y) <= limx then
return vector.new (math.floor (x + 0.5),
math.floor (y + 0.5),
math.floor (z + 0.5))
return vector.new (math.floor (x),
math.floor (y),
math.floor (z))
end
return nil
end
@ -715,13 +720,10 @@ local IDLE_TIME_MAX = 250
function mob_class:init_ai ()
self.ai_idle_time = 2 + math.random (2)
self.avoiding_sunlight = nil
self.avoiding = false
self.attack = nil
self.mate = nil
self.following = nil
self.herd_following = nil
self.pacing = false
if self._active_activity then
self[self._active_activity] = nil
self._active_activity = nil
end
self:cancel_navigation ()
self:halt_in_tracks ()
@ -729,6 +731,14 @@ function mob_class:init_ai ()
self:gwp_configure_aquatic_mob ()
self:configure_aquatic_mob ()
end
if self.airborne then
self:gwp_configure_airborne_mob ()
self:configure_airborne_mob ()
end
-- Last mob to have attacked this mob within the past five
-- seconds.
self._recent_attacker = nil
self._recent_attacker_age = 0
end
function mob_class:is_frightened (dtime)
@ -745,6 +755,14 @@ function mob_class:ai_step (dtime)
else
self.follow_cooldown = nil
end
if self._recent_attacker then
self._recent_attacker_age = self._recent_attacker_age + dtime
if not self._recent_attacker:is_valid ()
or self._recent_attacker_age > 5 then
self._recent_attacker = nil
self._recent_attacker_age = 0
end
end
self:tick_breeding ()
end
@ -756,19 +774,45 @@ function mob_class:check_avoid (self_pos)
if self.avoiding then
if self:navigation_finished () then
self.avoiding = false
self.avoiding = nil
elseif not self.avoiding:is_valid () then
self.avoiding = nil
self:cancel_navigation ()
self:halt_in_tracks ()
else
local avoid_pos = self.avoiding:get_pos ()
local distance = vector.distance (self_pos, avoid_pos)
if distance < 7.0 then
self.gowp_velocity
= self.movement_speed * self.runaway_bonus_near
else
self.gowp_velocity
= self.movement_speed * self.runaway_bonus_far
end
end
return true
else
-- Search for nearby mobs to avoid.
local target, max_distance, target_pos
local range = self.runaway_view_range
local objects
= minetest.get_objects_inside_radius (self_pos, self.view_range)
= minetest.get_objects_inside_radius (self_pos, range)
local runaway_from_players
= table.indexof (runaway_from, "players") ~= -1
for _, object in ipairs (objects) do
local entity = object:get_luaentity ()
local eligible = false
if entity
and table.indexof (runaway_from, entity.name) ~= -1
and self:target_visible (self_pos, object) then
eligible = true
elseif object:is_player ()
and runaway_from_players
and not minetest.is_creative_enabled (object:get_player_name ())
and self:target_visible (self_pos, object) then
eligible = true
end
if eligible then
local pos = object:get_pos ()
local distance = vector.distance (self_pos, pos)
if not max_distance or distance < max_distance then
@ -781,16 +825,11 @@ function mob_class:check_avoid (self_pos)
if target then
local pos = self:target_away_from (self_pos, target_pos)
if pos and vector.distance (pos, target_pos) > max_distance then
self:gopath (pos)
self.avoiding = true
-- Interupt other activities.
self.frightened = false
self.attack = nil
self.mate = nil
self.following = nil
self.herd_following = nil
self.pacing = false
return true
local initial_velocity
= self.movement_speed * self.runaway_bonus_near
self:gopath (pos, nil, false, initial_velocity)
self.avoiding = target
return "avoiding"
end
end
end
@ -833,34 +872,22 @@ function mob_class:check_following (self_pos, dtime)
if distance < self.follow_distance
and distance > self.stop_distance and self:follow_holding (player) then
self.following = player
-- Interrupt other activities.
self.herd_following = nil
self.pacing = nil
return true
return "following"
end
end
end
return false
end
function mob_class:run_ai (dtime)
local idle = true
local pos = self.object:get_pos ()
if self.dead then
self:halt_in_tracks ()
return
end
function mob_class:check_avoid_sunlight (pos)
if self.avoiding_sunlight then
idle = false
-- Still seeking sunlight?
if self:navigation_finished () then
self.avoiding_sunlight = false
self:set_animation ("stand")
end
elseif idle and self.avoids_sunlight
return true
elseif self.avoids_sunlight
and (self.time_of_day > 0.2 and self.time_of_day < 0.8)
and self.sunlight > 12
and mcl_burning.is_burning (self.object) then
@ -870,32 +897,20 @@ function mob_class:run_ai (dtime)
self:gopath (tpos, nil, true,
self.movement_speed * self.run_bonus)
self.avoiding_sunlight = true
-- Interupt other activities.
self.avoiding = false
self.attack = nil
self.mate = nil
self.following = nil
self.herd_following = nil
self.pacing = false
idle = false
return "avoiding_sunlight"
end
end
return false
end
if idle and self:check_avoid (pos) then
idle = false
end
if self.attack_type and not self.avoiding_sunlight then
idle = not self:check_attack (pos, dtime)
end
function mob_class:check_frightened (pos)
if self.frightened then
idle = false
-- Still frightened?
if self:navigation_finished () then
self.frightened = false
self:set_animation ("stand")
end
return true
else
if self:is_frightened () then
-- If this mob is burning, search for water.
@ -908,37 +923,28 @@ function mob_class:run_ai (dtime)
tpos = self:pacing_target (pos, 5, 4, {"group:solid"})
end
if tpos then
self.frightened = true
self:gopath (tpos, nil, true,
self.movement_speed * self.run_bonus)
-- Interupt other activities.
self.avoiding = false
self.mate = nil
self.following = nil
self.herd_following = nil
self.pacing = false
idle = false
self.frightened = true
return "frightened"
end
end
end
if idle and (self:check_breeding (pos)
or self:check_following (pos)
or self:follow_herd (pos)) then
idle = false
end
return false
end
function mob_class:check_pace (pos)
if self.pacing then
idle = false
-- Still pacing?
if self:navigation_finished () then
self.pacing = false
self:set_animation ("stand")
end
return true
else
-- Should pace?
if idle and self.ai_idle_time > self.pace_interval then
if self.ai_idle_time > self.pace_interval then
-- Minecraft mobs pace to random positions
-- within a 20 block distance lengthwise and
-- 14 blocks vertically.
@ -949,19 +955,57 @@ function mob_class:run_ai (dtime)
-- swimming.
groups = self.swims_in
end
local target = self:pacing_target (pos, 10, self.pace_height, groups)
local width, height = self.pace_width, self.pace_height
local target = self:pacing_target (pos, width, height, groups)
if target and self:gopath (target) then
self.pacing = true
return "pacing"
end
idle = false
end
return false
end
end
function mob_class:replace_activity (activity_name)
if self._active_activity then
self[self._active_activity] = nil
end
self._active_activity = activity_name
end
function mob_class:run_ai (dtime)
local pos = self.object:get_pos ()
if self.dead then
self:halt_in_tracks ()
return
end
local active = nil
for _, fn in ipairs (self.ai_functions) do
active = fn (self, pos, dtime)
if active then
if active ~= true then
local current = self._active_activity
-- Cancel the current activity.
if current and current ~= active then
self[current] = nil
end
self._active_activity = active
end
break
end
end
if not idle then
if active then
self.ai_idle_time = 0
elseif self.ai_idle_time < IDLE_TIME_MAX then
self:set_animation ("stand")
self._active_activity = nil
self.ai_idle_time = self.ai_idle_time + dtime
self:cancel_navigation ()
self:halt_in_tracks ()
end
end
@ -986,6 +1030,20 @@ local function aquatic_movement_step (self, dtime, moveresult)
self._acc_no_gravity
= self._immersion_depth >= self.head_eye_height
end
if self.flops and self._immersion_depth and self._immersion_depth <= 0 then
local touching_ground
touching_ground = moveresult.touching_ground
or moveresult.standing_on_object
if touching_ground then
local flop_velocity = {
x = math.random () - 0.5 * 2,
y = 8.0,
z = math.random () - 0.5 * 2,
}
self.object:add_velocity (flop_velocity)
end
end
self._acc_y_fixed = nil
mob_class.movement_step (self, dtime, moveresult)
end
@ -1001,29 +1059,218 @@ function mob_class:fish_do_go_pos (dtime, moveresult)
local move_speed = 0.4
self._acc_movement_speed = current_speed
self.acc_speed = current_speed
self.acc_speed = move_speed
self.acc_dir.z = current_speed / 20
if dy ~= 0 then
-- acc_speed_aquatic * current_speed/20 is the speed
-- at which the mob will move horizontally, but
-- current_speed * dy/dxyz provides an absolute rate
-- of ascent or descent.
local dxyz = math.sqrt (dx * dx + dy * dy + dz * dz)
local t1 = self.acc_dir.z * move_speed
local t2 = current_speed * (dy / dxyz) * 0.1
self.acc_dir.z = t1
self.acc_dir.y = t2
self.acc_dir = vector.normalize (self.acc_dir)
self.acc_speed = math.abs (t1) + math.abs (t2)
self._acc_y_fixed = t2
end
local dir = math.atan2 (dz, dx) - math.pi / 2
local rotation = clip_rotation (self.object:get_yaw (), dir, math.pi / 2)
self.object:set_yaw (rotation)
end
function mob_class.school_init_group (list)
-- Designate one of the school's fish as its leader.
local leader = list[math.random (#list)]
local entity = leader:get_luaentity ()
entity._school = {}
entity._desired_school_size = #list - 1
for _, item in ipairs (list) do
if item ~= leader then
local mob = item:get_luaentity ()
table.insert (entity._school, list)
mob._leader = leader
mob:replace_activity ("_leader")
end
end
end
local function find_school_leader (list, species, cluster)
for _, mob in ipairs (list) do
local entity = mob:get_luaentity ()
if entity and entity.name == species
and entity._school
and #entity._school > 1
and #entity._school < cluster then
return entity
end
end
end
function mob_class:check_schooling (self_pos, list)
if self._leader then
if not self._leader:is_valid () then
self._leader = nil
return false
end
local leader_pos = self._leader:get_pos ()
if vector.distance (leader_pos, self_pos) > 11.0 then
local luaentity = self._leader:get_luaentity ()
local index = table.indexof (luaentity._school, self.object)
assert (index >= 1)
table.remove (luaentity._school, index)
self._leader = nil
return false
end
if self:check_timer ("school_pathfind", 0.5) then
self:gopath (leader_pos, nil, false, nil, nil, 3)
end
return true
elseif self._school and #self._school > 0 then
-- This fish already leads a school. Remove invalid
-- entries from its list of members.
local cleaned = {}
for _, follower in ipairs (self._school) do
if follower:is_valid () then
table.insert (cleaned, follower)
end
end
self._school = cleaned
return false
elseif self:check_timer ("form_school", (200 + math.random (20)) / 40) then
local nearby = minetest.get_objects_inside_radius (self_pos, 8)
local cluster = self._school_size or self.spawn_in_group or 4
local leader = find_school_leader (nearby, self.name, cluster) or self
leader._school = leader._school or {}
-- Assign nearby unassigned mobs other than the
-- selected leader to its school.
for _, mob in ipairs (nearby) do
local entity = mob:get_luaentity ()
if entity
and entity.object ~= leader.object
and entity.name == self.name
and (not entity._school or #entity._school == 0)
and (not entity._leader) then
entity._leader = leader.object
entity:replace_activity ("_leader")
table.insert (leader._school, mob)
end
end
-- Was this mob assigned to a leader, or has it gained
-- a school?
if self._school and #self._school > 0 then
return false
end
if self._leader then
return "_leader"
end
return false
end
end
function mob_class:configure_aquatic_mob ()
self.pacing_target = aquatic_pacing_target
self.motion_step = self.aquatic_step
self.movement_step = aquatic_movement_step
self._acc_no_gravity = false
end
------------------------------------------------------------------------
-- Flying mob behavior. This only applies to mobs that are truly
-- adapted to flight in all respects, which excludes bats, whose
-- flight is implemented in their AI loop, or ghasts, which do not
-- pathfind.
------------------------------------------------------------------------
function mob_class:airborne_do_go_pos (dtime, moveresult)
local target = self.movement_target or vector.zero ()
local vel = self.movement_velocity
local self_pos = self.object:get_pos ()
local dx, dy, dz = target.x - self_pos.x,
target.y - self_pos.y,
target.z - self_pos.z
local touching_ground = moveresult.touching_ground
or moveresult.standing_on_object
-- Replace movement_speed with airborne_speed if airborne.
if not touching_ground then
vel = self.airborne_speed
self:set_animation ("fly")
else
self:set_animation ("run")
end
local yaw = math.atan2 (dz, dx) - math.pi / 2
local old_yaw = self.object:get_yaw ()
local clipped = clip_rotation (old_yaw, yaw, math.pi / 2)
self.object:set_yaw (clipped)
self:set_velocity (vel)
local xz_magnitude = math.sqrt (dx * dx + dz * dz)
if math.abs (dy) > 1.0e-5 or math.abs (xz_magnitude) > 1.0e-5 then
-- Vertical acceleration is not adjusted by the pitch
-- in order to simulate real world flight.
self.acc_dir.y = dy > 0.0 and vel / 20 or -vel / 20
end
self._acc_no_gravity = true
end
local function airborne_movement_step (self, dtime, moveresult)
if self.dead then
return
end
if self.movement_goal == nil then
-- Arrest movement.
self.acc_dir.z = 0
self.acc_dir.y = 0
self.acc_dir.x = 0
self.acc_speed = 0
if not self._hovers then
self._acc_no_gravity = false
end
return
elseif self.movement_goal == "go_pos" then
self:do_go_pos (dtime, moveresult)
end
end
local function airborne_pacing_target (self, pos, width, height, groups)
return self:airborne_pacing_target (pos, width, height, groups)
end
function mob_class:airborne_pacing_target (pos, width, height, groups)
-- First, generate a position within 90 degrees of this mob's
-- current direction of sight.
local dir = self.object:get_yaw ()
dir = { x = -math.sin (dir), z = math.cos (dir), }
local node_pos = vector.copy (pos)
node_pos.x = math.floor (node_pos.x + 0.5)
node_pos.y = math.floor (node_pos.y + 0.5)
node_pos.z = math.floor (node_pos.z + 0.5)
for i = 1, 10 do
local node = self:random_node_direction (width, height, dir, math.pi / 20)
if node then
local target = node_pos + node
local class = self:gwp_classify_for_movement (target)
-- Is this node walkable?
if class == "WALKABLE" then
-- Move an arbitrary number of blocks
-- into the air above this node.
local n = math.random (3)
repeat
target.y = target.y + 1
if not self:gwp_classify_for_movement (target) == "OPEN" then
target.y = target.y - 1
break
end
n = n - 1
until n < 1
return target
end
end
end
end
function mob_class:configure_airborne_mob ()
self.movement_step = airborne_movement_step
self.do_go_pos = mob_class.airborne_do_go_pos
self.pacing_target = airborne_pacing_target
end

@ -288,7 +288,7 @@ function h_to_nearest_target (node, context)
return best_distance
end
function mob_class:gwp_initialize (targets, range)
function mob_class:gwp_initialize (targets, range, tolerance)
local context = self:new_gwp_context ()
-- Compute pathfinding bounds.
@ -301,6 +301,7 @@ function mob_class:gwp_initialize (targets, range)
pos.z - range)
context.maxpos = vector.new (pos.x + range, pos.y + range,
pos.z + range)
context.tolerance = tolerance or context.tolerance
-- Establish a limit on the distance of routes and on the
-- number of nodes examined.
@ -395,31 +396,33 @@ function mob_class:gwp_cycle (context, timeout)
-- Enter each neighbor into the queue.
local neighbors = self:gwp_edges (context, node)
for _, neighbor in ipairs (neighbors) do
-- What is the distance from hence to this
-- neighbor?
local dist = d (node, neighbor)
neighbor.total_d = node.total_d + dist
if dist <= context.range
and neighbor.total_d < context.maxdist then
local new_g = node.g + dist + neighbor.penalty
local new_h = h_to_nearest_target (neighbor, context) * 1.5 -- Minecraft value.
if set:contains (neighbor) then
-- Re-enqueue this neighbor if this
-- path to it is shorter.
if new_g < neighbor.g then
if not neighbor.covered then
-- What is the distance from hence to this
-- neighbor?
local dist = d (node, neighbor)
neighbor.total_d = node.total_d + dist
if dist <= context.range
and neighbor.total_d < context.maxdist then
local new_g = node.g + dist + neighbor.penalty
local new_h = h_to_nearest_target (neighbor, context) * 1.5 -- Minecraft value.
if set:contains (neighbor) then
-- Re-enqueue this neighbor if this
-- path to it is shorter.
if new_g < neighbor.g then
neighbor.g = new_g
neighbor.h = new_h
neighbor.referrer = node
set:update (neighbor, new_g + new_h)
end
else
-- N.B. in this branch neighbor.g and
-- .h might not yet have been
-- computed.
neighbor.g = new_g
neighbor.h = new_h
neighbor.referrer = node
set:update (neighbor, new_g + new_h)
set:enqueue (neighbor, new_g + new_h)
end
else
-- N.B. in this branch neighbor.g and
-- .h might not yet have been
-- computed.
neighbor.g = new_g
neighbor.h = new_h
neighbor.referrer = node
set:enqueue (neighbor, new_g + new_h)
end
end
end
@ -661,10 +664,7 @@ local function gwp_edges_1 (self, context, parent, floor, xoff, zoff, jump)
object = nil
end
end
if object and not object.covered then
return object
end
return nil
return object
end
end
@ -672,20 +672,20 @@ mob_class.gwp_penalties = {
-- A penalty < 0 indicates unconditional rejection, while one
-- greater than zero compounds the heuristic distance.
BLOCKED = -1.0,
IGNORE = -1.0,
OPEN = 0.0,
WALKABLE = 0.0,
DOOR_OPEN = 0.0,
TRAPDOOR = 0.0,
WATER = 8.0,
DANGER_FIRE = 8.0,
DAMAGE_FIRE = 16.0,
DAMAGE_OTHER = -1.0,
DANGER_FIRE = 8.0,
DANGER_OTHER = 8.0,
DOOR_IRON_CLOSED = -1.0,
DOOR_OPEN = 0.0,
DOOR_WOOD_CLOSED = -1.0,
FENCE = -1.0,
IGNORE = -1.0,
LAVA = -1.0,
OPEN = 0.0,
TRAPDOOR = 0.0,
WALKABLE = 0.0,
WATER = 8.0,
}
local gwp_floortypes = {
@ -818,7 +818,7 @@ local function gwp_classify_node_1 (self, pos)
-- Otherwise, this is walkable. Adjust its
-- class according to its surroundings.
return floortype or self:gwp_classify_surroundings (pos)
return floortype or self:gwp_classify_surroundings (pos, "WALKABLE")
end
return class_1
@ -924,7 +924,7 @@ local gwp_influence_by_type = {
DAMAGE_OTHER = "DANGER_OTHER",
}
function mob_class:gwp_classify_surroundings (pos)
function mob_class:gwp_classify_surroundings (pos, default)
local x, y, z = pos.x, pos.y, pos.z
local v = gwp_classify_surroundings_scratch
local influences = gwp_influence_by_type
@ -1146,7 +1146,7 @@ function mob_class:gwp_classify_surroundings (pos)
end
-- Otherwise the node is walkable.
return "WALKABLE"
return default
end
function mob_class:gwp_check_diagonal (node, flanking1, flanking2)
@ -1194,19 +1194,19 @@ function mob_class:gwp_edges (context, node)
-- Consider diagonal neighbors at an angle.
if self:gwp_check_diagonal (node, c1, c2) then
local d = gwp_edges_1 (self, context, node, floor, 1, 1)
if d then n = n + 1; array[n] = d end
if d and d.penalty >= 0.0 then n = n + 1; array[n] = d end
end
if self:gwp_check_diagonal (node, c1, c4) then
local d = gwp_edges_1 (self, context, node, floor, 1, -1)
if d then n = n + 1; array[n] = d end
if d and d.penalty >= 0.0 then n = n + 1; array[n] = d end
end
if self:gwp_check_diagonal (node, c3, c2) then
local d = gwp_edges_1 (self, context, node, floor, -1, 1)
if d then n = n + 1; array[n] = d end
if d and d.penalty >= 0.0 then n = n + 1; array[n] = d end
end
if self:gwp_check_diagonal (node, c3, c4) then
local d = gwp_edges_1 (self, context, node, floor, -1, -1)
if d then n = n + 1; array[n] = d end
if d and d.penalty >= 0.0 then n = n + 1; array[n] = d end
end
array[n + 1] = nil
return array
@ -1220,7 +1220,7 @@ if minetest.global_exists ("jit") then
-- jit.off (gwp_basic_classify, true)
-- jit.off (mob_class.gwp_essay_drop, true)
-- jit.off (mob_class.gwp_essay_jump, true)
jit.opt.start ("maxtrace=8000", "maxrecord=16000", "minstitch=3", "maxmcode=40960")
jit.opt.start ("maxtrace=16000", "maxrecord=24000", "minstitch=3", "maxmcode=81920")
end
----------------------------------------------------------------------------------
@ -1343,7 +1343,7 @@ local cdef = {
minetest.chat_send_player (playername, msg)
local entity = mob:get_luaentity ()
entity.pathfinding_context = entity:gwp_initialize ({position}, 128)
entity.pathfinding_context = entity:gwp_initialize ({position})
entity.pathfinding_duration = 0
local old_step = entity.on_step
entity._old_onstep = old_step
@ -1524,6 +1524,19 @@ minetest.register_tool ("mcl_mobs:pathfinder_edge_stick", {
on_use = print_node_neighbors,
})
minetest.register_tool ("mcl_mobs:pathfinder_dump_stick", {
description = "Dump object luaentities",
inventory_image = "default_stick.png",
groups = { testtool = 1, disable_repair = 1,
not_in_creative_inventory = 1, },
on_use = function (itemstack, user, pointed_thing)
if not (user and user:is_player ()) or pointed_thing.type ~= "object" then
return
end
dbg.pp (pointed_thing.ref:get_luaentity ())
end,
})
-- Number of seconds per step permissible for pathfinding.
local PATHFIND_PER_STEP = 0.035
local PATHFIND_TIMEOUT = 10.0 / 1000
@ -1625,15 +1638,47 @@ local function waterbound_gwp_classify_node (self, context, pos)
vector.z = z
local class = waterbound_gwp_basic_classify (vector)
context.class_cache[hash] = class
if class then
return class
end
end
end
end
context.class_cache[hash] = "WATER"
return "WATER"
end
local function waterbound_gwp_classify_for_movement (self, pos)
local b_width, b_height
local collisionbox = self.collisionbox
local width = math.max (0, collisionbox[4] - collisionbox[1])
local height = math.max (0, collisionbox[5] - collisionbox[2])
local length = math.max (0, collisionbox[6] - collisionbox[3])
b_width = floor (math.max (width, length) + 1.0) - 1
b_height = math.ceil (height) - 1
local sx, sy, sz = pos.x, pos.y, pos.z
for x = sx, sx + b_width do
for y = sy, sy + b_height do
for z = sz, sz + b_width do
vector.x = x
vector.y = y
vector.z = z
local class = waterbound_gwp_basic_classify (vector)
if class then
return class
end
end
end
end
-- Water should be reported as walkable, to match callers'
-- expectations.
return "WALKABLE"
end
-- The final two elements of each vector define the indices of nodes
-- on either side of a diagonal movement that must be checked in
-- validating it.
@ -1731,13 +1776,534 @@ local function waterbound_gwp_start (self, context)
return pos
end
local function waterbound_gwp_initialize (self, targets, range)
local context = mob_class.gwp_initialize (self, targets, range)
if not context then
return nil
end
local cbox = self.collisionbox
-- Offset Y positions of reconstructed path nodes so as to
-- center the mob in the said nodes.
local cbox_height = cbox[5] - cbox[2]
context.y_offset = -(cbox_height / 2) - cbox[2]
return context
end
------------------------------------------------------------------------
-- Pathfinding for airborne mobs.
------------------------------------------------------------------------
local function airborne_gwp_edges_1 (self, context, pos)
local class = self:gwp_classify_node (context, pos)
local penalty = self.gwp_penalties[class]
if penalty >= 0.0 then
local node = self:get_gwp_node (context, pos.x, pos.y, pos.z)
node.class = class
node.penalty = math.max (node.penalty or 0, penalty)
if class == "WALKABLE" then
-- Prefer direct paths to paths over land.
node.penalty = node.penalty + 1
end
return node
end
return nil
end
local airborne_gwp_edges_scratch = vector.zero ()
local airborne_gwp_edges_buffer = {}
local function airborne_gwp_edges (self, context, node)
local results = airborne_gwp_edges_buffer
local n = 0
local v = airborne_gwp_edges_scratch
v.x = node.x + 0
v.y = node.y + 0
v.z = node.z + 1
local e1 = airborne_gwp_edges_1 (self, context, v)
if e1 then
n = n + 1; results[n] = e1
end
v.x = node.x + -1
v.y = node.y + 0
v.z = node.z + 0
local e2 = airborne_gwp_edges_1 (self, context, v)
if e2 then
n = n + 1; results[n] = e2
end
v.x = node.x + 0
v.y = node.y + 0
v.z = node.z + -1
local e3 = airborne_gwp_edges_1 (self, context, v)
if e3 then
n = n + 1; results[n] = e3
end
v.x = node.x + 1
v.y = node.y + 0
v.z = node.z + 0
local e4 = airborne_gwp_edges_1 (self, context, v)
if e4 then
n = n + 1; results[n] = e4
end
v.x = node.x + 0
v.y = node.y + -1
v.z = node.z + 0
local e5 = airborne_gwp_edges_1 (self, context, v)
if e5 then
n = n + 1; results[n] = e5
end
v.x = node.x + 0
v.y = node.y + 1
v.z = node.z + 0
local e6 = airborne_gwp_edges_1 (self, context, v)
if e6 then
n = n + 1; results[n] = e6
end
v.x = node.x + 0
v.y = node.y + 1
v.z = node.z + 1
local e7 = airborne_gwp_edges_1 (self, context, v)
if e7
and e6
and e1 then
n = n + 1; results[n] = e7
end
v.x = node.x + -1
v.y = node.y + 1
v.z = node.z + 0
local e8 = airborne_gwp_edges_1 (self, context, v)
if e8
and e6
and e2 then
n = n + 1; results[n] = e8
end
v.x = node.x + 0
v.y = node.y + 1
v.z = node.z + -1
local e9 = airborne_gwp_edges_1 (self, context, v)
if e9
and e6
and e3 then
n = n + 1; results[n] = e9
end
v.x = node.x + 1
v.y = node.y + 1
v.z = node.z + 0
local e10 = airborne_gwp_edges_1 (self, context, v)
if e10
and e6
and e4 then
n = n + 1; results[n] = e10
end
v.x = node.x + 0
v.y = node.y + -1
v.z = node.z + 1
local e11 = airborne_gwp_edges_1 (self, context, v)
if e11
and e5
and e1 then
n = n + 1; results[n] = e11
end
v.x = node.x + -1
v.y = node.y + -1
v.z = node.z + 0
local e12 = airborne_gwp_edges_1 (self, context, v)
if e12
and e5
and e2 then
n = n + 1; results[n] = e12
end
v.x = node.x + 0
v.y = node.y + -1
v.z = node.z + -1
local e13 = airborne_gwp_edges_1 (self, context, v)
if e13
and e5
and e3 then
n = n + 1; results[n] = e13
end
v.x = node.x + 1
v.y = node.y + -1
v.z = node.z + 0
local e14 = airborne_gwp_edges_1 (self, context, v)
if e14
and e5
and e4 then
n = n + 1; results[n] = e14
end
v.x = node.x + -1
v.y = node.y + 0
v.z = node.z + 1
local e15 = airborne_gwp_edges_1 (self, context, v)
if e15
and e1
and e2 then
n = n + 1; results[n] = e15
end
v.x = node.x + 1
v.y = node.y + 0
v.z = node.z + -1
local e16 = airborne_gwp_edges_1 (self, context, v)
if e16
and e1
and e4 then
n = n + 1; results[n] = e16
end
v.x = node.x + -1
v.y = node.y + 0
v.z = node.z + -1
local e17 = airborne_gwp_edges_1 (self, context, v)
if e17
and e3
and e2 then
n = n + 1; results[n] = e17
end
v.x = node.x + 1
v.y = node.y + 0
v.z = node.z + -1
local e18 = airborne_gwp_edges_1 (self, context, v)
if e18
and e3
and e4 then
n = n + 1; results[n] = e18
end
v.x = node.x + -1
v.y = node.y + 1
v.z = node.z + 1
local e19 = airborne_gwp_edges_1 (self, context, v)
if e19
and e15
and e1
and e2
and e6
and e7
and e8 then
n = n + 1; results[n] = e19
end
v.x = node.x + 1
v.y = node.y + 1
v.z = node.z + -1
local e20 = airborne_gwp_edges_1 (self, context, v)
if e20
and e16
and e1
and e4
and e6
and e7
and e10 then
n = n + 1; results[n] = e20
end
v.x = node.x + -1
v.y = node.y + 1
v.z = node.z + -1
local e21 = airborne_gwp_edges_1 (self, context, v)
if e21
and e17
and e3
and e2
and e6
and e9
and e8 then
n = n + 1; results[n] = e21
end
v.x = node.x + 1
v.y = node.y + 1
v.z = node.z + -1
local e22 = airborne_gwp_edges_1 (self, context, v)
if e22
and e18
and e3
and e4
and e6
and e9
and e10 then
n = n + 1; results[n] = e22
end
v.x = node.x + -1
v.y = node.y + -1
v.z = node.z + 1
local e23 = airborne_gwp_edges_1 (self, context, v)
if e23
and e15
and e1
and e2
and e5
and e11
and e12 then
n = n + 1; results[n] = e23
end
v.x = node.x + 1
v.y = node.y + -1
v.z = node.z + -1
local e24 = airborne_gwp_edges_1 (self, context, v)
if e24
and e16
and e1
and e4
and e5
and e11
and e14 then
n = n + 1; results[n] = e24
end
v.x = node.x + -1
v.y = node.y + -1
v.z = node.z + -1
local e25 = airborne_gwp_edges_1 (self, context, v)
if e25
and e17
and e3
and e2
and e5
and e13
and e12 then
n = n + 1; results[n] = e25
end
v.x = node.x + 1
v.y = node.y + -1
v.z = node.z + -1
local e26 = airborne_gwp_edges_1 (self, context, v)
if e26
and e18
and e3
and e4
and e5
and e13
and e14 then
n = n + 1; results[n] = e26
end
results[n + 1] = nil
return results
end
local function aabb_avg_size (aabb)
return ((aabb[4] - aabb[1])
+ (aabb[5] - aabb[2])
+ (aabb[6] - aabb[3])) / 3
end
local function airborne_gwp_start_1 (self, self_pos, context)
local cbox = self.collisionbox
local size = aabb_avg_size (cbox)
local positions = {}
if size < 1.0 then
local v1, v2, v3, v4
v1 = vector.offset (self_pos, cbox[1], 0, cbox[3])
v1 = vector.apply (v1, round_trunc)
v2 = vector.offset (self_pos, cbox[4], 0, cbox[3])
v2 = vector.apply (v2, round_trunc)
v3 = vector.offset (self_pos, cbox[1], 0, cbox[6])
v3 = vector.apply (v3, round_trunc)
v4 = vector.offset (self_pos, cbox[4], 0, cbox[6])
v4 = vector.apply (v4, round_trunc)
table.insert (positions, v1)
table.insert (positions, v2)
table.insert (positions, v3)
table.insert (positions, v4)
return ipairs (positions)
else
local n = 10
cbox = vector.copy (cbox)
cbox[1] = floor (cbox[1] + self_pos.x + 0.5)
cbox[2] = floor (cbox[2] + self_pos.y + 0.5)
cbox[3] = floor (cbox[3] + self_pos.z + 0.5)
cbox[4] = floor (cbox[4] + self_pos.x + 0.5)
cbox[5] = floor (cbox[5] + self_pos.y + 0.5)
cbox[6] = floor (cbox[6] + self_pos.z + 0.5)
local xw, yw, zw = cbox[4] - cbox[1] + 1,
cbox[5] - cbox[2] + 1,
cbox[6] - cbox[3] + 1
return function ()
if n <= 0 then
return nil
end
n = n - 1
return 10 - n, {
x = cbox[1] + math.random (0, xw + 1),
y = cbox[2] + math.random (0, yw + 1),
z = cbox[3] + math.random (0, zw + 1),
}
end
end
end
local function airborne_gwp_start (self, context)
local self_pos = self.object:get_pos ()
-- TODO: ascend to surface of water if floating.
for _, pos in airborne_gwp_start_1 (self, self_pos, context) do
local class = self:gwp_classify_node (context, pos)
if class and self.gwp_penalties[class] >= 0.0 then
return pos
end
end
return nil
end
local function airborne_gwp_initialize (self, targets, range)
local context = mob_class.gwp_initialize (self, targets, range)
if not context then
return nil
end
local cbox = self.collisionbox
-- Offset Y positions of reconstructed path nodes so as to
-- center the mob's feet, or the mob itself, if it is less
-- than one node in height, in the said nodes.
local feet_height = math.min (1, cbox[5] - cbox[2])
context.y_offset = -(feet_height / 2) - cbox[2]
return context
end
local gwp_airborne_floortypes = {
BLOCKED = "WALKABLE",
DAMAGE_FIRE = "DAMAGE_FIRE",
DAMAGE_OTHER = "DAMAGE_OTHER",
DANGER_FIRE = "WALKABLE",
DANGER_OTHER = "WALKABLE",
DOOR_IRON_CLOSED = "WALKABLE",
DOOR_WOOD_CLOSED = "WALKABLE",
FENCE = "WALKABLE",
IGNORE = "IGNORE",
DOOR_OPEN = "WALKABLE",
LAVA = "OPEN",
OPEN = "OPEN",
TRAPDOOR = "WALKABLE",
WALKABLE = "WALKABLE",
WATER = "OPEN",
}
local function airborne_gwp_classify_node_1 (self, pos)
local class_1 = gwp_basic_classify (pos)
-- If this block (the block in which the mob stands) is air,
-- evaluate the node below.
if class_1 == "OPEN" then
-- Don't cons a new vector.
local pos_2 = gwp_classify_node_1_scratch
pos_2.x = pos.x
pos_2.y = pos.y - 1
pos_2.z = pos.z
local class_2 = gwp_basic_classify (pos_2)
local floortype = gwp_airborne_floortypes[class_2]
-- Open nodes should also be modified by their
-- surroundings with airborne mobs.
if floortype == "OPEN" or floortype == "WALKABLE" then
floortype = self:gwp_classify_surroundings (pos, floortype)
end
return floortype
end
return class_1
end
-- This duplicates much of `gwp_classify_node', but the performance
-- improvement yielded by calling an upvalue is substantial.
local function airborne_gwp_classify_node (self, context, pos)
local hash = hashpos (context, pos.x, pos.y, pos.z)
local cache = context.class_cache[hash]
-- This is very expensive, as minetest.get_node conses too
-- much.
if cache then
-- if record_pathfinding_stats then
-- gwp_cc_hits = gwp_cc_hits + 1
-- end
return cache
end
-- if record_pathfinding_stats then
-- gwp_cc_misses = gwp_cc_misses + 1
-- end
local sx, sy, sz = pos.x, pos.y, pos.z
local worst, penalty = "OPEN", 0.0
local vector = gwp_classify_node_scratch
local b_width, b_height
local penalties = self.gwp_penalties
b_width = context.mob_width - 1
b_height = context.mob_height - 1
for x = sx, sx + b_width do
for y = sy, sy + b_height do
for z = sz, sz + b_width do
vector.x = x
vector.y = y
vector.z = z
local class = airborne_gwp_classify_node_1 (self, vector)
-- Report impassible nodes
-- immediately.
if penalties[class] < 0.0 then
return class
-- Otherwise select the worst class possible.
elseif worst == "OPEN" or penalty < penalties[class] then
penalty = penalties[class]
worst = class
end
end
end
end
cache = worst
context.class_cache[hash] = cache
return cache
end
local function airborne_gwp_classify_for_movement (self, pos)
local sx, sy, sz = pos.x, pos.y, pos.z
local worst, penalty = "OPEN", 0.0
local vector = gwp_classify_node_scratch
local b_width, b_height
local penalties = self.gwp_penalties
local collisionbox = self.collisionbox
local width = math.max (0, collisionbox[4] - collisionbox[1])
local height = math.max (0, collisionbox[5] - collisionbox[2])
local length = math.max (0, collisionbox[6] - collisionbox[3])
b_width = floor (math.max (width, length) + 1.0) - 1
b_height = math.ceil (height) - 1
for x = sx, sx + b_width do
for y = sy, sy + b_height do
for z = sz, sz + b_width do
vector.x = x
vector.y = y
vector.z = z
local class = airborne_gwp_classify_node_1 (self, vector)
-- Report impassible nodes
-- immediately.
if penalties[class] < 0.0 then
return class
-- Otherwise select the worst class possible.
elseif worst == "OPEN" or penalty < penalties[class] then
penalty = penalties[class]
worst = class
end
end
end
end
return worst
end
------------------------------------------------------------------------
-- External interface.
------------------------------------------------------------------------
function mob_class:gopath (target, callback_arrived, prioritised, velocity, animation)
self:cancel_navigation ()
self.order = nil
local MAX_STALE_PATH_AGE = 0.25
function mob_class:gopath (target, callback_arrived, prioritised, velocity, animation, tolerance)
if self.waypoints then
local wp_target = self.waypoints[1]
-- Attempt to reuse existing paths if possible.
if wp_target and wp_target.x == floor (target.x + 0.5)
and wp_target.y == floor (target.y + 0.5)
and wp_target.z == floor (target.z + 0.5)
and self.waypoint_age < MAX_STALE_PATH_AGE then
return
end
end
self.gowp_velocity = velocity
self.gowp_animation = animation or "walk"
self.pathfinding_context = self:gwp_initialize ({target})
@ -1764,14 +2330,14 @@ function mob_class:gwp_timeout (dtime)
timeout = timeout - dtime
if timeout <= 0 then
local speed = self.acc_speed or self.movement_speed
local expected_speed
if self.movement_speed >= 1.0 then
if speed >= 1.0 then
-- The speed won't be scaled by itself.
expected_speed = self.movement_speed
expected_speed = speed
else
expected_speed
= self.movement_speed * self.movement_speed
expected_speed = speed * speed
end
local pos = self:gwp_position_on_path ()
if previous_pos then
@ -1819,11 +2385,19 @@ function mob_class:next_waypoint (dtime)
-- destinations that are too distant.
if waypoints then
self.waypoints = waypoints
self.waypoint_age = 0
-- if self.name == "mobs_mc:parrot" then
-- create_path_particles (waypoints, "repetitivestrain")
-- end
else
self:cancel_navigation ()
end
self:set_animation (self.gowp_animation)
self.pathfinding_context = nil
end
elseif self.waypoints then
self.waypoint_age = self.waypoint_age + dtime
self:gwp_next_waypoint (dtime)
self:gwp_timeout (dtime)
end
@ -1833,7 +2407,6 @@ function mob_class:gwp_next_waypoint (dtime)
local waypoints = self.waypoints
if #waypoints < 1 then
self:cancel_navigation ()
self.movement_goal = nil
self:halt_in_tracks ()
if self.callback_arrived then
self:callback_arrived ()
@ -1879,11 +2452,11 @@ local function obstruction_is_water (name, def)
or name == "mclx_core:river_water_source"
end
-- N.B.: also reused for airborne mobs.
local function aquatic_gwp_next_waypoint (self, dtime)
local waypoints = self.waypoints
if #waypoints < 1 then
self:cancel_navigation ()
self.movement_goal = nil
self:halt_in_tracks ()
if self.callback_arrived then
self:callback_arrived ()
@ -1910,13 +2483,21 @@ local function aquatic_gwp_next_waypoint (self, dtime)
while #waypoints > 1 do
local ahead = waypoints[#waypoints - 1]
local cbox = self.collisionbox
local center = {
local bottom = {
x = self_pos.x,
y = self_pos.y + cbox[2] + (cbox[5] - cbox[2]) / 2,
y = self_pos.y + cbox[2],
z = self_pos.z,
}
local ahead_adj = {
x = ahead.x,
-- Test a position slightly above the
-- target position to deal with
-- grazing lines of sight.
y = ahead.y + (cbox[5] - cbox[2]) / 2,
z = ahead.z,
}
if self:line_of_sight (center, ahead, obstruction_is_water) then
if self:line_of_sight (bottom, ahead_adj, obstruction_is_water) then
next_wp = ahead
waypoints[#waypoints] = nil
else
@ -1938,24 +2519,25 @@ local function aquatic_gwp_next_waypoint (self, dtime)
end
end
local function waterbound_gwp_initialize (self, targets, range)
local context = mob_class.gwp_initialize (self, targets, range)
local cbox = self.collisionbox
-- Offset Y positions of reconstructed path nodes so as to
-- center the mob in the said nodes.
local cbox_height = cbox[5] - cbox[2]
context.y_offset = -(cbox_height / 2) - cbox[2]
return context
end
function mob_class:gwp_configure_aquatic_mob ()
self.gwp_edges = waterbound_gwp_edges
self.gwp_start = waterbound_gwp_start
self.gwp_initialize = waterbound_gwp_initialize
self.gwp_classify_node = waterbound_gwp_classify_node
self.gwp_classify_for_movement
= waterbound_gwp_classify_for_movement
self.gwp_next_waypoint = aquatic_gwp_next_waypoint
local new_penalties = table.copy (mob_class.gwp_penalties)
new_penalties.WATER = 0.0
self.gwp_penalties = new_penalties
end
function mob_class:gwp_configure_airborne_mob ()
self.gwp_edges = airborne_gwp_edges
self.gwp_start = airborne_gwp_start
self.gwp_initialize = airborne_gwp_initialize
self.gwp_classify_node = airborne_gwp_classify_node
self.gwp_classify_for_movement
= airborne_gwp_classify_for_movement
self.gwp_next_waypoint = aquatic_gwp_next_waypoint
end

@ -151,7 +151,11 @@ end
-- collision function borrowed amended from jordan4ibanez open_ai mod
function mob_class:collision()
local pos = self.object:get_pos()
if not pos then return {0,0} end
local attach = self.object:get_attach ()
if not pos or attach then
return 0, 0
end
local x = 0
local z = 0
local cbox = self.collisionbox
@ -191,19 +195,10 @@ end
-- move mob in facing direction
function mob_class:set_velocity(v)
-- halt mob if it has been ordered to stay
if self.order == "stand" or self.order == "sit" then
self.acc_speed = 0
self.acc_dir.z = 0
return
end
local vv = self.object:get_velocity()
if vv then
self.acc_speed = v
-- Minecraft scales forward acceleration by desired
-- velocity in blocks/tick.
self.acc_dir.z = v / 20
end
self.acc_speed = v
-- Minecraft scales forward acceleration by desired
-- velocity in blocks/tick.
self.acc_dir.z = v / 20
end
-- calculate mob velocity
@ -656,7 +651,8 @@ function mob_class:env_damage (_, pos)
-- Calculate depth of immersion. This value is also utilized
-- by run_ai.
self._immersion_depth = 0
if (self.floats and minetest.get_item_group (self.standing_in, "water") > 0)
if ((self.floats or self.swims)
and minetest.get_item_group (self.standing_in, "water") > 0)
or minetest.get_item_group (self.head_in, "water") > 0 then
local ymin = self.collisionbox[2]
local height = self.collisionbox[5] - ymin
@ -932,7 +928,6 @@ local function horiz_collision (v, moveresult)
return moveresult.collides and not (moveresult.standing_on_object or moveresult.touching_ground)
end
--- TODO: flying and swimming mobs
--- TODO: correct uses of fall_speed and other modified fields
--- TODO: mob mounting
@ -979,9 +974,9 @@ function mob_class:motion_step (dtime, moveresult)
local standon = minetest.registered_nodes[self.standing_on]
local acc_dir = self.acc_dir
local acc_speed = self.acc_speed
local fall_speed = self.fall_speed
local fall_speed = self._acc_no_gravity and 0 or self.fall_speed
local touching_ground = moveresult.touching_ground or moveresult.standing_on_object
local jumping = self.order == "jump"
local jumping = self._jump
local p
local h_scale, v_scale
local climbing = false
@ -1016,13 +1011,13 @@ function mob_class:motion_step (dtime, moveresult)
if jumping or horiz_collision (v, moveresult) then
v.y = 4.0
jumping = false
self.order = ""
self._jump = false
end
climbing = true
self.reset_fall_damage = 1
end
local water_vec = not self.swims and self:check_water_flow ()
local water_vec = not self.swims and self:check_water_flow () or nil
local velocity_factor = standon._mcl_velocity_factor or 1
if standin.groups.water then
@ -1134,7 +1129,8 @@ function mob_class:motion_step (dtime, moveresult)
v.z = v.z + water_vec.z * LIQUID_FORCE * h_scale
end
if self.gravity_drag and v.y < 0 and not touching_ground then
if self.gravity_drag and v.y < 0
and fall_speed ~= 0 and not touching_ground then
local f = pow_by_step (self.gravity_drag, dtime)
v.y = v.y * f
end
@ -1167,13 +1163,13 @@ function mob_class:motion_step (dtime, moveresult)
end
-- Clear the jump flag even when jumping is not yet possible.
self.order = ""
self._jump = false
self.object:set_velocity (v)
self:check_collision ()
end
-- Simplified `motion_step' for true (i.e., not birds or blazes)
-- flying mobs.
-- flying mobs unaffected by gravity (i.e., not ghasts).
function mob_class:flying_step (dtime, moveresult)
if not moveresult then
@ -1221,6 +1217,8 @@ function mob_class:flying_step (dtime, moveresult)
v.x = v.x * p + fv.x
v.y = v.y * p + fv.y
v.z = v.z * p + fv.z
self._previously_floating = true
self.object:set_properties ({stepheight = 0.0})
self.object:set_velocity (v)
self:check_collision ()
end
@ -1239,6 +1237,7 @@ function mob_class:aquatic_step (dtime, moveresult)
if standin.groups.water then
local acc_speed = self.acc_speed
local acc_dir = self.acc_dir
local acc_fixed = self._acc_y_fixed or 0
local p = pow_by_step (AIR_DRAG, dtime)
local fv, scale
local v = self.object:get_velocity ()
@ -1250,7 +1249,7 @@ function mob_class:aquatic_step (dtime, moveresult)
fv = self:accelerate_relative (acc_dir, acc_speed * scale)
v.x = v.x * p + fv.x
v.y = v.y * p + fv.y
v.y = v.y * p + fv.y + acc_fixed * scale
v.z = v.z * p + fv.z
-- Apply gravity unless attacking mob.
@ -1260,6 +1259,8 @@ function mob_class:aquatic_step (dtime, moveresult)
self.object:set_velocity (v)
self:check_collision ()
self._previously_floating = true
self.object:set_properties ({stepheight = 0.0})
else
default_motion_step (self, dtime, moveresult)
end

@ -300,6 +300,7 @@ end
local function spawn_group(p,mob,spawn_on,group_max,group_min)
if not group_min then group_min = 1 end
local nn= minetest.find_nodes_in_area_under_air(vector.offset(p,-5,-3,-5),vector.offset(p,5,3,5),spawn_on)
local group_members = {}
local o
table.shuffle(nn)
if not nn or #nn < 1 then
@ -313,9 +314,16 @@ local function spawn_group(p,mob,spawn_on,group_max,group_min)
sp = get_water_spawn(sp)
end
o = mcl_mobs.spawn(sp,mob.name)
if o then dbg_spawn_succ = dbg_spawn_succ + 1 end
if o then
dbg_spawn_succ = dbg_spawn_succ + 1
table.insert (group_members, o)
end
end
end
local init_func = minetest.registered_entities[mob.name].initialize_group
if init_func and #group_members > 0 then
init_func (group_members)
end
return o
end

@ -61,8 +61,6 @@ mcl_mobs.register_mob("mobs_mc:bat", {
fall_damage = 0,
view_range = 16,
fear_height = 0,
jump = false,
fly = true,
makes_footstep_sound = false,
check_light = check_light,

@ -51,11 +51,16 @@ local cod = {
min = 1,
max = 1,},
},
runaway_from = {"players"},
runaway_bonus_near = 1.6,
runaway_bonus_far = 1.4,
runaway_view_range = 8,
visual_size = {x=3, y=3},
makes_footstep_sound = false,
swims = true,
pace_height = 1.0,
do_go_pos = mcl_mobs.mob_class.fish_do_go_pos,
flops = true,
breathes_in_water = true,
movement_speed = 14.0,
jump = false,

@ -232,6 +232,7 @@ function creeper_defs:custom_attack ()
self._swell_time = 0
self._swell_dir = 1
self:cancel_navigation ()
self:halt_in_tracks ()
end
mcl_mobs.register_mob("mobs_mc:creeper", table.merge(creeper_defs, {

@ -47,6 +47,7 @@ mcl_mobs.register_mob("mobs_mc:dolphin", {
visual_size = {x=3, y=3},
makes_footstep_sound = false,
swims = true,
flops = true,
do_go_pos = mcl_mobs.mob_class.dolphin_do_go_pos,
swims_in = { "mcl_core:water_source", "mclx_core:river_water_source" },
_player_check_time = 0,
@ -78,6 +79,7 @@ mcl_mobs.register_mob("mobs_mc:dolphin", {
if closest_player then
self.following = closest_player
self:replace_activity ("following")
mcl_potions.give_effect ("dolphin_grace", closest_player, 1, 5)
else
self.following = nil
@ -85,7 +87,6 @@ mcl_mobs.register_mob("mobs_mc:dolphin", {
end,
breathes_in_water = true,
idle_gravity_in_liquids = true,
jump = false,
view_range = 16,
fear_height = 4,
movement_speed = 24.0,

@ -127,6 +127,7 @@ mcl_mobs.register_mob("mobs_mc:ghast", {
textures = {
{"mobs_mc_ghast.png"},
},
attack_type = "null",
visual_size = {x=12, y=12},
sounds = {
shoot_attack = "mobs_fireball",
@ -153,7 +154,6 @@ mcl_mobs.register_mob("mobs_mc:ghast", {
shoot_offset = -5,
tracking_distance = 64,
passive = false,
jump = true,
jump_height = 4,
head_eye_height = 2.6,
floats=1,

@ -95,7 +95,6 @@ mcl_mobs.register_mob("mobs_mc:guardian", {
},
swims = true,
makes_footstep_sound = false,
jump = false,
})
-- spawn eggs

@ -100,7 +100,6 @@ mcl_mobs.register_mob("mobs_mc:guardian_elder", {
},
swims = true,
makes_footstep_sound = false,
jump = false,
do_custom = function (self, dtime)
local self_pos
-- See:

@ -38,7 +38,6 @@ local hoglin = {
death = "extra_mobs_hoglin_hurt",
distance = 16,
},
jump = true,
makes_footstep_sound = true,
movement_speed = 6.0,
retaliates = true,

@ -13,63 +13,6 @@ for x=-2, 2 do
end
end
--[[ Periodically check and teleport mob to owner if not sitting (order ~= "sit") and
the owner is too far away. To be used with do_custom. Note: Optimized for mobs smaller than 1×1×1.
Larger mobs might have space problems after teleportation.
* dist: Minimum required distance from owner to teleport. Default: 12
* teleport_check_interval: Optional. Interval in seconds to check the mob teleportation. Default: 4 ]]
mobs_mc.make_owner_teleport_function = function(dist, teleport_check_interval)
return function(self, dtime)
-- No teleportation if no owner or if sitting
if not self.owner or self.order == "sit" then
return
end
if not teleport_check_interval then
teleport_check_interval = 4
end
if not dist then
dist = 12
end
if self._teleport_timer == nil then
self._teleport_timer = teleport_check_interval
return
end
self._teleport_timer = self._teleport_timer - dtime
if self._teleport_timer <= 0 then
self._teleport_timer = teleport_check_interval
local mob_pos = self.object:get_pos()
local owner = minetest.get_player_by_name(self.owner)
if not owner then
-- No owner found, no teleportation
return
end
local owner_pos = owner:get_pos()
local dist_from_owner = vector.distance(owner_pos, mob_pos)
if dist_from_owner > dist then
-- Check for nodes below air in a 5×1×5 area around the owner position
local check_offsets = table.copy(offsets)
-- Attempt to place mob near player. Must be placed on walkable node below a non-walkable one. Place inside that air node.
while #check_offsets > 0 do
local r = pr:next(1, #check_offsets)
local telepos = vector.add(owner_pos, check_offsets[r])
local telepos_below = {x=telepos.x, y=telepos.y-1, z=telepos.z}
table.remove(check_offsets, r)
-- Long story short, spawn on a platform
local trynode = minetest.registered_nodes[minetest.get_node(telepos).name]
local trybelownode = minetest.registered_nodes[minetest.get_node(telepos_below).name]
if trynode and not trynode.walkable and
trybelownode and trybelownode.walkable then
-- Correct position found! Let's teleport.
self.object:set_pos(telepos)
return
end
end
end
end
end
end
mobs_mc.shears_wear = 276
mobs_mc.water_level = tonumber(minetest.settings:get("water_level")) or 0

@ -92,7 +92,6 @@ mcl_mobs.register_mob("mobs_mc:iron_golem", {
run_start = 40, run_end = 80, run_speed = 25,
punch_start = 80, punch_end = 90, punch_speed = 5,
},
jump = false,
do_custom = function(self, dtime)
self:crack_overlay()
self.home_timer = (self.home_timer or 0) + dtime

@ -30,6 +30,7 @@ local ocelot = {
hp_max = 10,
xp_min = 1,
xp_max = 3,
visual_size = { x = 1.75, y = 1.75, },
head_swivel = "head.control",
bone_eye_height = 6.2,
head_eye_height = 0.35,
@ -111,9 +112,9 @@ table.update(cat,{
owner_loyal = true,
tamed = false,
runaway = false,
visual_size = { x = 0.8, y = 0.8 },
-- Automatically teleport cat to owner
do_custom = mobs_mc.make_owner_teleport_function(12),
visual_size = { x = 1.75 * 0.8, y = 1.75 * 0.8 },
chase_owner_distance = 10.0,
stop_chasing_distance = 2.0,
sounds = {
random = "mobs_mc_cat_idle",
damage = "mobs_mc_cat_hiss",

@ -4,6 +4,7 @@
--License for code WTFPL and otherwise stated in readmes
local S = minetest.get_translator("mobs_mc")
local mob_class = mcl_mobs.mob_class
--###################
--################### PARROT
@ -89,40 +90,7 @@ local function perch(self,player)
end
end
local function check_perch(self,dtime)
if self.object:get_attach() then
for p in mcl_util.connected_players() do
for _,o in pairs(p:get_children()) do
local l = o:get_luaentity()
if l and l.name == "mobs_mc:parrot" then
local n1 = minetest.get_node(vector.offset(p:get_pos(),0,-0.6,0)).name
local n2 = minetest.get_node(vector.offset(p:get_pos(),0,0,0)).name
if ( n1 == "air" or minetest.get_item_group(n2,"water") > 0 or minetest.get_item_group(n2,"lava") > 0) and
not minetest.is_creative_enabled(p:get_player_name()) then
o:set_detach()
self.detach_timer = 0
return
end
end
end
end
elseif not self.detach_timer then
for p in mcl_util.connected_players() do
if vector.distance(self.object:get_pos(),p:get_pos()) < 0.5 then
perch(self,p)
return
end
end
elseif self.detach_timer then
if self.detach_timer > 1 then
self.detach_timer = nil
else
self.detach_timer = self.detach_timer + dtime
end
end
end
mcl_mobs.register_mob("mobs_mc:parrot", {
local parrot_def = {
description = S("Parrot"),
type = "animal",
spawn_class = "passive",
@ -160,11 +128,7 @@ mcl_mobs.register_mob("mobs_mc:parrot", {
stand_start = 0, stand_end = 0, stand_speed = 50,
fly_start = 130, fly_end = 150, fly_speed = 50,
walk_start = 20, walk_end = 40, walk_speed = 50,
-- TODO: actual walk animation
--walk_start = 0,
--walk_end = 20,
-- TODO: more unused animations between 45 and 130
sit_start = 160, sit_end = 160,
},
fall_damage = 0,
attack_type = "melee",
@ -172,11 +136,13 @@ mcl_mobs.register_mob("mobs_mc:parrot", {
floats = 1,
physical = true,
movement_speed = 4.0,
fly = true,
fly_chance = 50,
airborne = true,
makes_footstep_sound = false,
fear_height = 0,
view_range = 16,
chase_owner_distance = 5.0,
stop_chasing_distance = 1.0,
pace_height = 7,
pace_width = 8,
on_rightclick = function(self, clicker)
if self._doomed then return end
local item = clicker:get_wielded_item()
@ -184,8 +150,8 @@ mcl_mobs.register_mob("mobs_mc:parrot", {
if item:get_name() == "mcl_farming:cookie" then
minetest.sound_play("mobs_mc_animal_eat_generic", {object = self.object, max_hear_distance=16}, true)
self.health = 0
-- Doomed to die
self._doomed = true
mcl_potions.give_effect_by_level ("poison", self.object, 900, 10)
if not minetest.is_creative_enabled(clicker:get_player_name()) then
item:take_item()
clicker:set_wielded_item(item)
@ -199,19 +165,94 @@ mcl_mobs.register_mob("mobs_mc:parrot", {
"mcl_farming:pumpkin_seeds",
"mcl_farming:beetroot_seeds",
}
if table.indexof(food, item:get_name()) ~= -1 and self:feed_tame(clicker, 4, false, true) then return end
perch(self,clicker)
if table.indexof (food, item:get_name()) ~= -1 then
self:feed_tame (clicker, 4, false, true, false, 0.1)
return
end
if self.order == "sit" then
self.order = ""
else
self:stay ()
end
end,
on_step = function (self, dtime, moveresult)
mob_class.on_step (self, dtime, moveresult)
end,
do_custom = function(self,dtime)
check_perch(self,dtime)
check_mobimitate(self,dtime)
check_mobimitate (self,dtime)
-- Lest sit_if_ordered should interrupt perching.
if self.object:get_attach () and not self.perching then
self.object:detach ()
end
end,
do_punch = function(self,puncher) --do_punch is the mcl_mobs_redo variant - it gets called by on_punch later....
if self.object:get_attach() == puncher then
return false --return false explicitly here. mcl_mobs checks for that
end
end,
})
}
function parrot_def:check_perch (self_pos, dtime)
local attach = self.object:get_attach ()
if self.perch_cooldown then
self.perch_cooldown
= math.max (0, self.perch_cooldown - dtime)
else
self.perch_cooldown = 0
end
if attach then
if not self.perching then
-- Perching was interrupted, and therefore
-- this object must be detached.
self.object:detach ()
return false
end
local n1 = minetest.get_node (vector.offset (self_pos, 0, -0.6, 0)).name
local n2 = minetest.get_node (vector.offset (self_pos, 0, 0, 0)).name
if n1 == "air" or minetest.get_item_group (n2,"water") > 0
or minetest.get_item_group (n2,"lava") > 0 then
self.object:set_detach()
self.perching = false
self.perch_cooldown = 1.0
return false
end
return true
elseif self.owner and self.perch_cooldown == 0 then
local owner = minetest.get_player_by_name (self.owner)
if not owner then
return false
end
if vector.distance (self_pos, owner:get_pos ()) < 0.5 then
perch (self, owner)
self.perching = true
return "perching"
end
end
return false
end
function parrot_def:airborne_pacing_target (pos, width, height, groups)
if math.random (100) <= 99 then
local aa = vector.offset (pos, -3, -6, -3)
local bb = vector.offset (pos, 3, 6, 3)
local nodes
= minetest.find_nodes_in_area_under_air (aa, bb, {"group:leaves"})
if #nodes > 0 then
return vector.offset (nodes[math.random (#nodes)], 0, 1, 0)
end
end
return mob_class.airborne_pacing_target (self, pos, width, height, groups)
end
parrot_def.ai_functions = {
mob_class.sit_if_ordered,
mob_class.check_travel_to_owner,
parrot_def.check_perch,
mob_class.check_frightened,
mob_class.check_pace,
}
mcl_mobs.register_mob("mobs_mc:parrot", parrot_def)
mcl_mobs.spawn_setup({
name = "mobs_mc:parrot",

@ -71,7 +71,6 @@ local piglin = {
death = "mobs_mc_zombiepig_death",
distance = 16,
},
jump = true,
makes_footstep_sound = true,
movement_speed = 4.6,
drops = {
@ -245,7 +244,6 @@ mcl_mobs.register_mob("mobs_mc:zombified_piglin",table.merge(piglin,{
head_eye_height = 1.79,
curiosity = 15,
collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3},
jump = true,
makes_footstep_sound = true,
pathfinding = 1,
lava_damage = 0,

@ -4,6 +4,7 @@
--License for code WTFPL and otherwise stated in readmes
local S = minetest.get_translator(minetest.get_current_modname())
local mob_class = mcl_mobs.mob_class
--###################
--################### salmon
@ -23,7 +24,7 @@ local salmon = {
spawn_in_group = 5,
tilt_swim = true,
head_eye_height = 0.26,
collisionbox = {-0.4, 0.0, -0.4, 0.4, 0.79, 0.4},
collisionbox = {-0.35, 0.0, -0.35, 0.35, 0.4, 0.35},
visual = "mesh",
mesh = "extra_mobs_salmon.b3d",
textures = {
@ -46,13 +47,25 @@ local salmon = {
min = 1,
max = 1,},
},
runaway_from = {"players"},
runaway_bonus_near = 1.6,
runaway_bonus_far = 1.4,
runaway_view_range = 8,
visual_size = {x=3, y=3},
makes_footstep_sound = false,
swims = true,
pace_height = 1.0,
do_go_pos = mcl_mobs.mob_class.fish_do_go_pos,
do_go_pos = mob_class.fish_do_go_pos,
initialize_group = mob_class.school_init_group,
_school_size = 5,
ai_functions = {
mob_class.check_schooling,
mob_class.check_avoid,
mob_class.check_frightened,
mob_class.check_pace,
},
breathes_in_water = true,
jump = false,
flops = true,
view_range = 16,
runaway = true,
fear_height = 4,

@ -68,7 +68,6 @@ mcl_mobs.register_mob("mobs_mc:shulker", {
-- TODO: sounds
visual_size = {x=3, y=3},
knock_back = false,
jump = false,
can_despawn = false,
fall_speed = 0,
does_not_prevent_sleep = true,

@ -41,7 +41,6 @@ mcl_mobs.register_mob("mobs_mc:silverfish", {
},
makes_footstep_sound = false,
movement_speed = 5.0,
jump = true,
fear_height = 4,
replace_what = {
{"mcl_core:stone", "mcl_monster_eggs:monster_egg_stone", -1},

@ -101,7 +101,7 @@ local function slime_do_go_pos (self, dtime, moveresult)
or not (moveresult.touching_ground
or moveresult.standing_on_object) then
if delay == 0 then
self.order = "jump"
self._jump = true
delay = (math.random (60) + 40) / 20 * self.jump_delay_multiplier
if self.attack then
delay = delay / 3
@ -172,6 +172,10 @@ end
local function slime_run_ai (self, dtime)
local self_pos = self.object:get_pos ()
if self.dead then
return
end
self:check_attack (self_pos, dtime)
slime_turn (self, dtime, self_pos)
slime_jump_continuously (self)
@ -183,17 +187,6 @@ local function slime_check_particle (self, dtime, moveresult)
and moveresult.touching_ground
and self._get_slime_particle then
local cbox = self.collisionbox
-- minetest.add_particlespawner ({
-- amount = 40,
-- time = 0.25,
-- attached = self.object,
-- texture = self._slime_particle,
-- collisiondetection = true,
-- pos = {
-- min = vector.new (cbox[1] - 0.4, 0, cbox[3] - 0.4),
-- max = vector.new (cbox[4] + 0.4, 0.2, cbox[6] + 0.4),
-- },
-- })
local radius = (cbox[6] - cbox[3])
local self_pos = self.object:get_pos ()
for i = 1, math.round (radius * 32) do
@ -287,7 +280,6 @@ local slime_big = {
fall_damage = 0,
view_range = 16,
passive = false,
jump = true,
movement_speed = 10, -- (0.2 + 0.1 * size) * 20
fear_height = 0,
spawn_small_alternative = "mobs_mc:slime_small",
@ -297,6 +289,7 @@ local slime_big = {
specific_attack = {
"mobs_mc:iron_golem",
},
attack_type = "null",
_get_slime_particle = function ()
return "[combine:" .. math.random (3)
.. "x" .. math.random (3) .. ":-"
@ -485,7 +478,6 @@ local magma_cube_big = {
view_range = 16,
jump_height = 14.4,
passive = false,
jump = true,
fear_height = 0,
spawn_small_alternative = "mobs_mc:magma_cube_small",
on_die = spawn_children_on_die("mobs_mc:magma_cube_small", 0.8, 1.5),
@ -496,6 +488,7 @@ local magma_cube_big = {
_get_slime_particle = function ()
return "mcl_particles_fire_flame.png"
end,
attack_type = "null",
_slime_particle_glow = 14,
}
mcl_mobs.register_mob("mobs_mc:magma_cube_big", magma_cube_big)

@ -57,7 +57,6 @@ mcl_mobs.register_mob("mobs_mc:snowman", {
drops = {{ name = "mcl_throwing:snowball", chance = 1, min = 0, max = 15 }},
visual_size = {x=3, y=3},
movement_speed = 4.0,
jump = true,
makes_footstep_sound = true,
attack_type = "ranged",
arrow = "mcl_throwing:snowball_entity",

@ -86,7 +86,6 @@ local spider = {
distance = 16,
},
movement_speed = 6.0,
jump = true,
jump_height = 4,
always_climb = true,
fear_height = 0,

@ -34,7 +34,6 @@ local strider = {
eat = "mobs_mc_animal_eat_generic",
distance = 16,
},
jump = true,
makes_footstep_sound = true,
movement_speed = 3.5,
runaway = true,

@ -1,5 +1,6 @@
--Tropical Fish by cora
local S = minetest.get_translator(minetest.get_current_modname())
local mob_class = mcl_mobs.mob_class
local base_colors = {
"#FF3855",
@ -92,13 +93,24 @@ local tropical_fish = {
min = 1,
max = 1,},
},
runaway_from = {"players"},
runaway_bonus_near = 1.6,
runaway_bonus_far = 1.4,
runaway_view_range = 8,
initialize_group = mob_class.school_init_group,
ai_functions = {
mob_class.check_schooling,
mob_class.check_avoid,
mob_class.check_frightened,
mob_class.check_pace,
},
visual_size = {x=3, y=3},
makes_footstep_sound = false,
swims = true,
pace_height = 1.0,
do_go_pos = mcl_mobs.mob_class.fish_do_go_pos,
breathes_in_water = true,
jump = false,
flops = true,
view_range = 16,
runaway = true,
fear_height = 4,

@ -115,9 +115,7 @@ function mobs_mc.villager_mob:stand_near_players()
-- Check infrequently to keep CPU load low
if self.order ~= "sleep" and self:check_timer("player_scan", PLAYER_SCAN_INTERVAL) then
if table.count(minetest.get_objects_inside_radius(self.object:get_pos(), PLAYER_SCAN_RADIUS), function(_, pl) return pl:is_player() end) > 0 then
self:stand_still()
else
self.jump = true
self:stand_still ()
end
end
end

@ -24,7 +24,6 @@ local GATHERING = "gathering"
local RUNAWAY = "runaway"
function mobs_mc.villager_mob:stand_still()
self.jump = false
self:halt_in_tracks (true)
-- self:set_state("stand")
end

@ -50,7 +50,7 @@ local function get_dim_relative_y(pos)
end
end
mcl_mobs.register_mob("mobs_mc:wither", {
local wither_def = {
description = S("Wither"),
type = "monster",
spawn_class = "hostile",
@ -78,7 +78,6 @@ mcl_mobs.register_mob("mobs_mc:wither", {
-- TODO: sounds
distance = 60,
},
jump = true,
jump_height = 10,
fly = true,
makes_footstep_sound = false,
@ -100,9 +99,9 @@ mcl_mobs.register_mob("mobs_mc:wither", {
shoot_offset = -0.5,
animation = {
walk_speed = 12, run_speed = 12, stand_speed = 12,
stand_start = 0, stand_end = 20,
walk_start = 0, walk_end = 20,
run_start = 0, run_end = 20,
stand_start = 0, stand_end = 20,
walk_start = 0, walk_end = 20,
run_start = 0, run_end = 20,
},
harmed_by_heal = true,
is_boss = true,
@ -113,272 +112,6 @@ mcl_mobs.register_mob("mobs_mc:wither", {
if not ent or not ent.is_mob or ent.harmed_by_heal or ent.name == "mobs_mc:ghast" then return true
else return false end
end,
do_custom = function(self, dtime)
if self._spawning then
if not self._spw_max then self._spw_max = self._spawning end
self._spawning = self._spawning - dtime
local bardef = {
color = "dark_purple",
text = "Wither spawning",
percentage = math.floor((self._spw_max - self._spawning) / self._spw_max * 100),
}
local pos = self.object:get_pos()
for player in mcl_util.connected_players() do
local d = vector.distance(pos, player:get_pos())
if d <= 80 then
mcl_bossbars.add_bar(player, bardef, true, d)
end
end
self.object:set_yaw(self._spawning*10)
local factor = math.floor((math.sin(self._spawning*10)+1.5) * 85)
local str = minetest.colorspec_to_colorstring({r=factor, g=factor, b=factor})
self.object:set_texture_mod("^[brighten^[multiply:"..str)
if self._spawning <= 0 then
if mobs_griefing and not minetest.is_protected(pos, "") then
mcl_explosions.explode(pos, WITHER_INIT_BOOM, { drop_chance = 1.0 }, self.object)
else
mcl_mobs.mob_class.safe_boom(self, pos, WITHER_INIT_BOOM)
end
self.object:set_texture_mod("")
self._spawning = nil
self._spw_max = nil
else
return false
end
end
self._custom_timer = self._custom_timer + dtime
if self._custom_timer > 1 then
self.health = math.min(self.health + 1, self.object:get_properties().hp_max)
self._custom_timer = self._custom_timer - 1
end
if self._spawner then
local spawner = minetest.get_player_by_name(self._spawner)
if spawner then
self._death_timer = 0
local pos = self.object:get_pos()
local spw = spawner:get_pos()
local dist = vector.distance(pos, spw)
if dist > 60 and follow_spawner then -- teleport to the player who spawned the wither
local R = 10
pos.x = spw.x + math.random(-R, R)
pos.y = spw.y + math.random(-R, R)
pos.z = spw.z + math.random(-R, R)
self.object:set_pos(pos)
end
else
self._death_timer = self._death_timer + self.health - self._health_old
if self.health == self._health_old then self._death_timer = self._death_timer + dtime end
if self._death_timer > 100 then
self.object:remove()
return false
end
self._health_old = self.health
end
end
local rand_factor
if self.health < (self.object:get_properties().hp_max / 2) then
self.base_texture = "mobs_mc_wither_half_health.png"
self.fly = false
self._arrow_resistant = true
rand_factor = 3
else
self.base_texture = "mobs_mc_wither.png"
self.fly = true
self._arrow_resistant = false
rand_factor = 10
end
if not self.attack then
local y = get_dim_relative_y(self.object:get_pos())
if y > 0 then
self.fly = false
else
self.fly = true
local vel = self.object:get_velocity()
-- self.object:set_velocity(vector.new(vel.x, self.walk_velocity, vel.z))
-- TODO
end
end
self.object:set_properties({textures={self.base_texture}})
mcl_bossbars.update_boss(self.object, "Wither", "dark_purple")
if math.random(1, rand_factor) < 2 then
self.arrow = "mobs_mc:wither_skull_strong"
else
self.arrow = "mobs_mc:wither_skull"
end
end,
attack_state = function(self, dtime)
local s = self.object:get_pos()
local p = self.attack:get_pos() or s
p.y = p.y - .5
s.y = s.y + .5
local dist = vector.distance(p, s)
local vec = {
x = p.x - s.x,
y = p.y - s.y,
z = p.z - s.z
}
local yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
if p.x > s.x then yaw = yaw +math.pi end
yaw = self:set_yaw( yaw, 0, dtime)
local stay_away_from_player = vector.zero()
--strafe back and fourth
--stay away from player so as to shoot them
if dist < self.avoid_distance and self.shooter_avoid_enemy then
self:set_animation( "shoot")
stay_away_from_player=vector.multiply(vector.direction(p, s), 0.33)
end
if self.fly then
local vel = self.object:get_velocity()
local diff = s.y - p.y
local FLY_FACTOR = 0 -- self.walk_velocity
if diff < 10 then
self.object:set_velocity({x=vel.x, y= FLY_FACTOR, z=vel.z})
elseif diff > 15 then
self.object:set_velocity({x=vel.x, y=-FLY_FACTOR, z=vel.z})
end
for i=1, 15 do
if minetest.get_node(vector.offset(s, 0, -i, 0)).name ~= "air" then
self.object:set_velocity({x=vel.x, y= FLY_FACTOR, z=vel.z})
break
elseif minetest.get_node(vector.offset(s, 0, i, 0)).name ~= "air" then
self.object:set_velocity({x=vel.x, y=-FLY_FACTOR/i, z=vel.z})
break
end
end
end
if self.strafes then
if not self.strafe_direction then
self.strafe_direction = 1.57
end
if math.random(40) == 1 then
self.strafe_direction = self.strafe_direction*-1
end
local dir = vector.rotate_around_axis(vector.direction(s, p), vector.new(0,1,0), self.strafe_direction)
local dir2 = vector.multiply(dir, 0.3 -- * self.walk_velocity TODO
)
if dir2 and stay_away_from_player then
self.acc = vector.add(dir2, stay_away_from_player)
end
else
self:set_velocity(0)
end
if dist > 30 then self.acc = vector.add(self.acc, vector.direction(s, p)*0.01) end
local side_cor = vector.new(0.7*math.cos(yaw), 0, 0.7*math.sin(yaw))
local m = self.object:get_pos() -- position of the middle head
local sr = self.object:get_pos() + side_cor -- position of side right head
local sl = self.object:get_pos() - side_cor -- position of side left head
-- height corrections
local cb = self.object:get_properties().collisionbox
m.y = m.y + cb[5]
sr.y = sr.y + cb[5] - 0.3
sl.y = sl.y + cb[5] - 0.3
local rand_pos = math.random(1,3)
if rand_pos == 1 then m = sr
elseif rand_pos == 2 then m = sl end
-- melee attack
if not self._melee_timer then
self._melee_timer = 0
end
if self._melee_timer < WITHER_MELEE_COOLDOWN then
self._melee_timer = self._melee_timer + dtime
else
self._melee_timer = 0
local pos = table.copy(s)
pos.y = pos.y + 2
local hit_some = false
for obj in minetest.objects_inside_radius(pos, self.reach) do
obj:punch(obj, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = 4},
}, pos)
local ent = obj:get_luaentity()
if obj:is_player() or (ent and ent ~= self and (not ent._shooter or ent._shooter ~= self)) then
mcl_util.deal_damage(obj, 8, {type = "magic"})
hit_some = true
end
mcl_potions.give_effect("withering", obj, 2, 10)
end
if hit_some then
mcl_mobs.effect(pos, 32, "mcl_particles_soul_fire_flame.png", 5, 10, self.reach, 1, 0)
end
end
if dist < self.reach then
self.shoot_interval = 3
else
self.shoot_interval = 1
end
if self.shoot_interval
and self.timer > self.shoot_interval
and not minetest.raycast(vector.add(m, vector.new(0,self.shoot_offset,0)), vector.add(self.attack:get_pos(), vector.new(0,1.5,0)), false, false):next()
and math.random(1, 100) <= 60 then
self.timer = 0
self:set_animation( "shoot")
-- play shoot attack sound
self:mob_sound("shoot_attack")
-- Shoot arrow
if minetest.registered_entities[self.arrow] then
local arrow, ent
local v = 1
if not self.shoot_arrow then
self.firing = true
minetest.after(1, function()
self.firing = false
end)
arrow = minetest.add_entity(m, self.arrow)
ent = arrow:get_luaentity()
if ent.velocity then
v = ent.velocity
end
ent.switch = 1
ent.owner_id = tostring(self.object) -- add unique owner id to arrow
-- important for mcl_shields
ent._shooter = self.object
ent._saved_shooter_pos = self.object:get_pos()
end
local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
-- 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)
if self.shoot_arrow then
vec = vector.normalize(vec)
self:shoot_arrow(m, vec)
else
arrow:set_velocity(vec)
end
end
end
end,
do_punch = function(self, hitter, tflp, tool_capabilities, dir) ---@diagnostic disable-line: unused-local
if self._spawning or hitter == self.object then return false end
local ent = hitter:get_luaentity()
@ -391,17 +124,92 @@ mcl_mobs.register_mob("mobs_mc:wither", {
wither_unstuck(self)
self.health = self.health - damage
end,
on_spawn = function(self)
minetest.sound_play("mobs_mc_wither_spawn", {object=self.object, gain=1.0, max_hear_distance=64})
minetest.sound_play("mobs_mc_wither_spawn", {gain=1.0})
self._custom_timer = 0.0
self._death_timer = 0.0
self._health_old = self.object:get_properties().hp_max
self._spawning = 10
return true
end,
}
})
function wither_def:do_custom (self, dtime, moveresult)
if self._spawning then
if not self._spw_max then self._spw_max = self._spawning end
self._spawning = self._spawning - dtime
local bardef = {
color = "dark_purple",
text = "Wither spawning",
percentage = math.floor((self._spw_max - self._spawning) / self._spw_max * 100),
}
local pos = self.object:get_pos()
for player in mcl_util.connected_players() do
local d = vector.distance(pos, player:get_pos())
if d <= 80 then
mcl_bossbars.add_bar(player, bardef, true, d)
end
end
self.object:set_yaw(self._spawning*10)
local factor = math.floor((math.sin(self._spawning*10)+1.5) * 85)
local str = minetest.colorspec_to_colorstring({r=factor, g=factor, b=factor})
self.object:set_texture_mod("^[brighten^[multiply:"..str)
if self._spawning <= 0 then
if mobs_griefing and not minetest.is_protected(pos, "") then
mcl_explosions.explode(pos, WITHER_INIT_BOOM, { drop_chance = 1.0 }, self.object)
else
mcl_mobs.mob_class.safe_boom(self, pos, WITHER_INIT_BOOM)
end
self.object:set_texture_mod("")
self._spawning = nil
self._spw_max = nil
else
return false
end
end
self._custom_timer = self._custom_timer + dtime
if self._custom_timer > 1 then
self.health = math.min(self.health + 1, self.object:get_properties().hp_max)
self._custom_timer = self._custom_timer - 1
end
local rand_factor
if self.health < (self.object:get_properties().hp_max / 2) then
self.base_texture = "mobs_mc_wither_half_health.png"
self.fly = false
self._arrow_resistant = true
rand_factor = 3
else
self.base_texture = "mobs_mc_wither.png"
self.fly = true
self._arrow_resistant = false
rand_factor = 10
end
if not self.attack then
local y = get_dim_relative_y(self.object:get_pos())
if y > 0 then
self.fly = false
else
self.fly = true
local vel = self.object:get_velocity()
-- self.object:set_velocity(vector.new(vel.x, self.walk_velocity, vel.z))
-- TODO
end
end
self.object:set_properties({textures={self.base_texture}})
mcl_bossbars.update_boss(self.object, "Wither", "dark_purple")
if math.random(1, rand_factor) < 2 then
self.arrow = "mobs_mc:wither_skull_strong"
else
self.arrow = "mobs_mc:wither_skull"
end
end
mcl_mobs.register_mob("mobs_mc:wither", wither_def)
local wither_rose_soil = { "group:grass_block", "mcl_core:dirt", "mcl_core:coarse_dirt", "mcl_nether:netherrack", "group:soul_block", "mcl_mud:mud", "mcl_lush_caves:moss" }
local function spawn_wither_rose(obj)

@ -223,8 +223,6 @@ dog.owner = ""
dog.order = "sit"
dog.state = "stand"
dog.owner_loyal = true
-- Automatically teleport dog to owner
dog.do_custom = mobs_mc.make_owner_teleport_function(12)
dog.attack_animals = nil
dog.specific_attack = nil
dog.after_activate = function(self)

@ -87,7 +87,6 @@ local zombie = {
reach = 2,
fear_height = 4,
pathfinding = 1,
jump = true,
jump_height = 8.4,
group_attack = { "mobs_mc:zombie", "mobs_mc:baby_zombie", "mobs_mc:husk", "mobs_mc:baby_husk" },
drops = drops_zombie,

@ -461,7 +461,8 @@ local function register_chest(basename, desc, longdesc, usagehelp, tt_help, tile
material_wood = 1,
flammable = -1,
chest_entity = 1,
not_in_creative_inventory = 1
not_in_creative_inventory = 1,
_mcl_partial = 2,
},
is_ground_content = false,
sounds = mcl_sounds.node_sound_wood_defaults(),

@ -9,6 +9,7 @@ else disabled_structures = {} end
local peaceful = minetest.settings:get_bool("only_peaceful_mobs", false)
local mob_cap_player = tonumber(minetest.settings:get("mcl_mob_cap_player")) or 75
local mob_cap_animal = tonumber(minetest.settings:get("mcl_mob_cap_animal")) or 10
local mobs_spawn = minetest.settings:get_bool("mobs_spawn", true) ~= false
local logging = minetest.settings:get_bool("mcl_logging_structures",true)
mcl_structures.DBG = false
@ -294,6 +295,9 @@ function mcl_structures.register_structure_spawn(def)
local limit = def.limit or 7
if active_object_count_wider > limit + mob_cap_animal then return end
if active_object_count_wider > mob_cap_player then return end
if not mobs_spawn then
return
end
local p = vector.offset(pos,0,1,0)
if not def.underwater and minetest.get_node(p).name ~= "air" then return end
if minetest.get_meta(pos):get_string("spawnblock") == "" then return end

@ -0,0 +1,87 @@
local gwp_airborne_directions = {
-- North (1).
{ 0, 0, 1, },
-- West (2).
{-1, 0, 0, },
-- South (3).
{ 0, 0, -1, },
-- East (4).
{ 1, 0, 0, },
-- Bottom (5).
{ 0, -1, 0, },
-- Top (6).
{ 0, 1, 0, },
-- North (7).
{ 0, 1, 1, 6, 1, },
-- West (8).
{-1, 1, 0, 6, 2, },
-- South (9).
{ 0, 1, -1, 6, 3, },
-- East (10).
{ 1, 1, 0, 6, 4, },
-- North (11).
{ 0, -1, 1, 5, 1, },
-- West (12).
{-1, -1, 0, 5, 2, },
-- South (13).
{ 0, -1, -1, 5, 3, },
-- East (14).
{ 1, -1, 0, 5, 4, },
-- Northwest (15).
{ -1, 0, 1, 1, 2, },
-- Northeast (16).
{ 1, 0, -1, 1, 4, },
-- Southwest (17).
{ -1, 0, -1, 3, 2, },
-- Southeast (18),
{ 1, 0, -1, 3, 4, },
-- Northwest (19).
{ -1, 1, 1, 15, 1, 2, 6, 7, 8, },
-- Northeast (20).
{ 1, 1, -1, 16, 1, 4, 6, 7, 10, },
-- Southwest (21).
{ -1, 1, -1, 17, 3, 2, 6, 9, 8, },
-- Southeast (22),
{ 1, 1, -1, 18, 3, 4, 6, 9, 10, },
-- Northwest (23).
{ -1, -1, 1, 15, 1, 2, 5, 11, 12, },
-- Northeast (24).
{ 1, -1, -1, 16, 1, 4, 5, 11, 14, },
-- Southwest (25).
{ -1, -1, -1, 17, 3, 2, 5, 13, 12, },
-- Southeast (26),
{ 1, -1, -1, 18, 3, 4, 5, 13, 14, },
}
local airborne_gwp_edges = "local function airborne_gwp_edges (self, context, node)\
\tlocal results = airborne_gwp_edges_buffer\
\tlocal n = 0\
\tlocal v = airborne_gwp_edges_scratch\n\n"
for i, direction in ipairs (gwp_airborne_directions) do
airborne_gwp_edges = airborne_gwp_edges
.. string.format ("\tv.x = node.x + %d\n\tv.y = node.y + %d\n\tv.z = node.z + %d\n",
direction[1], direction[2], direction[3])
.. string.format ("\tlocal e%d = airborne_gwp_edges_1 (self, context, v)\n",
i)
airborne_gwp_edges = airborne_gwp_edges
.. string.format ("\tif e%d", i)
for i = 4, #direction do
airborne_gwp_edges = airborne_gwp_edges
.. "\n\t\tand "
.. string.format ("e%d", direction[i])
end
airborne_gwp_edges = airborne_gwp_edges
.. " then\n"
.. string.format ("\t\tn = n + 1; results[n] = e%d\n", i)
.. "\tend\n"
end
airborne_gwp_edges = airborne_gwp_edges .. "\tresults[n + 1] = nil\n\treturn results\nend\n"
print (airborne_gwp_edges)