WIP -- squash later
This commit is contained in:
parent
40967866bb
commit
6f34a133eb
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
BIN
mods/ENTITIES/mobs_mc/textures/mcl_particles_fire_flame.png
Normal file
BIN
mods/ENTITIES/mobs_mc/textures/mcl_particles_fire_flame.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user