Making movement work better.

This commit is contained in:
kno10 2024-10-02 21:24:42 +02:00
parent 19c1863100
commit 8c38745f6c
17 changed files with 317 additions and 242 deletions

View File

@ -28,6 +28,16 @@ if minetest.settings:get_bool("only_peaceful_mobs", false) then
end)
end
function mob_class:safe_remove()
self.removed = true
minetest.after(0,function(obj)
if obj and obj:get_pos() then
mcl_burning.extinguish(obj)
obj:remove()
end
end,self.object)
end
function mob_class:update_tag() --update nametag and/or the debug box
local tag
if mobs_debug then
@ -206,7 +216,6 @@ function mob_class:mob_activate(staticdata, def, dtime)
self.standing_in = NODE_IGNORE
self.standing_on = NODE_IGNORE
self.standing_under = NODE_IGNORE
self.standing_body = NODE_IGNORE
self.standing_depth = 0
self.state = self.state or "stand"
self.jump_sound_cooloff = 0 -- used to prevent jump sound from being played too often in short time
@ -220,6 +229,8 @@ function mob_class:mob_activate(staticdata, def, dtime)
self.blinktimer = 0
self.blinkstatus = false
self.acceleration = vector.zero()
self.nametag = self.nametag or def.nametag
self.object:set_properties(self)
@ -299,22 +310,22 @@ end
local function on_step_work(self, dtime, moveresult)
local pos = self.object:get_pos()
if not pos then return end
if self:check_despawn(pos, dtime) then return true end
if not pos or self.removed then return end
if self:check_despawn(pos, dtime) then return end
if self:outside_limits() then return end
-- Update what we know of the mobs environment for physics and movement
self:update_standing()
pos = self:limit_vel_acc_for_large_dtime(pos, dtime, moveresult) -- limit maximum movement to reduce lag effects
self:update_standing(pos, moveresult) -- update what we know of the mobs environment for physics and movement
local player_in_active_range = self:player_in_active_range()
-- The following functions return true when the mob died and we should stop processing
if self:check_suspend(player_in_active_range) then return end
if self:gravity_and_floating(pos, dtime, moveresult) then return end
if self:step_damage(dtime, pos) then return end
self:check_water_flow()
self:check_water_flow(dtime, pos)
if self.state == "die" then return end
self._can_jump_cliff = not self._jumping_cliff and self:can_jump_cliff()
--self:flop()
self:flop()
self:smooth_rotation(dtime)
if player_in_active_range then
@ -350,6 +361,7 @@ local function on_step_work(self, dtime, moveresult)
self:smooth_acceleration(dtime)
local cx, cz = self:collision()
self.object:add_velocity(vector.new(cx, 0, cz))
self:update_vel_acc(dtime)
if mobs_debug then self:update_tag() end
if not self.object:get_luaentity() then return false end
end
@ -416,9 +428,7 @@ local function update_lifetimer(dtime)
timer = 0
end
minetest.register_globalstep(function(dtime)
update_lifetimer(dtime)
end)
minetest.register_globalstep(update_lifetimer)
minetest.register_chatcommand("clearmobs", {
privs = { maphack = true },

View File

@ -3,6 +3,7 @@ local mob_class = mcl_mobs.mob_class
local damage_enabled = minetest.settings:get_bool("enable_damage")
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
local mobs_see_through_opaque = mcl_mobs.see_through_opaque
-- pathfinding settings
local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
@ -89,7 +90,8 @@ function mob_class:smart_mobs(s, p, dist, dtime)
self.path.lastpos = vector_copy(s)
local use_pathfind = false
local has_lineofsight = self:line_of_sight(vector_offset(s, 0, .5, 0), vector_offset(target_pos, 0, 1.5, 0), self.see_through_opaque or mobs_see_through, false)
local has_lineofsight = self:line_of_sight(vector_offset(s, 0, .5, 0), vector_offset(target_pos, 0, 1.5, 0),
self.see_through_opaque or mobs_see_through_opaque, false)
-- im stuck, search for path
if not has_lineofsight then
@ -157,12 +159,7 @@ function mob_class:smart_mobs(s, p, dist, dtime)
local dropheight = 12
if self.fear_height ~= 0 then dropheight = self.fear_height end
local jumpheight = 0
if self.jump and self.jump_height >= 4 then
jumpheight = min(ceil(self.jump_height * 0.25), 4)
elseif self.stepheight > 0.5 then
jumpheight = 1
end
local jumpheight = self.jump and floor(self.jump_height + 0.1) or 0
self.path.way = minetest.find_path(s, p1, 16, jumpheight, dropheight, "A*_noprefetch")
self.state = ""

View File

@ -75,7 +75,7 @@ function mob_class:mob_sound(soundname, is_opinion, fixed_pitch)
local sound = soundinfo[soundname]
if not sound then return end
if is_opinion and self.opinion_sound_cooloff > 0 then return end
local pitch = fixed_pitch
local pitch
if not fixed_pitch then
pitch = soundinfo.base_pitch or 1
if self.child and not self.sounds_child then pitch = pitch * 1.5 end

View File

@ -6,6 +6,7 @@ local modname = minetest.get_current_modname()
local path = minetest.get_modpath(modname)
local S = minetest.get_translator(modname)
mcl_mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt"
mcl_mobs.see_through_opaque = minetest.settings:get_bool("mobs_see_through_opaque", false)
-- used by the libaries below.
-- get node but use fallback for nil or unknown
@ -147,20 +148,25 @@ function mcl_mobs.register_mob(name, def)
end
end
if type(def.fly_in) == "string" then
def.fly_in = { def.fly_in }
end
local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25}
-- Workaround for <https://github.com/minetest/minetest/issues/5966>:
-- Increase upper Y limit to avoid mobs glitching through solid nodes.
-- Removed now, as this was supposedly fixed in 5.3.0?
-- FIXME: Remove workaround if it's no longer needed.
if collisionbox[5] < 0.79 then
collisionbox[5] = 0.79
end
--if collisionbox[5] < 0.79 then
-- collisionbox[5] = 0.79
--end
local final_def = {
use_texture_alpha = def.use_texture_alpha,
head_swivel = def.head_swivel or nil, -- bool to activate this function
head_yaw_offset = math.rad(def.head_yaw_offset or 0), -- for wonkey model bones
head_pitch_multiplier = def.head_pitch_multiplier or 1, --for inverted pitch
bone_eye_height = def.bone_eye_height or 1.4, -- head bone offset
head_eye_height = def.head_eye_height or def.bone_eye_height or 0, -- how hight aproximatly the mobs head is fromm the ground to tell the mob how high to look up at the player
head_eye_height = def.head_eye_height or def.bone_eye_height or 0, -- how high aproximately the mobs head is from the ground to tell the mob how high to look up at the player
curiosity = def.curiosity or 1, -- how often mob will look at player on idle
head_yaw = def.head_yaw or "y", -- axis to rotate head on
horizontal_head_height = def.horizontal_head_height or 0,
@ -179,7 +185,7 @@ function mcl_mobs.register_mob(name, def)
spawn_small_alternative = def.spawn_small_alternative,
do_custom = def.do_custom,
detach_child = def.detach_child,
jump_height = def.jump_height or 4, -- was 6
jump_height = def.jump_height or 1,
rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2
lifetimer = def.lifetimer or 57.73,
hp_min = scale_difficulty(def.hp_min, 5, 1),
@ -398,11 +404,8 @@ end
-- register arrow for shoot attack
function mcl_mobs.register_arrow(name, def)
if not name or not def then return end -- errorcheck
minetest.register_entity(name, {
physical = false,
visual = def.visual,
visual_size = def.visual_size,
@ -426,31 +429,21 @@ function mcl_mobs.register_arrow(name, def)
self._puncher = puncher
end,
collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0},
automatic_face_movement_dir = def.rotate
and (def.rotate - (math.pi / 180)) or false,
automatic_face_movement_dir = def.rotate and (def.rotate - (math.pi / 180)) or false,
on_activate = def.on_activate,
on_step = def.on_step or function(self, dtime)
self.timer = self.timer + dtime
local pos = self.object:get_pos()
if self.switch == 0
or self.timer > self._lifetime
or not within_limits(pos, 0) then
if self.switch == 0 or self.timer > self._lifetime or not within_limits(pos, 0) then
mcl_burning.extinguish(self.object)
self.object:remove();
return
end
-- does arrow have a tail (fireball)
if def.tail
and def.tail == 1
and def.tail_texture then
if def.tail == 1 and def.tail_texture then
minetest.add_particle({
pos = pos,
velocity = {x = 0, y = 0, z = 0},
@ -464,24 +457,17 @@ function mcl_mobs.register_arrow(name, def)
end
if self.hit_node then
local node = node_ok(pos).name
if minetest.registered_nodes[node].walkable then
self.hit_node(self, pos, node)
if self.drop == true then
pos.y = pos.y + 1
self.lastpos = (self.lastpos or pos)
minetest.add_item(self.lastpos, self.object:get_luaentity().name)
end
self.object:remove();
return
end
end
@ -498,12 +484,8 @@ function mcl_mobs.register_arrow(name, def)
end
if self.hit_player or self.hit_mob or self.hit_object then
for _,object in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do
if self.hit_player
and object:is_player() then
if self.hit_player and object:is_player() then
self.hit_player(self, object)
self.object:remove();
return
@ -544,30 +526,20 @@ end
-- * spawn_egg=1: Spawn egg (generic mob, no metadata)
-- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
function mcl_mobs.register_egg(mob_id, desc, background_color, overlay_color, addegg, no_creative)
local grp = {spawn_egg = 1}
-- do NOT add this egg to creative inventory (e.g. dungeon master)
if no_creative == true then
grp.not_in_creative_inventory = 1
end
local grp = { spawn_egg = 1 }
if no_creative == true then grp.not_in_creative_inventory = 1 end
local invimg = "(spawn_egg.png^[multiply:" .. background_color ..")^(spawn_egg_overlay.png^[multiply:" .. overlay_color .. ")"
if old_spawn_icons then
local mobname = mob_id:gsub("mobs_mc:","")
local fn = "mobs_mc_spawn_icon_"..mobname..".png"
if mcl_util.file_exists(minetest.get_modpath("mobs_mc").."/textures/"..fn) then
invimg = fn
end
local fn = "mobs_mc_spawn_icon_" .. mob_id:gsub("mobs_mc:","") .. ".png"
if mcl_util.file_exists(minetest.get_modpath("mobs_mc").."/textures/"..fn) then invimg = fn end
end
if addegg == 1 then
invimg = "mobs_chicken_egg.png^(" .. invimg ..
"^[mask:mobs_chicken_egg_overlay.png)"
invimg = "mobs_chicken_egg.png^(" .. invimg .. "^[mask:mobs_chicken_egg_overlay.png)"
end
-- register old stackable mob egg
minetest.register_craftitem(mob_id, {
description = desc,
inventory_image = invimg,
groups = grp,
@ -604,9 +576,6 @@ function mcl_mobs.register_egg(mob_id, desc, background_color, overlay_color, ad
local dim = mcl_worlds.pos_to_dimension(placer:get_pos())
local mob_light_lvl = {mcl_mobs:mob_light_lvl(itemstack:get_name(),dim)}
--minetest.log("min light: " .. mob_light_lvl[1])
--minetest.log("max light: " .. mob_light_lvl[2])
-- Handle egg conversion
local convert_to = (minetest.registered_entities[mob_name] or {})._convert_to
if convert_to then mob_name = convert_to end
@ -618,9 +587,7 @@ function mcl_mobs.register_egg(mob_id, desc, background_color, overlay_color, ad
return itemstack
end
if not minetest.registered_entities[mob_name] then
return itemstack
end
if not minetest.registered_entities[mob_name] then return itemstack end
if minetest.settings:get_bool("only_peaceful_mobs", false)
and minetest.registered_entities[mob_name].type == "monster" then

View File

@ -126,7 +126,7 @@ function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
-- jump
if ctrl.jump then
if velo.y == 0 then
velo.y = velo.y + entity.jump_height
velo.y = velo.y + sqrt(entity.jump_height * 20)
acce_y = acce_y + 1
end
end

View File

@ -1,7 +1,7 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local DEFAULT_FALL_SPEED = -9.81*1.5
local FLOP_HEIGHT = 6
local FLOP_VEL = math.sqrt(1.5 * 20) -- 1.5 blocks
local FLOP_HOR_SPEED = 1.5
local CHECK_HERD_FREQUENCY = 4
@ -12,7 +12,6 @@ local node_snow = "mcl_core:snow"
local logging = minetest.settings:get_bool("mcl_logging_mobs_movement", true)
local mobs_griefing = minetest.settings:get_bool("mobs_griefing", true)
local mobs_see_through_opaque = minetest.settings:get_bool("mobs_see_through_opaque", false)
local random = math.random
local sin = math.sin
@ -31,7 +30,8 @@ local vector_offset = vector.offset
local vector_distance = vector.distance
local node_ok = mcl_mobs.node_ok
local see_through_opaque = mcl_mobs.see_through_opaque
local mobs_see_through_opaque = mcl_mobs.see_through_opaque
local line_of_sight = mcl_mobs.line_of_sight
-- Stop movement and stand
function mob_class:stand()
@ -86,12 +86,12 @@ function mob_class:target_visible(origin)
local cbox = self.collisionbox
-- TODO also worth testing midway between feet and head?
-- to top of entity
if line_of_sight(origin_eye_pos, vector_offset(target_pos, 0, cbox[5], 0), mobs_see_through_opaque, true) then return true end
if line_of_sight(origin_eye_pos, vector_offset(target_pos, 0, cbox[5], 0), self.see_through_opaque or mobs_see_through_opaque, true) then return true end
-- to feed of entity
if self.attack:is_player() then
if line_of_sight(origin_eye_pos, target_pos, mobs_see_through_opaque, true) then return true end -- Cbox would put feet under ground which interferes with ray
if line_of_sight(origin_eye_pos, target_pos, self.see_through_opaque or mobs_see_through_opaque, true) then return true end -- Cbox would put feet under ground which interferes with ray
else
if line_of_sight(origin_eye_pos, vector_offset(target_pos, 0, cbox[2], 0), mobs_see_through_opaque, true) then return true end
if line_of_sight(origin_eye_pos, vector_offset(target_pos, 0, cbox[2], 0), self.see_through_opaque or mobs_see_through_opaque, true) then return true end
end
--minetest.log("start targ_head_height: " .. dump(targ_head_height))
@ -111,7 +111,7 @@ end
-- check line of sight
function mob_class:line_of_sight(pos1, pos2, stepsize)
return line_of_sight(pos1, pos2, mobs_see_through_opaque, true)
return line_of_sight(pos1, pos2, self.see_through_opaque or mobs_see_through_opaque, true)
end
function mob_class:can_jump_cliff()
@ -169,7 +169,7 @@ function mob_class:is_at_cliff_or_danger()
return "free fall"
end
local height = ypos + 0.4 - blocker.y
local chance = (self.jump_height or 4) * 0.25 / (height * height)
local chance = self.jump_height / (height * height)
if height >= self.fear_height and random() < chance then
if logging then
minetest.log("action", "[mcl_mobs] "..self.name.." avoiding drop of "..height) --.." chance "..chance)
@ -304,17 +304,12 @@ function mob_class:do_jump()
return false
end
v.y = math.min(v.y, 0) + self.jump_height -- + 0.3
--if in_water then
-- v.x, v.y, v.z = v.x * 1.2, v.y + self.jump_height * 0.5, v.z * 1.2
--elseif self._can_jump_cliff then
-- v.x, v.y, v.z = v.x * 2.5, v.y + self.jump_height * 0.1, v.z * 2.5
--end
if in_water or self._cam_jump_cliff then v.y = v.y + self.jump_height * 0.25 end
v.y = math.min(v.y, 0) + math.sqrt(self.jump_height * 20) + (in_water or self._can_jump_cliff and 0.5 or 0)
v.y = math.min(-self.fall_speed, math.max(v.y, self.fall_speed))
self:set_animation("jump") -- only when defined
self.object:set_velocity(v)
self:set_animation("run")
self:set_animation("jump") -- only when defined
self:set_velocity(self.run_velocity)
if self.jump_sound_cooloff <= 0 then
self:mob_sound("jump")
@ -454,7 +449,7 @@ function mob_class:check_runaway_from()
sp = s
dist = vector_distance(p, s)
-- choose closest player/mpb to runaway from
if dist < min_dist and line_of_sight(vector_offset(sp, 0, 1, 0), vector_offset(p, 0, 1, 0), mobs_see_through_opaque, false) then
if dist < min_dist and line_of_sight(vector_offset(sp, 0, 1, 0), vector_offset(p, 0, 1, 0), self.see_through_opaque or mobs_see_through_opaque, false) then
-- aim higher to make looking up hills more realistic
min_dist = dist
min_player = player
@ -531,29 +526,25 @@ function mob_class:check_follow()
end
end
-- swimmers flop when out of their element, and swim again when back in
function mob_class:flop()
-- swimmers flop when out of their element, and swim again when back in
if self.fly then
local s = self.object:get_pos()
if self:flight_check(s) == false then
self.state = "flop"
self.object:set_acceleration(vector_new(0, DEFAULT_FALL_SPEED, 0))
local p = self.object:get_pos()
local sdef = minetest.registered_nodes[node_ok(vector_offset(p, 0, self.collisionbox[2] - 0.2, 0)).name]
-- Flop on ground
if sdef and sdef.walkable then
if self.object:get_velocity().y < 0.1 then
self:mob_sound("flop")
self.object:set_velocity(vector_new((random() * 2 - 1) * FLOP_HOR_SPEED, FLOP_HEIGHT, (random() * 2 - 1) * FLOP_HOR_SPEED))
end
if not self.fly then return end
if not self:flight_check() then
self.state = "flop"
self.acceleration.y = DEFAULT_FALL_SPEED
local sdef = self.standing_on
if sdef and sdef.walkable then -- flop on ground
if self.object:get_velocity().y == 0 then
self:mob_sound("flop")
self.object:add_velocity(vector_new(0, FLOP_VEL, 0))
self:turn_by(TWOPI * random(), 8)
self:set_velocity(FLOP_HOR_SPEED)
end
self:set_animation("stand", true)
elseif self.state == "flop" then
self:stand()
end
self:set_animation("stand", true)
elseif self.state == "flop" then
--self:stand()
self.acceleration.y = 0
end
end
@ -625,28 +616,33 @@ function mob_class:do_states_walk()
end
-- facing wall? then turn
local facing_wall = false
local cbox = self.collisionbox
local dir_x, dir_z = -sin(yaw - QUARTERPI) * (cbox[4] + 0.5), cos(yaw - QUARTERPI) * (cbox[4] + 0.5)
local nodface = minetest.registered_nodes[minetest.get_node(vector_offset(s, dir_x, cbox[5] - cbox[2], dir_z)).name]
if nodface and nodface.walkable then
dir_x, dir_z = -sin(yaw + QUARTERPI) * (cbox[4] + 0.5), cos(yaw + QUARTERPI) * (cbox[4] + 0.5)
nodface = minetest.registered_nodes[minetest.get_node(vector_offset(s, dir_x, cbox[5] - cbox[2], dir_z)).name]
-- todo: use moveresult collision info here?
if self:get_velocity_xyz() < 0.1 then
facing_wall = true
elseif not facing_wall then
local cbox = self.collisionbox
local dir_x, dir_z = -sin(yaw - QUARTERPI) * (cbox[4] + 0.5), cos(yaw - QUARTERPI) * (cbox[4] + 0.5)
local nodface = minetest.registered_nodes[minetest.get_node(vector_offset(s, dir_x, (cbox[5] - cbox[2]) * 0.5, dir_z)).name]
if nodface and nodface.walkable then
facing_wall = true
dir_x, dir_z = -sin(yaw + QUARTERPI) * (cbox[4] + 0.5), cos(yaw + QUARTERPI) * (cbox[4] + 0.5)
nodface = minetest.registered_nodes[minetest.get_node(vector_offset(s, dir_x, (cbox[5] - cbox[2]) * 0.5, dir_z)).name]
if nodface and nodface.walkable then
facing_wall = true
end
end
end
if facing_wall then
if logging then
minetest.log("action", "[mcl_mobs] "..self.name.." facing a wall, turning.")
end
self:turn_by(TWOPI * (random() - 0.5), 6)
self:turn_by(TWOPI * (random() - 0.5), 10)
-- otherwise randomly turn
elseif random() <= 0.3 then
local home = self._home or self._bed
if home and random() < 0.1 then
self:turn_in_direction(home.x - s.x, home.z - s.z, 8)
else
self:turn_by(QUARTERPI * (random() - 0.5), 10)
self:turn_by(QUARTERPI * (random() - 0.5), 20)
end
end
self:set_velocity(self.walk_velocity)
@ -687,9 +683,7 @@ function mob_class:do_states_stand(player_in_active_range)
end
-- npc's ordered to stand stay standing
if self.order == "stand" or self.order == "sleep" or self.order == "work" then
else
if self.order ~= "stand" and self.order ~= "sleep" and self.order ~= "work" then
if player_in_active_range then
if self.walk_chance ~= 0
and self.facing_fence ~= true
@ -709,8 +703,7 @@ end
function mob_class:do_states_runaway()
self.runaway_timer = self.runaway_timer + 1
-- stop after 5 seconds or when at cliff
if self.runaway_timer > 5
or self:is_at_cliff_or_danger() then
if self.runaway_timer > 5 or self:is_at_cliff_or_danger() then
self.runaway_timer = 0
self:stand()
self:turn_by(PI * (random() + 0.5), 8)

View File

@ -2,6 +2,8 @@ local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local validate_vector = mcl_util.validate_vector
local MAX_DTIME = 0.25 -- todo: make user configurable?
local ACCELERATION_MIX = 1.0 -- how much of acceleration to handle in Lua instead of MTE todo: make user configurable
local ENTITY_CRAMMING_MAX = 24
local CRAMMING_DAMAGE = 3
local DEATH_DELAY = 0.5
@ -17,6 +19,7 @@ local abs = math.abs
local atan2 = math.atan2
local sin = math.sin
local cos = math.cos
local sqrt = math.sqrt
local node_ok = mcl_mobs.node_ok
local PATHFINDING = "gowp"
@ -48,13 +51,25 @@ end
-- standing_on: node below
-- standing_under: node above
-- these may be "nil" (= ignore) and are otherwise already resolved via minetest.registered_nodes
function mob_class:update_standing()
local pos = self.object:get_pos()
function mob_class:update_standing(pos, moveresult)
local temp_pos = vector.offset(pos, 0, self.collisionbox[2] + 0.5, 0) -- foot level
self.standing_in = minetest.registered_nodes[minetest.get_node(temp_pos).name] or NODE_IGNORE
temp_pos.y = temp_pos.y - 1.5 -- below
self.standing_on = minetest.registered_nodes[minetest.get_node(temp_pos).name] or NODE_IGNORE
self.standing_height = pos.y - math.ceil(temp_pos.y + 0.5) + self.head_eye_height * 0.75
self.standing_on_node = minetest.get_node(temp_pos) -- to allow access to param2 in, e.g., stalker
self.standing_on = standing_on or minetest.registered_nodes[self.standing_on_node.name] or NODE_IGNORE
-- sometimes, we may be colliding with a node *not* below us, effectively standing on it instead (e.g., a corner)
if not self.standing_on.walkable and moveresult and moveresult.collisions then
-- to inspect: minetest.log("action", dump(moveresult):gsub(" *\n\\s*",""))
for _, c in ipairs(moveresult.collisions) do
if c.axis == "y" and c.type == "node" and c.old_velocity.y < 0 then
self.standing_on_node = minetest.get_node(c.node_pos)
self.standing_on = minetest.registered_nodes[self.standing_on_node.name]
break
end
end
end
-- approximate height of head over ground:
self.standing_height = pos.y - math.floor(temp_pos.y + 0.5) - 0.5 + self.head_eye_height * 0.9
temp_pos.y = temp_pos.y + 2 -- at +1 = above
self.standing_under = minetest.registered_nodes[minetest.get_node(temp_pos).name] or NODE_IGNORE
end
@ -77,28 +92,20 @@ function mob_class:object_in_range(object)
factor = factors and factors[self.name]
end
-- Distance check
local dist
if factor and factor == 0 then
return false
elseif factor then
dist = self.view_range * factor
else
dist = self.view_range
local dist = self.view_range
if factor then
if factor == 0 then return false end
dist = dist * factor
end
local p1, p2 = self.object:get_pos(), object:get_pos()
return p1 and p2 and (vector.distance(p1, p2) <= dist)
end
function mob_class:item_drop(cooked, looting_level)
if not mobs_drop_items then return end
looting_level = looting_level or 0
if (self.child and self.type ~= "monster") then
return
end
if (self.child and self.type ~= "monster") then return end
local obj, item, num
local pos = self.object:get_pos()
@ -160,7 +167,6 @@ end
function mob_class:collision()
local pos = self.object:get_pos()
if not pos then return 0,0 end
local vel = self.object:get_velocity()
local x, z = 0, 0
local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5
for _,object in pairs(minetest.get_objects_inside_radius(pos, width)) do
@ -172,7 +178,7 @@ function mob_class:collision()
local pos2 = object:get_pos()
local vx, vz = pos.x - pos2.x, pos.z - pos2.z
local force = width - (vx*vx+vz*vz)^0.5
local force = width - sqrt(vx*vx+vz*vz)
if force > 0 then
force = force * force * (object:is_player() and 2 or 1) -- players push more
-- minetest.log("mob push force "..force.." "..tostring(self.name).." by "..tostring(ent and ent.name or "player"))
@ -202,22 +208,30 @@ function mob_class:set_velocity(v)
self.target_vel = v
end
-- calculate mob velocity (3d)
function mob_class:get_velocity_xyz()
local v = self.object:get_velocity()
if not v then return 0 end
local x, y, z = v.x, v.y, v.z
return sqrt(x*x + y*y + z*z)
end
-- calculate mob velocity (2d)
function mob_class:get_velocity_xz()
local v = self.object:get_velocity()
if not v then return 0 end
return (v.x*v.x + v.z*v.z)^0.5
local x, z = v.x, v.z
return sqrt(x*x + z*z)
end
-- legacy API
mob_class.get_velocity = mob_class.get_velocity_xz
-- Relative turn, primarily for random turning
-- @param angle number: realative angle, in radians
-- @param dtime deprecated: ignored now, because of smooth rotations
-- @param delay number: time needed to turn
-- @param dtime deprecated: ignored now, because of smooth rotations
-- @return target angle
function mob_class:turn_by(angle, delay, dtime)
return self:set_yaw((self.object:get_yaw() or 0) + angle, delay, dtime)
return self:set_yaw((self.object:get_yaw() or 0) + self.rotate + angle, delay, dtime)
end
-- Turn into a direction (e.g., to the player, or away)
-- @param dx number: delta in x axis to target
@ -273,7 +287,7 @@ function mob_class:smooth_rotation(dtime)
if not self.target_yaw then return end
local delay = self.delay
local initial_yaw = self.object:get_yaw() or 0
local initial_yaw = (self.object:get_yaw() or 0) + self.rotate
local yaw -- resulting yaw for this tick
if delay and delay > 1 then
local dif = (self.target_yaw - initial_yaw + PI) % TWOPI - PI
@ -286,44 +300,33 @@ function mob_class:smooth_rotation(dtime)
if self.shaking then
yaw = yaw + (random() * 2 - 1) / 72 * dtime
end
if yaw ~= initial_yaw then self.object:set_yaw(yaw) end
if yaw ~= initial_yaw then self.object:set_yaw(yaw - self.rotate) end
--update_roll() -- Fleckenstein easter egg
end
-- Handling of intentional acceleration by the mob
-- its best to place environmental effects afterwards
-- TODO: have mobs that act faster and that act slower?
-- TODO: have mobs that acccelerate faster and that accelerate slower?
-- @param dtime number: timestep length
function mob_class:smooth_acceleration(dtime)
local yaw = self.target_yaw or (self.object:get_yaw() or 0) + self.rotate
local vel = self.target_vel or 0
local x, z = -sin(yaw) * vel, cos(yaw) * vel
local v = self.object:get_velocity()
local w = min(1, dtime * 10)
local w = min(dtime * 5, 1)
v.x, v.z = v.x + w * (x - v.x), v.z + w * (z - v.z)
self.object:set_velocity(v)
end
-- are we flying in what we are suppose to? (taikedz)
function mob_class:flight_check()
if not self.standin_in or not self.standing_on then return true end -- nil check
if not self.standing_in or self.standing_in.name == "ignore" then return true end -- unknown?
if not self.fly_in then return false end
local fly_in
if type(self.fly_in) == "string" then
fly_in = { self.fly_in }
elseif type(self.fly_in) == "table" then
fly_in = self.fly_in
else
return false
local nod = self.standing_in.name
-- todo: allow flowers etc. for birds
for _,checknode in pairs(self.fly_in) do
if nod == checknode then return true end
end
for _,checknode in pairs(fly_in) do
if nod == checknode or nod == "ignore" then
return true
end
end
return false
end
@ -738,10 +741,9 @@ end
function mob_class:check_entity_cramming()
local p = self.object:get_pos()
if not p then return end
local oo = minetest.get_objects_inside_radius(p,1)
local mobs = {}
for i = 1,#oo do
local l = oo[i]:get_luaentity()
for o in minetest.objects_inside_radius(p, 0.5) do
local l = o:get_luaentity()
if l and l.is_mob and l.health > 0 then table.insert(mobs,l) end
end
local clear = #mobs < ENTITY_CRAMMING_MAX
@ -769,34 +771,44 @@ end
-- @param moveresult table: minetest engine movement result (collisions)
-- @return true if mob died
function mob_class:gravity_and_floating(pos, dtime, moveresult)
if self._just_portaled then
self.start_fall_y = nil -- reset fall damage
end
if self.fly and self.state ~= "die" then return end
if not self.fall_speed then self.fall_speed = DEFAULT_FALL_SPEED end -- TODO: move to initialization code only?
if self.standing_in == NODE_IGNORE then -- not emerged yet, do not fall
self.object:set_velocity(vector.zero())
return false
end
self.object:set_properties({ nametag = "on: "..self.standing_on.name.."\nin: "..self.standing_in.name.."\n "..tostring(self.standing_height) })
-- self.object:set_properties({ nametag = "on: "..self.standing_on.name.."\nin: "..self.standing_in.name.."\n "..tostring(self.standing_height) })
-- Gravity
--local acc_y = self.fall_speed
local acc_y = moveresult and moveresult.touching_ground and 0 or self.fall_speed
local visc = 1
local acc = vector.new(0, not self.fly and moveresult and moveresult.touching_ground and 0 or self.fall_speed, 0)
self.visc = 1
local vel = self.object:get_velocity() or vector.zero()
local standbody = self.standing_in
if standbody.groups.water then
visc = 0.8
self.visc = 0.4
if self.floats > 0 then --and minetest.registered_nodes[node_ok(vector.offset(pos, 0, self.collisionbox[5] - 0.25, 0)).name].groups.water then
local w = self.standing_under.groups.water and 0 or self.standing_height -- 0 is submerged, 1 is out
acc_y = self.fall_speed * max(-1, min(1, 2 * w - 1)) -- -1 to +1
--self.object:set_acceleration(vector.new(0, -self.fall_speed * 0.5, 0))
local w = (self.standing_under.groups.water and 0 or self.standing_height) -- <1 is submerged, >1 is out
if w > 0.95 and w < 1.05 then
acc.y = 0 -- stabilize floating
else
acc.y = self.fall_speed * max(-1, min(1, w - 1)) -- -1 to +1
end
end
self.start_fall_y = nil -- otherwise might receive fall damage on the next jump?
elseif standbody.groups.lava then
visc = 0.7
self.visc = 0.5
if self.floats_on_lava > 0 then
local w = self.standing_under.groups.water and 0 or self.standing_height -- 0 is submerged, 1 is out
acc_y = self.fall_speed * max(-1, min(1, 2 * w - 1)) -- -1 to +1
--self.object:set_acceleration(vector.new(0, -self.fall_speed * 0.5, 0))
-- todo: relative to body height?
if w > 0.95 and w < 1.05 then
acc.y = 0
else
acc.y = self.fall_speed * max(-1, min(1, w - 1)) -- -1 to +1
end
end
self.start_fall_y = nil -- otherwise might receive fall damage on the next jump?
else
-- fall damage onto solid ground (bouncy ground will yield vel.y > 0)
if self.fall_damage == 1 and vel.y == 0 then
@ -819,50 +831,112 @@ function mob_class:gravity_and_floating(pos, dtime, moveresult)
end
end
end
--vel.x, vel.y, vel.z = vel.x * visc, (vel.y + acc_y * dtime) * visc, vel.z * visc
self.acceleration = acc
end
--- Limit the velocity and acceleration of a mob applied by MTE
-- This is an attempt to solve mobs trampolining on water.
-- The problem is when a large timestep occurs, acceleration and velocity is applied
-- by the minetest engine (MTE) for a long timestep. If a mob enters or leaves water
-- during this time (or a similar transition occurs), this can become wildly inaccurate.
-- A mob slightly above water will fall deep into water, or may fly high into
-- the air because of updrift.
function mob_class:limit_vel_acc_for_large_dtime(pos, dtime, moveresult)
-- hack not in use:
if ACCELERATION_MIX == 0 and dtime < MAX_DTIME then return pos end
local edtime, rdtime = dtime, 0 -- effective dtime and reverted dtime
if dtime >= MAX_DTIME then
edtime, rdtime = MAX_DTIME, dtime - MAX_DTIME
end
local vel = self.object:get_velocity()
local acc = self.object:get_acceleration()
-- revert excess movement and acceleration from MTE
if rdtime > 0 and not (moveresult and moveresult.collides) then
vel = vel - acc * rdtime
pos = pos - (vel - acc * rdtime * 0.5) * rdtime -- at average velocity during excess
end
-- apply the missing lua part of acceleration:
if ACCELERATION_MIX > 0 and self.acceleration then
local dx, dy, dz = self.acceleration.x, self.acceleration.y, self.acceleration.z
-- use collision information:
if moveresult and moveresult.collisions then
for _, c in ipairs(moveresult.collisions) do
if c.axis == "y" then
if c.old_velocity.y < 0 and dy < 0 then dy = 0 end
if c.old_velocity.y > 0 and dy > 0 then dy = 0 end
elseif c.axis == "x" then
if c.old_velocity.x < 0 and dx < 0 then dx = 0 end
if c.old_velocity.x > 0 and dx > 0 then dx = 0 end
elseif c.axis == "z" then
if c.old_velocity.z < 0 and dz < 0 then dz = 0 end
if c.old_velocity.z > 0 and dz > 0 then dz = 0 end
end
end
end
vel.x = vel.x + dx * edtime * ACCELERATION_MIX
vel.y = vel.y + dy * edtime * ACCELERATION_MIX
vel.z = vel.z + dz * edtime * ACCELERATION_MIX
-- because we cannot check for collission, we simply allow the extra acceleration to lag a timestep:
-- pos = pos + self.acceleration * edtime * 0.5 * rdtime
end
self.object:set_velocity(vel)
self.object:set_pos(pos)
return pos
end
--- Update velocity and acceleration at the end of our movement logic
--
function mob_class:update_vel_acc(dtime)
local vel = self.object:get_velocity()
--vel.x, vel.y, vel.z = vel.x * visc, (vel.y + acc.y * dtime) * visc, vel.z * visc
vel.y = max(min(vel.y, -self.fall_speed), self.fall_speed)
-- Cap dtime to reduce bopping on water (hence we also do not use minetest acceleration)
-- but the minetest engine already applied the current velocity on the full timestep
dtime = min(dtime, 0.01)
-- ideally, we could use: self.object:set_acceleration(vector.new(0, acc_y * visc, 0))
vel.y = vel.y + acc_y * dtime -- apply acceleration in LUA, with limited dtime
dtime = min(dtime, MAX_DTIME)
-- Slowdown in liquids:
if self.visc then
-- TODO: only on y, or also apply to vel.x, vel.z, acceleration?
vel.y = vel.y * self.visc^(dtime*10)
-- vel = vel * self.visc^(dtime*10)
end
-- acceleration:
if self.acceleration and ACCELERATION_MIX < 1 then
self.object:set_acceleration(self.acceleration * (1 - ACCELERATION_MIX))
-- the remaining part is applied after the dtime step
end
vel.y = vel.y * visc
self.object:set_velocity(vel)
end
function mob_class:check_water_flow()
-- Add water flowing for mobs from mcl_item_entity
local p = self.object:get_pos()
local node = minetest.get_node_or_nil(p)
local def = node and minetest.registered_nodes[node.name]
-- Add water flowing for mobs from mcl_item_entity
function mob_class:check_water_flow(dtime, pos)
local def = self.standing_in
-- Move item around on flowing liquids
if def and def.liquidtype == "flowing" then
--[[ Get flowing direction (function call from flowlib), if there's a liquid.
NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7.
Luckily, this is exactly what we need if we only care about water, which has this flowing distance. ]]
local vec = flowlib.quick_flow(p, node)
local vec = flowlib.quick_flow(pos, minetest.get_node(pos))
-- Just to make sure we don't manipulate the speed for no reason
if vec.x ~= 0 or vec.y ~= 0 or vec.z ~= 0 then
-- Minecraft Wiki: Flowing speed is "about 1.39 meters per second"
local f = 1.39
local f = 8 -- but we have acceleration ehre, not velocity. Was: 1.39
-- Set new item moving speed into the direciton of the liquid
local newv = vector.multiply(vec, f)
--self.object:set_acceleration(vector.zero())
self.object:add_velocity(vector.new(newv.x, -0.22, newv.z))
self.physical_state = true
self._flowing = true
self.object:set_properties({ physical = true })
self.acceleration = self.acceleration + vector.new(vec.x * f, -0.22, vec.z * f)
--self.physical_state = true
--self._flowing = true
--self.object:set_properties({ physical = true })
return
end
elseif self._flowing == true then
-- Disable flowing physics if not on/in flowing liquid
self._flowing = false
return
--elseif self._flowing == true then
-- -- Disable flowing physics if not on/in flowing liquid
-- self._flowing = false
-- return
end
end

View File

@ -83,7 +83,7 @@ mcl_mobs.register_mob("mobs_mc:blaze", {
shoot_offset = 1.0,
passive = false,
jump = true,
jump_height = 4,
jump_height = 1,
fly = true,
makes_footstep_sound = false,
fear_height = 0,

View File

@ -22,7 +22,7 @@ mcl_mobs.register_mob("mobs_mc:chicken", {
floats = 1,
head_swivel = "head.control",
bone_eye_height = 4,
head_eye_height = 1.5,
head_eye_height = .5,
horizontal_head_height = -.3,
curiosity = 10,
head_yaw="z",

View File

@ -77,7 +77,7 @@ mcl_mobs.register_mob("mobs_mc:enderdragon", {
damage = 10,
knock_back = false,
jump = true,
jump_height = 14,
jump_height = 10,
fly = true,
makes_footstep_sound = false,
dogshoot_switch = 1,

View File

@ -59,7 +59,7 @@ mcl_mobs.register_mob("mobs_mc:ghast", {
dogshoot_count_max =1,
passive = false,
jump = true,
jump_height = 4,
jump_height = 1,
floats=1,
fly = true,
makes_footstep_sound = false,

View File

@ -158,7 +158,7 @@ local horse = {
floats = 1,
makes_footstep_sound = true,
jump = true,
jump_height = 5.75, -- can clear 2.5 blocks
jump_height = 2.5, -- can clear 2.5 blocks
drops = {
{name = "mcl_mobitems:leather",
chance = 1,
@ -560,7 +560,7 @@ donkey.collisionbox = {
horse.collisionbox[6] * d,
}
donkey.jump = true
donkey.jump_height = 3.75 -- can clear 1 block height
donkey.jump_height = 1 -- can clear 1 block height
mcl_mobs.register_mob("mobs_mc:donkey", donkey)

View File

@ -134,7 +134,7 @@ local slime_big = {
walk_velocity = 1.9,
run_velocity = 1.9,
walk_chance = 0,
jump_height = 5.2,
jump_height = 1,
fear_height = 0,
spawn_small_alternative = "mobs_mc:slime_small",
on_die = spawn_children_on_die("mobs_mc:slime_small", 1.0, 1.5),
@ -156,7 +156,6 @@ slime_small.damage = 3
slime_small.reach = 2.25
slime_small.walk_velocity = 1.8
slime_small.run_velocity = 1.8
slime_small.jump_height = 4.3
slime_small.spawn_small_alternative = "mobs_mc:slime_tiny"
slime_small.on_die = spawn_children_on_die("mobs_mc:slime_tiny", 0.6, 1.0)
mcl_mobs.register_mob("mobs_mc:slime_small", slime_small)
@ -181,7 +180,6 @@ slime_tiny.drops = {
}
slime_tiny.walk_velocity = 1.7
slime_tiny.run_velocity = 1.7
slime_tiny.jump_height = 3
slime_tiny.spawn_small_alternative = nil
slime_tiny.on_die = nil
@ -363,7 +361,7 @@ local magma_cube_big = {
attack_type = "dogfight",
passive = false,
jump = true,
jump_height = 8,
jump_height = 4,
walk_chance = 0,
fear_height = 0,
spawn_small_alternative = "mobs_mc:magma_cube_small",
@ -386,7 +384,7 @@ magma_cube_small.damage = 3
magma_cube_small.reach = 2.1
magma_cube_small.walk_velocity = .8
magma_cube_small.run_velocity = 2.0
magma_cube_small.jump_height = 6
magma_cube_small.jump_height = 2
magma_cube_small.damage = 4
magma_cube_small.reach = 2.75
magma_cube_small.armor = 66
@ -407,7 +405,7 @@ magma_cube_tiny.collisionbox = {-0.2505, -0.01, -0.2505, 0.2505, 0.50, 0.2505, r
magma_cube_tiny.visual_size = {x=3.125, y=3.125}
magma_cube_tiny.walk_velocity = 1.02
magma_cube_tiny.run_velocity = 1.02
magma_cube_tiny.jump_height = 4
magma_cube_tiny.jump_height = 1
magma_cube_tiny.damage = 3
magma_cube_tiny.reach = 2
magma_cube_tiny.armor = 50

View File

@ -86,7 +86,7 @@ local spider = {
walk_velocity = 1.3,
run_velocity = 2.4,
jump = true,
jump_height = 4,
jump_height = 1,
view_range = 16,
floats = 1,
drops = {

View File

@ -2,16 +2,34 @@
local S = minetest.get_translator("mobs_mc")
--###################
--################### STALKER
--###################
-- foliage and grass palettes, loaded from mcl_maps
local palettes = {}
local function load_json_file(name)
local file = assert(io.open(name, "r"))
local data = minetest.parse_json(file:read("*all"))
file:close()
return data
end
local mapmodpath = minetest.get_modpath("mcl_maps")
if mapmodpath then
for k,v in pairs(load_json_file(mapmodpath .. "/palettes_grass.json")) do
palettes[k] = v
end
for k,v in pairs(load_json_file(mapmodpath .. "/palettes_foliage.json")) do
palettes[k] = v
end
for k,v in pairs(load_json_file(mapmodpath .. "/palettes_water.json")) do
palettes[k] = v
end
end
local function get_texture(self, prev)
local standing_on = self.standing_on
local texture
local texture_suff = ""
if standing_on and standing_on.groups.solid then
if standing_on and (standing_on.walkable or standing_on.groups.liquid) then
local tiles = standing_on.tiles
if tiles then
local tile = tiles[1]
@ -27,21 +45,47 @@ local function get_texture(self, prev)
if not color then
color = minetest.colorspec_to_colorstring(standing_on.color)
end
-- handle param2
if standing_on.palette and self.standing_on_node then
local param2
if standing_on.paramtype2 == "color" then
param2 = self.standing_on_node.param2
elseif standing_on.paramtype2 == "colorfacedir" then
param2 = math.floor(self.standing_on_node.param2 / 8)
elseif standing_on.paramtype2 == "colorwallmounted" then
param2 = math.floor(self.standing_on_node.param2 / 32)
elseif standing_on.paramtype2 == "color4dir" then
param2 = math.floor(self.standing_on_node.param2 / 64)
elseif standing_on.paramtype2 == "colordegrotate" then
param2 = math.floor(self.standing_on_node.param2 / 8)
end
local palette = palettes[standing_on.palette]
local oldcol = color
if param2 and palette then
local c = palette[param2 + 1]
if c then color = minetest.rgba(c[1], c[2], c[3], c[4]) end
end
end
if color then
texture_suff = "^[multiply:" .. color .. "^[hsl:0:0:20"
texture_suff = "^[multiply:" .. color .. "^[contrast:20:10" --"^[hsl:0:0:20"
end
end
end
if not texture or texture == "" then
if prev then return prev end
-- try to keep last texture when, e.g., falling
if prev and (not (not self.attack)) == (string.find(prev, "vl_mobs_starker_overlay_angry.png") ~= nil) then
return prev
end
texture = "vl_stalker_default.png"
end
texture = texture:gsub("([\\^:\\[])","\\%1") -- escape texture modifiers
texture = "([combine:16x24:0,0=(" .. texture .. "):0,16=(" .. texture ..")".. texture_suff
if self.attack then
texture = texture .. ")^vl_mobs_stalker_overlay_angry.png"
if texture_suff then texture = texture .. texture_suff end
else
texture = texture .. ")^vl_mobs_stalker_overlay.png"
texture = texture:gsub("([\\^:\\[])", "\\%1") -- escape texture modifiers
texture = "(vl_stalker_default.png^[combine:16x24:0,0=(" .. texture .. "):0,16=(" .. texture .. ")" .. texture_suff .. ")"
end
if self.attack then
texture = texture .. "^vl_mobs_stalker_overlay_angry.png"
else
texture = texture .. "^vl_mobs_stalker_overlay.png"
end
return texture
end

View File

@ -13,14 +13,6 @@ local anti_troll = minetest.settings:get_bool("wither_anti_troll_measures", fals
local WITHER_INIT_BOOM = 7
local WITHER_MELEE_COOLDOWN = 3
local function atan(x)
if not x or x ~= x then
return 0
else
return math.atan(x)
end
end
--###################
--################### WITHER
--###################
@ -96,7 +88,7 @@ mcl_mobs.register_mob("mobs_mc:wither", {
distance = 60,
},
jump = true,
jump_height = 10,
jump_height = 5,
fly = true,
makes_footstep_sound = false,
dogshoot_switch = 1, -- unused
@ -260,7 +252,7 @@ mcl_mobs.register_mob("mobs_mc:wither", {
z = p.z - s.z
}
local yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
local yaw = (atan2(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)

View File

@ -85,7 +85,7 @@ local zombie = {
fear_height = 4,
pathfinding = 1,
jump = true,
jump_height = 4,
jump_height = 1,
group_attack = { "mobs_mc:zombie", "mobs_mc:baby_zombie", "mobs_mc:husk", "mobs_mc:baby_husk" },
drops = drops_zombie,
animation = {