WIP -- squash later
This commit is contained in:
parent
e7745c2659
commit
ce6f198155
mods
ENTITIES
mcl_mobs
mobs_mc
bat.luacod.luacreeper.luadolphin.luaghast.luaguardian.luaguardian_elder.luahoglin+zoglin.luainit.luairon_golem.lua
models
ocelot.luaparrot.luapiglin.luasalmon.luashulker.luasilverfish.luaslime+magma_cube.luasnowman.luaspider.luastrider.luatropical_fish.luavillager.luavillagers
wither.luawolf.luazombie.luaITEMS/mcl_chests
MAPGEN/mcl_structures
tools
@ -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
|
||||
|
Binary file not shown.
@ -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
|
||||
|
87
tools/generate_airborne_gwp_edges.lua
Normal file
87
tools/generate_airborne_gwp_edges.lua
Normal file
@ -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)
|
Loading…
x
Reference in New Issue
Block a user