WIP -- squash later

This commit is contained in:
Po Lu 2024-10-03 17:03:05 +08:00 committed by cora
parent 40967866bb
commit 6f34a133eb
No known key found for this signature in database
14 changed files with 599 additions and 98 deletions

View File

@ -417,7 +417,7 @@ function mob_class:on_step(dtime, moveresult)
end
if self.do_custom then
if self.do_custom(self, dtime) == false then
if self.do_custom(self, dtime, moveresult) == false then
return
end
end

View File

@ -136,6 +136,13 @@ mcl_mobs.mob_class = {
wears_armor = false,
steer_class = "controls",
steer_item = nil,
swim_max_pitch = 85 * math.pi / 180,
max_yaw_movement = 10 * math.pi / 180,
swim_speed_factor = 0.02,
idle_gravity_in_liquids = false,
grounded_speed_factor = 0.10,
pace_interval = 5,
pace_height = 7,
_mcl_fishing_hookable = true,
_mcl_fishing_reelable = true,

View File

@ -1,51 +1,8 @@
local mob_class = mcl_mobs.mob_class
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
-- Returns true is node can deal damage to self
function mob_class:is_node_dangerous(nodename)
local nn = nodename
if self.lava_damage > 0 then
if minetest.get_item_group(nn, "lava") ~= 0 then
return true
end
end
if self.fire_damage > 0 then
if minetest.get_item_group(nn, "fire") ~= 0 then
return true
end
end
if minetest.registered_nodes[nn] and minetest.registered_nodes[nn].damage_per_second and minetest.registered_nodes[nn].damage_per_second > 0 then
return true
end
return false
end
-- Returns true if node is a water hazard to this mob
function mob_class:is_node_waterhazard(nodename)
if self.swims or self.breathes_in_water or self.object:get_properties().breath_max == -1 then
return false
end
if self.water_damage > 0 then
if minetest.get_item_group(nodename, "water") ~= 0 then
return true
end
end
if
minetest.registered_nodes[nodename]
and minetest.registered_nodes[nodename].drowning
and minetest.registered_nodes[nodename].drowning > 0
and minetest.get_item_group(nodename, "water") ~= 0
then
return true
end
return false
end
function mob_class:target_visible(origin, target)
-- This cache is flushed on each call to on_step.
-- This cache is flushed on each call to on_step.
if self._targets_visible[target] then
return true
end
@ -106,7 +63,7 @@ local function minnum (a, b)
return math.min (a, b)
end
local function aabb_clear (node, origin, pos2, direction, d)
local function aabb_clear (node, origin, pos2, direction, d, typetest)
local node_type = minetest.get_node (node)
if node_type.name == "air" then
return true
@ -114,6 +71,8 @@ local function aabb_clear (node, origin, pos2, direction, d)
local def = minetest.registered_nodes[node_type.name]
if def and not def.walkable then
return true
elseif typetest and typetest (node_type.name, def) then
return true
elseif not def then
return false
end
@ -187,7 +146,7 @@ local function scale_poses (pos1, pos2)
return v1, v2
end
function mob_class:line_of_sight (pos1, pos2)
function mob_class:line_of_sight (pos1, pos2, typetest)
-- Move pos1 and pos2 by minuscule values to avoid generating
-- Inf or NaN.
pos1, pos2 = scale_poses (pos1, pos2)
@ -234,7 +193,7 @@ function mob_class:line_of_sight (pos1, pos2)
v.x = x
v.y = y
v.z = z
if not aabb_clear (v, pos1, pos2, direction, distance) then
if not aabb_clear (v, pos1, pos2, direction, distance, typetest) then
return false
end
@ -489,6 +448,90 @@ function mob_class:do_go_pos (dtime, moveresult)
end
end
local function norm_radians (x)
local x = x % (math.pi * 2)
if x >= math.pi then
x = x - math.pi * 2
end
if x < -math.pi then
x = x + math.pi * 2
end
return x
end
local function clip_rotation (from, to, limit)
local difference = norm_radians (to - from)
if difference > limit then
difference = limit
end
if difference < -limit then
difference = -limit
end
return from + difference
end
function mob_class:dolphin_do_go_pos (dtime, moveresult)
local target = self.movement_target
local pos = self.object:get_pos ()
local dist = vector.distance (pos, target)
if dist < 0.5 then
return
end
local dx, dy, dz = target.x - pos.x,
target.y - pos.y,
target.z - pos.z
local dir = math.atan2 (dz, dx) - math.pi / 2 - self.rotate
local standin = minetest.registered_nodes[self.standing_in]
local yaw = self.object:get_yaw ()
local f = dtime / 0.05
local target_yaw = clip_rotation (yaw, dir, self.max_yaw_movement * f)
-- Orient the mob vertically.
local speed = self.movement_velocity
if standin.groups.water then
local old_rot = self.object:get_rotation ()
local xz_mag = math.sqrt (dx * dx + dz * dz)
local des_pitch
if xz_mag > 1.0e-5 or xz_mag < -1.0e-5 then
local swim_max_pitch = self.swim_max_pitch
local old_pitch = old_rot.x
des_pitch = -math.atan2 (dy, xz_mag)
if des_pitch > swim_max_pitch then
des_pitch = self.swim_max_pitch
elseif des_pitch < -swim_max_pitch then
des_pitch = -self.swim_max_pitch
end
local target
-- ~50 degrees.
target = clip_rotation (old_pitch, des_pitch, 0.8727 * f)
self.object:set_rotation ({
x = target,
y = target_yaw,
z = 0,
})
des_pitch = target
else
-- Not moving horizontally.
des_pitch = self.object:get_rotation ().x
end
self.acc_dir.z = math.cos (des_pitch) * speed / 20
self.acc_dir.y = -math.sin (des_pitch) * speed / 20
self.acc_speed = speed * self.swim_speed_factor
self._acc_no_gravity = true
else
-- Fish cannot change their pitch outside a body of
-- water.
self.acc_dir.y = 0
self.acc_dir.z = 0
self._acc_no_gravity = false
self.object:set_rotation (vector.new (0, target_yaw, 0))
end
end
function mob_class:do_strafe (dtime, moveresult)
local vel = self.movement_velocity
local sx, sz = self.strafe_direction.x, self.strafe_direction.z
@ -531,6 +574,7 @@ function mob_class:halt_in_tracks (immediate)
self.acc_dir.y = 0
self.acc_dir.x = 0
self.acc_speed = 0
self._acc_movement_speed = 0
self.movement_goal = nil
self:cancel_navigation ()
@ -670,7 +714,7 @@ end
local IDLE_TIME_MAX = 250
function mob_class:init_ai ()
self.ai_idle_time = 0
self.ai_idle_time = 2 + math.random (2)
self.avoiding_sunlight = nil
self.avoiding = false
self.attack = nil
@ -680,6 +724,11 @@ function mob_class:init_ai ()
self.pacing = false
self:cancel_navigation ()
self:halt_in_tracks ()
if self.swims then
self:gwp_configure_aquatic_mob ()
self:configure_aquatic_mob ()
end
end
function mob_class:is_frightened (dtime)
@ -889,11 +938,18 @@ function mob_class:run_ai (dtime)
end
else
-- Should pace?
if idle and self.ai_idle_time > 5 and math.random (120) == 1 then
if idle and self.ai_idle_time > self.pace_interval then
-- Minecraft mobs pace to random positions
-- within a 20 block distance lengthwise and
-- 14 blocks vertically.
local target = self:pacing_target (pos, 10, 7, {"group:solid"})
local groups = {"group:solid"}
if self.swims_in and self.swims then
-- If this is an aquatic mob, search
-- for nodes in which it is capable of
-- swimming.
groups = self.swims_in
end
local target = self:pacing_target (pos, 10, self.pace_height, groups)
if target and self:gopath (target) then
self.pacing = true
end
@ -908,3 +964,66 @@ function mob_class:run_ai (dtime)
self.ai_idle_time = self.ai_idle_time + dtime
end
end
------------------------------------------------------------------------
-- Aquatic mob behavior.
------------------------------------------------------------------------
local function aquatic_pacing_target (self, pos, width, height, groups)
local aa = vector.new (pos.x - width, pos.y - height, pos.z - width)
local bb = vector.new (pos.x + width, pos.y + height, pos.z + width)
local nodes = minetest.find_nodes_in_area (aa, bb, groups)
return #nodes >= 1 and nodes[math.random (#nodes)]
end
local function aquatic_movement_step (self, dtime, moveresult)
if self.movement_goal ~= "go_pos"
and self.idle_gravity_in_liquids then
self._acc_no_gravity = false
end
if not self.idle_gravity_in_liquids and self._immersion_depth then
self._acc_no_gravity
= self._immersion_depth >= self.head_eye_height
end
mob_class.movement_step (self, dtime, moveresult)
end
function mob_class:fish_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 current_speed = self._acc_movement_speed or 0
current_speed = (vel - current_speed) * 0.125 + current_speed
local move_speed = 0.4
self._acc_movement_speed = current_speed
self.acc_speed = current_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)
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: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

View File

@ -132,6 +132,7 @@ function mob_class:new_gwp_context ()
tolerance = 1,
time_elapsed = 0,
total_nodes = 0,
y_offset = 0,
}
end
@ -225,10 +226,11 @@ end
function mob_class:gwp_start (context)
local pos = self:gwp_start_1 (context)
if pos then
local start_class = self:gwp_classify_node (context, pos)
-- If this mob is wider than 1 block, check for valid
-- start positions at every block on which it is
-- standing.
if self:gwp_classify_node (context, pos) == "BLOCKED" then
if start_class == "BLOCKED" or start_class == "IGNORE" then
local c1, c2, c3, c4
local cbox = self.collisionbox
c1 = vector.new (pos.x + cbox[1], pos.y, pos.z + cbox[3])
@ -437,6 +439,7 @@ function mob_class:gwp_reconstruct_path (context, arrival)
-- the path.
arrival.x = arrival.x + context.mob_width * 0.5 - 0.5
arrival.z = arrival.z + context.mob_width * 0.5 - 0.5
arrival.y = arrival.y + context.y_offset
arrival = arrival.referrer
end
return list
@ -519,7 +522,9 @@ end
local gwp_ej_scratch = vector.zero ()
local gwp_parent_penalty = nil
function mob_class:gwp_essay_jump (context, target, parent)
local GWP_JUMP_HEIGHT = 1.125
function mob_class:gwp_essay_jump (context, target, parent, floor)
local class = self:gwp_classify_node (context, target)
local penalty = self.gwp_penalties[class]
@ -541,6 +546,12 @@ function mob_class:gwp_essay_jump (context, target, parent)
end
-- Return true if this node is walkable or water.
if class ~= "OPEN" or (self.floats == 0 and class == "WATER") then
-- But first, verify that the node is not too far
-- above the current node.
local this_floor = ground_height (context, target)
if this_floor - floor > GWP_JUMP_HEIGHT then
return nil
end
local node = self:get_gwp_node (context, target.x, target.y,
target.z)
node.class = class
@ -639,9 +650,11 @@ local function gwp_edges_1 (self, context, parent, floor, xoff, zoff, jump)
-- this node?
if class == "OPEN" then
object = self:gwp_essay_drop (context, node)
-- This explicitly excludes trapdoors
-- and suchlike from consideration.
elseif class == "BLOCKED" then
node.y = node.y + 1
object = self:gwp_essay_jump (context, node, parent)
object = self:gwp_essay_jump (context, node, parent, floor)
elseif (class == "WATER" and self.floats == 0) then
object = self:gwp_essay_drift (context, node, object)
elseif class == "IGNORE" then
@ -1569,6 +1582,155 @@ minetest.register_globalstep (function (dtime)
pathfinding_quota = PATHFIND_PER_STEP
end)
------------------------------------------------------------------------
-- Pathfinding for swimming mobs.
------------------------------------------------------------------------
local function waterbound_gwp_basic_classify (pos)
local nodename, value = gwp_get_node (pos), nil
if not nodename then
return "IGNORE"
end
local def = minetest.registered_nodes[nodename]
if not def.groups.water then
value = "BLOCKED"
end
return value
end
local function waterbound_gwp_classify_node (self, context, pos)
local hash = hashpos (context, pos.x, pos.y, pos.z)
local cache = context.class_cache[hash]
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 b_width, b_height
b_width = context.mob_width - 1
b_height = context.mob_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
return "WATER"
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.
-- Cardinal directions.
local gwp_waterbound_directions = {
-- North.
{ 0, 0, 1, },
-- West.
{-1, 0, 0, },
-- South.
{ 0, 0, -1, },
-- East.
{ 1, 0, 0, },
-- Bottom.
{ 0, -1, 0, },
-- Top.
{ 0, 1, 0, },
}
-- Level diagonal movement.
for i, d in ipairs (gwp_waterbound_directions) do
if i > 4 then
break
end
local ccw = i == 4 and 1 or i + 1
local counterclockwise = gwp_waterbound_directions[ccw]
local diagonal = {
d[1] + counterclockwise[1],
d[2] + counterclockwise[2],
d[3] + counterclockwise[3],
i,
ccw,
}
table.insert (gwp_waterbound_directions, diagonal)
end
local waterbound_gwp_edges_scratch = vector.zero ()
local waterbound_gwp_edges_scratch_1 = {}
local waterbound_gwp_edges_buffer = {}
local function waterbound_gwp_edges (self, context, node)
local penalties = self.gwp_penalties
local buffer = waterbound_gwp_edges_buffer
local saved = waterbound_gwp_edges_scratch_1
local directions = gwp_waterbound_directions
local n = 0
for i, direction in ipairs (directions) do
local vector = waterbound_gwp_edges_scratch
local x, y, z = node.x + direction[1],
node.y + direction[2],
node.z + direction[3]
vector.x = x
vector.y = y
vector.z = z
if not direction[4]
or (saved[direction[4]] and saved[direction[5]]) then
local class = waterbound_gwp_classify_node (self, context, node)
local penalty = penalties[class]
if penalty >= 0.0 then
local object = self:get_gwp_node (context, x, y, z)
-- Record this class and update the node's
-- pathfinding penalty.
object.class = class
if penalty > object.penalty then
object.penalty = penalty
end
-- Save the result.
saved[i] = object
n = n + 1
buffer[n] = object
end
end
end
buffer[n + 1] = nil
return buffer
end
local function waterbound_gwp_start (self, context)
local pos = self.object:get_pos ()
-- Center pos vertically.
pos.y = pos.y + self.collisionbox[2]
+ (self.collisionbox[5] - self.collisionbox[2] / 2)
pos.x = floor (pos.x + 0.5)
pos.y = floor (pos.y + 0.5)
pos.z = floor (pos.z + 0.5)
return pos
end
------------------------------------------------------------------------
-- External interface.
------------------------------------------------------------------------
@ -1662,49 +1824,138 @@ function mob_class:next_waypoint (dtime)
self.pathfinding_context = nil
end
elseif self.waypoints then
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 ()
end
return
end
local next_wp = waypoints[#waypoints]
local self_pos = self.object:get_pos ()
local dist_to_xcenter = math.abs (next_wp.x - self_pos.x)
local dist_to_ycenter = math.abs (next_wp.y + 0.5 - self_pos.y)
local dist_to_zcenter = math.abs (next_wp.z - self_pos.z)
local cbox = self.collisionbox
local girth = math.max (cbox[4] - cbox[1], cbox[6] - cbox[3])
local mindist = girth > 0.75 and girth / 2 or 0.75 - girth / 2
self:gwp_next_waypoint (dtime)
self:gwp_timeout (dtime)
end
end
if dist_to_xcenter < mindist
and dist_to_zcenter < mindist
and dist_to_ycenter < 1.5 then
waypoints[#waypoints] = nil
else
-- Is this mob already en route to the next waypoint?
if #waypoints > 1 then
local ahead = waypoints[#waypoints - 1]
self_pos.y = ahead.y
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 ()
end
return
end
local next_wp = waypoints[#waypoints]
local self_pos = self.object:get_pos ()
local dist_to_xcenter = math.abs (next_wp.x - self_pos.x)
local dist_to_ycenter = math.abs (next_wp.y + 0.5 - self_pos.y)
local dist_to_zcenter = math.abs (next_wp.z - self_pos.z)
local cbox = self.collisionbox
local girth = math.max (cbox[4] - cbox[1], cbox[6] - cbox[3])
local mindist = girth > 0.75 and girth / 2 or 0.75 - girth / 2
if dist_to_xcenter < mindist
and dist_to_zcenter < mindist
and dist_to_ycenter < 1.5 then
waypoints[#waypoints] = nil
else
-- Is this mob already en route to the next waypoint?
if #waypoints > 1 then
local ahead = waypoints[#waypoints - 1]
self_pos.y = ahead.y
local dir = vector.direction (self_pos, ahead)
local dir1 = vector.direction (self_pos, next_wp)
if vector.dot (dir, dir1) < 0 then
next_wp = ahead
waypoints[#waypoints] = nil
end
end
-- Head to the center of the waypoint.
self.movement_goal = "go_pos"
self.movement_target = next_wp
self.movement_velocity = self.gowp_velocity or self.movement_speed
end
end
local function obstruction_is_water (name, def)
-- Water source blocks are always traversible.
return name == "mcl_core:water_source"
or name == "mclx_core:river_water_source"
end
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 ()
end
return
end
local next_wp = waypoints[#waypoints]
local self_pos = self.object:get_pos ()
local dist_to_xcenter = math.abs (next_wp.x - self_pos.x)
local dist_to_ycenter = math.abs (next_wp.y + 0.5 - self_pos.y)
local dist_to_zcenter = math.abs (next_wp.z - self_pos.z)
local cbox = self.collisionbox
local girth = math.max (cbox[4] - cbox[1], cbox[6] - cbox[3])
local mindist = girth > 0.75 and girth / 2 or 0.75 - girth / 2
if dist_to_xcenter < mindist
and dist_to_zcenter < mindist
and dist_to_ycenter < 1.0 then
waypoints[#waypoints] = nil
else
-- Is this mob already en route to the next waypoint?
-- Alternatively, is there a line of sight between
-- this and the next waypoint?
while #waypoints > 1 do
local ahead = waypoints[#waypoints - 1]
local cbox = self.collisionbox
local center = {
x = self_pos.x,
y = self_pos.y + cbox[2] + (cbox[5] - cbox[2]) / 2,
z = self_pos.z,
}
if self:line_of_sight (center, ahead, obstruction_is_water) then
next_wp = ahead
waypoints[#waypoints] = nil
else
local dir = vector.direction (self_pos, ahead)
local dir1 = vector.direction (self_pos, next_wp)
if vector.dot (dir, dir1) < 0 then
next_wp = ahead
waypoints[#waypoints] = nil
else
break
end
end
-- Head to the center of the waypoint.
self.movement_goal = "go_pos"
self.movement_target = next_wp
self.movement_velocity = self.gowp_velocity or self.movement_speed
end
self:gwp_timeout (dtime)
-- Head to the center of the waypoint.
self.movement_goal = "go_pos"
self.movement_target = next_wp
self.movement_velocity = self.gowp_velocity or self.movement_speed
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_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

View File

@ -197,9 +197,8 @@ function mob_class:set_velocity(v)
self.acc_dir.z = 0
return
end
local yaw = (self.object:get_yaw() or 0) + self.rotate
local vv = self.object:get_velocity()
if vv and yaw then
if vv then
self.acc_speed = v
-- Minecraft scales forward acceleration by desired
-- velocity in blocks/tick.
@ -865,10 +864,13 @@ end
local function pow_by_step (value, dtime)
return math.pow (value, dtime / 0.05)
end
mcl_mobs.pow_by_step = pow_by_step
local AIR_DRAG = 0.98
local AIR_FRICTION = 0.91
local WATER_DRAG = 0.8
local AQUATIC_WATER_DRAG = 0.9
local AQUATIC_GRAVITY = -0.1
local LAVA_FRICTION = 0.5
local LAVA_SPEED = 0.4
local FLYING_LIQUID_SPEED = 0.4
@ -894,7 +896,7 @@ local function scale_speed_flying (speed, friction)
end
function mob_class:accelerate_relative (acc, speed)
local yaw = self.object:get_yaw ()
local yaw = self.object:get_yaw () + self.rotate
acc = vector.length (acc) <= 1
and vector.copy (acc)
or vector.normalize (acc)
@ -904,13 +906,14 @@ function mob_class:accelerate_relative (acc, speed)
-- local rv = vector.rotate_around_axis (acc, {x = 0, y = 1, z = 0,}, yaw)
local s = -math.sin (yaw)
local c = math.cos (yaw)
local rv = vector.new (acc.x * c + acc.z * s, 0, acc.z * c - acc.x * s)
local rv = vector.new (acc.x * c + acc.z * s, acc.y * speed, acc.z * c - acc.x * s)
return rv
end
function mob_class:jump_actual (v)
self.order = ""
self:set_animation ("jump")
self:mob_sound ("jump")
v = {x = v.x, y = self.jump_height, z = v.z,}
if self:can_jump_cliff () then
v = vector.multiply (v, vector.new (2.8, 1, 2.8))
@ -1019,7 +1022,7 @@ function mob_class:motion_step (dtime, moveresult)
self.reset_fall_damage = 1
end
local water_vec = self:check_water_flow ()
local water_vec = not self.swims and self:check_water_flow ()
local velocity_factor = standon._mcl_velocity_factor or 1
if standin.groups.water then
@ -1221,3 +1224,43 @@ function mob_class:flying_step (dtime, moveresult)
self.object:set_velocity (v)
self:check_collision ()
end
-- Simplified `motion_step' for true (i.e., not birds or blazes)
-- swimming mobs.
local default_motion_step = mob_class.motion_step
function mob_class:aquatic_step (dtime, moveresult)
if not moveresult then
return
end
local standin = minetest.registered_nodes[self.standing_in]
if standin.groups.water then
local acc_speed = self.acc_speed
local acc_dir = self.acc_dir
local p = pow_by_step (AIR_DRAG, dtime)
local fv, scale
local v = self.object:get_velocity ()
acc_dir.x = acc_dir.x * p
acc_dir.z = acc_dir.z * p
p = pow_by_step (AQUATIC_WATER_DRAG, dtime)
scale = (1 - p) / (1 - AQUATIC_WATER_DRAG)
fv = self:accelerate_relative (acc_dir, acc_speed * scale)
v.x = v.x * p + fv.x
v.y = v.y * p + fv.y
v.z = v.z * p + fv.z
-- Apply gravity unless attacking mob.
if not self.attacking and not self._acc_no_gravity then
v.y = v.y + AQUATIC_GRAVITY * scale
end
self.object:set_velocity (v)
self:check_collision ()
else
default_motion_step (self, dtime, moveresult)
end
end

View File

@ -65,6 +65,8 @@ local axolotl = {
end,
makes_footstep_sound = false,
swims = true,
do_go_pos = mcl_mobs.mob_class.dolphin_do_go_pos,
idle_gravity_in_liquids = true,
breathes_in_water = true,
jump = true,
damage = 2,

View File

@ -54,6 +54,8 @@ local cod = {
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,
movement_speed = 14.0,
jump = false,

View File

@ -21,8 +21,10 @@ mcl_mobs.register_mob("mobs_mc:dolphin", {
rotate = 180,
spawn_in_group_min = 3,
spawn_in_group = 5,
pace_interval = 0.5,
tilt_swim = true,
collisionbox = {-0.3, 0.0, -0.3, 0.3, 0.79, 0.3},
floats = false,
collisionbox = {-0.45, -0.0, -0.45, 0.45, 0.6, 0.45},
head_eye_height = 0.3,
visual = "mesh",
mesh = "extra_mobs_dolphin.b3d",
@ -45,8 +47,8 @@ mcl_mobs.register_mob("mobs_mc:dolphin", {
visual_size = {x=3, y=3},
makes_footstep_sound = false,
swims = true,
fly = true,
fly_in = { "mcl_core:water_source", "mclx_core:river_water_source" },
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,
follow_holding = function (_) return true end,
do_custom = function (self, dtime)
@ -82,6 +84,7 @@ mcl_mobs.register_mob("mobs_mc:dolphin", {
end
end,
breathes_in_water = true,
idle_gravity_in_liquids = true,
jump = false,
view_range = 16,
fear_height = 4,

View File

@ -49,6 +49,8 @@ local salmon = {
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,
view_range = 16,

View File

@ -178,6 +178,55 @@ local function slime_run_ai (self, dtime)
slime_check_attack (self, self_pos, dtime)
end
local function slime_check_particle (self, dtime, moveresult)
if not self._slime_was_touching_ground
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
local scale = math.random () * 0.5 + 0.5
local angle = math.random () * math.pi * 2
local x, z
x = math.sin (angle) * scale * radius
z = math.cos (angle) * scale * radius
minetest.add_particle ({
pos = vector.offset (self_pos, x, 0, z),
collisiondetection = true,
texture = self._get_slime_particle (),
time = 0.20,
velocity = {
x = math.random (-1, 1),
y = math.random (1, 2),
z = math.random (-1, 1),
},
acceleration = {
x = 0,
y = math.random(-9, -5),
z = 0,
},
collision_removal = true,
size = math.random (0.5, 1.5),
glow = self._slime_particle_glow,
})
end
end
self._slime_was_touching_ground = moveresult.touching_ground
end
local function slime_do_attack (self, target)
self.attack = target
self.target_invisible_time = 3.0
@ -233,6 +282,7 @@ local slime_big = {
do_go_pos = slime_do_go_pos,
run_ai = slime_run_ai,
do_attack = slime_do_attack,
do_custom = slime_check_particle,
jump_delay_multiplier = 1,
fall_damage = 0,
view_range = 16,
@ -247,6 +297,12 @@ local slime_big = {
specific_attack = {
"mobs_mc:iron_golem",
},
_get_slime_particle = function ()
return "[combine:" .. math.random (3)
.. "x" .. math.random (3) .. ":-"
.. math.random (4) .. ",-"
.. math.random (4) .. "=mcl_core_slime.png"
end
}
mcl_mobs.register_mob("mobs_mc:slime_big", slime_big)
@ -419,6 +475,7 @@ local magma_cube_big = {
do_go_pos = slime_do_go_pos,
run_ai = slime_run_ai,
do_attack = slime_do_attack,
do_custom = slime_check_particle,
jump_delay_multiplier = 4,
water_damage = 0,
_mcl_freeze_damage = 5,
@ -433,6 +490,13 @@ local magma_cube_big = {
spawn_small_alternative = "mobs_mc:magma_cube_small",
on_die = spawn_children_on_die("mobs_mc:magma_cube_small", 0.8, 1.5),
fire_resistant = true,
specific_attack = {
"mobs_mc:iron_golem",
},
_get_slime_particle = function ()
return "mcl_particles_fire_flame.png"
end,
_slime_particle_glow = 14,
}
mcl_mobs.register_mob("mobs_mc:magma_cube_big", magma_cube_big)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -95,6 +95,8 @@ local tropical_fish = {
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,
view_range = 16,

View File

@ -317,7 +317,11 @@ local function register_slab(subname, stairdef)
paramtype = "light",
-- Facedir intentionally left out (see below)
is_ground_content = false,
groups = table.merge(stairdef.groups, { slab = 1, building_block = 1 }),
groups = table.merge(stairdef.groups, {
slab = 1,
building_block = 1,
_mcl_partial=2,
}),
sounds = stairdef.sounds,
node_box = {
type = "fixed",
@ -383,7 +387,7 @@ local function register_slab(subname, stairdef)
}, stairdef.overrides or {})
minetest.register_node(":"..lower_slab, table.merge(nodedef,{
groups = table.merge(stairdef.groups,{slab = 1}),
groups = table.merge(stairdef.groups,{slab = 1, _mcl_partial=2}),
}))
-- Register the upper slab.
@ -427,6 +431,7 @@ local function register_slab(subname, stairdef)
dgroups.not_in_creative_inventory = 1
dgroups.not_in_craft_guide = 1
dgroups.slab = nil
dgroups._mcl_partial = 2
dgroups.double_slab = 1
minetest.register_node(":"..double_slab, {
description = stairdef.double_description,

View File

@ -73,8 +73,9 @@ local function player_collision (player)
local r2 = (math.random (300) - 150) / 2400
local x_diff = pos2.x - pos.x + r1
local z_diff = pos2.z - pos.z + r2
local max_diff, d_scale
local max_diff
= math.max (math.abs (x_diff), math.abs (z_diff))
local d_scale
if max_diff > 0.01 then
max_diff = math.sqrt (max_diff)