Compare commits

...

34 Commits

Author SHA1 Message Date
kno10 95bcf86cbd mob attack tweaks 2024-11-16 14:26:22 +01:00
kno10 1bc24999b4 fix child == true when child = 1 2024-11-16 14:26:21 +01:00
kno10 3adbd63fd6 More tweaks to pathfinding 2024-11-16 14:26:21 +01:00
kno10 76d67ba941 Make villagers hurry for long paths and night 2024-11-16 14:26:21 +01:00
kno10 304b83d7ac avoid trivial fences, open some fence gates 2024-11-16 14:26:21 +01:00
kno10 73a303ac83 Improve starting and end point of pathfinding. 2024-11-16 14:26:21 +01:00
kno10 f629a92e92 pathfinding improvements 2024-11-16 14:26:21 +01:00
kno10 1e5139a09d Improve danger avoidance code. 2024-11-16 14:26:21 +01:00
kno10 3ff229224b fix and optimize Fleckenstein 2024-11-16 14:26:21 +01:00
kno10 9b2d0d1b32 small code cleanups 2024-11-16 14:26:21 +01:00
kno10 de60029d1a also cleanup mount.lua 2024-11-16 14:26:21 +01:00
kno10 cf274dba4a some more cleanups, from code review 2024-11-16 14:26:21 +01:00
kno10 fa9bde370b code cleanups 2024-11-16 14:26:21 +01:00
kno10 935fc15770 movement improvements, door opening 2024-11-16 14:26:21 +01:00
kno10 63198c6f9b further movement tweaks 2024-11-16 14:26:21 +01:00
kno10 92aad6fd9a Movement and path finding improvements. 2024-11-16 14:26:21 +01:00
kno10 b58e5dedee Mob pushing improvements 2024-11-16 14:26:21 +01:00
kno10 71fe8bf146 add and use turn_by/turn_in_direction methods 2024-11-16 14:26:21 +01:00
kno10 cf73d64550 reduce code duplication, add mob:stand() 2024-11-16 14:26:21 +01:00
kno10 a0bce0d2fd cleanups 2024-11-16 14:26:21 +01:00
kno10 6f4a82cf9e fix delay=0 in combat code, tune turning parameters 2024-11-16 14:26:21 +01:00
kno10 995972bb40 More help getting out of water 2024-11-16 14:26:21 +01:00
kno10 f0add79a8d More movement code cleanups.
Closes #4506 #4502
2024-11-16 14:26:21 +01:00
kno10 d060ec65a9 More cleanup and improvements to movement code 2024-11-16 14:26:21 +01:00
kno10 4b47eb304e More movement code improvements. 2024-11-16 14:26:21 +01:00
kno10 6d7ceb37ce Try to reduce how much mobs fall off cliffs.
See #4464 and many more.
2024-11-16 14:26:21 +01:00
kno10 2d4f5a4518 Rewrite the head swivel code math 2024-11-16 14:25:06 +01:00
marro 4dc5d0939c Whitespace fix in translation (#4701)
Reviewed-on: VoxeLibre/VoxeLibre#4701
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
Co-authored-by: marro <marronclement0403@gmail.com>
Co-committed-by: marro <marronclement0403@gmail.com>
2024-11-11 03:49:43 +01:00
the-real-herowl 32b334322b Merge pull request 'Mobile fixes & improvements (cherry-pick from Mineclonia)' (#4685) from grorp/MineClone2:vl-mobile-fixes-and-improvements into master
Reviewed-on: VoxeLibre/VoxeLibre#4685
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
2024-11-11 01:44:27 +01:00
grorp 88c3c4558b Fix for VoxeLibre 2024-11-10 15:14:56 +01:00
grorp 3954acdfb7 Creative inventory: padding[-0.015,-0.015] on mobile
- less wasted screen space
- matches old layout
2024-11-10 15:14:56 +01:00
grorp 02b354f54a Avoid tab buttons going off-screen with high scaling values 2024-11-10 15:14:56 +01:00
grorp cb624fe1d9 Creative inventory: Make the whole tab button clickable
Previously, only the tab icon was clickable. Clicking next to the icon would
just close the inventory.
The icon is still kept clickable too since that gives a nicer press animation.
I didn't end up using image_button because that resulted in a different image
size and position, even with the exact same coordinates.
2024-11-10 15:14:56 +01:00
grorp bd9ab16762 Add touch_interaction to (cross)bow and spyglass 2024-11-10 15:14:56 +01:00
41 changed files with 1265 additions and 2076 deletions

View File

@ -1,29 +1,24 @@
local mob_class = mcl_mobs.mob_class local mob_class = mcl_mobs.mob_class
local mob_class_meta = {__index = mcl_mobs.mob_class} local mob_class_meta = {__index = mcl_mobs.mob_class}
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
-- API for Mobs Redo: VoxeLibre Edition
local PATHFINDING = "gowp" local PATHFINDING = "gowp"
local CRASH_WARN_FREQUENCY = 60 local CRASH_WARN_FREQUENCY = 60
local LIFETIMER_DISTANCE = 47 local LIFETIMER_DISTANCE = 47
local MAPGEN_LIMIT = mcl_vars.mapgen_limit
local MAPGEN_MOB_LIMIT = MAPGEN_LIMIT - 90
-- 30927 seems to be the edge of the world, so could be closer, but this is safer
-- Localize
local S = minetest.get_translator("mcl_mobs") local S = minetest.get_translator("mcl_mobs")
local DEVELOPMENT = minetest.settings:get_bool("mcl_development",false)
-- Invisibility mod check -- Invisibility mod check
mcl_mobs.invis = {} mcl_mobs.invis = {}
local remove_far = true local remove_far = true
local mobs_debug = minetest.settings:get_bool("mobs_debug", false) -- Shows helpful debug info above each mob local mobs_debug = minetest.settings:get_bool("mobs_debug", false) -- Shows helpful debug info above each mob
local spawn_logging = minetest.settings:get_bool("mcl_logging_mobs_spawn", false) local spawn_logging = minetest.settings:get_bool("mcl_logging_mobs_spawn", true)
local DEVELOPMENT = minetest.settings:get_bool("mcl_development", false)
local MAPGEN_LIMIT = mcl_vars.mapgen_limit
local MAPGEN_MOB_LIMIT = MAPGEN_LIMIT - 90
-- 30927 seems to be the edge of the world, so could be closer, but this is safer
-- Peaceful mode message so players will know there are no monsters -- Peaceful mode message so players will know there are no monsters
if minetest.settings:get_bool("only_peaceful_mobs", false) then if minetest.settings:get_bool("only_peaceful_mobs", false) then
@ -36,10 +31,7 @@ end
function mob_class:update_tag() --update nametag and/or the debug box function mob_class:update_tag() --update nametag and/or the debug box
local tag local tag
if mobs_debug then if mobs_debug then
local name = self.name local name = self.nametag ~= "" and self.nametag or self.name
if self.nametag and self.nametag ~= "" then
name = self.nametag
end
tag = "name = '"..tostring(name).."'\n".. tag = "name = '"..tostring(name).."'\n"..
"state = '"..tostring(self.state).."'\n".. "state = '"..tostring(self.state).."'\n"..
"order = '"..tostring(self.order).."'\n".. "order = '"..tostring(self.order).."'\n"..
@ -56,9 +48,7 @@ function mob_class:update_tag() --update nametag and/or the debug box
else else
tag = self.nametag tag = self.nametag
end end
self.object:set_properties({ self.object:set_properties({ nametag = tag })
nametag = tag,
})
end end
function mob_class:jock_to(mob, reletive_pos, rot) function mob_class:jock_to(mob, reletive_pos, rot)
@ -74,19 +64,15 @@ function mob_class:jock_to(mob, reletive_pos, rot)
end end
function mob_class:get_staticdata() function mob_class:get_staticdata()
for _,p in pairs(minetest.get_connected_players()) do for _,p in pairs(minetest.get_connected_players()) do
self:remove_particlespawners(p:get_player_name()) self:remove_particlespawners(p:get_player_name())
end end
-- remove mob when out of range unless tamed -- remove mob when out of range unless tamed
if remove_far if remove_far and self:despawn_allowed() and self.lifetimer <= 20 then
and self:despawn_allowed()
and self.lifetimer <= 20 then
if spawn_logging then if spawn_logging then
minetest.log("action", "[mcl_mobs] Mob "..tostring(self.name).." despawns at "..minetest.pos_to_string(vector.round(self.object:get_pos())) .. " - out of range") minetest.log("action", "[mcl_mobs] Mob "..tostring(self.name).." despawns at "..minetest.pos_to_string(vector.round(self.object:get_pos())) .. " - out of range")
end end
return "remove"-- nil return "remove"-- nil
end end
@ -95,17 +81,9 @@ function mob_class:get_staticdata()
self.state = "stand" self.state = "stand"
local tmp = {} local tmp = {}
for tag, stat in pairs(self) do for tag, stat in pairs(self) do
local t = type(stat) local t = type(stat)
if t ~= "function" and t ~= "nil" and t ~= "userdata" and tag ~= "_cmi_components" then tmp[tag] = self[tag] end
if t ~= "function"
and t ~= "nil"
and t ~= "userdata"
and tag ~= "_cmi_components" then
tmp[tag] = self[tag]
end
end end
tmp._mcl_potions = self._mcl_potions tmp._mcl_potions = self._mcl_potions
@ -120,10 +98,7 @@ function mob_class:get_staticdata()
end end
local function valid_texture(self, def_textures) local function valid_texture(self, def_textures)
if not self.base_texture then if not self.base_texture then return false end
return false
end
if self.texture_selected then if self.texture_selected then
if #def_textures < self.texture_selected then if #def_textures < self.texture_selected then
self.texture_selected = nil self.texture_selected = nil
@ -148,32 +123,18 @@ function mob_class:mob_activate(staticdata, def, dtime)
end end
local tmp = minetest.deserialize(staticdata) local tmp = minetest.deserialize(staticdata)
if tmp then if tmp then
-- Patch incorrectly converted mobs -- Patch incorrectly converted mobs
if tmp.base_mesh ~= minetest.registered_entities[self.name].mesh then if tmp.base_mesh ~= minetest.registered_entities[self.name].mesh then mcl_mobs.strip_staticdata(tmp) end
mcl_mobs.strip_staticdata(tmp) for _, stat in pairs(tmp) do self[_] = stat end
end
for _,stat in pairs(tmp) do
self[_] = stat
end
end end
--If textures in definition change, reload textures --If textures in definition change, reload textures
if not valid_texture(self, def.textures) then if not valid_texture(self, def.textures) then
-- compatiblity with old simple mobs textures -- compatiblity with old simple mobs textures
if type(def.textures[1]) == "string" then if type(def.textures[1]) == "string" then def.textures = {def.textures} end
def.textures = {def.textures}
end
if not self.texture_selected then
local c = 1
if #def.textures > c then c = #def.textures end
self.texture_selected = math.random(c)
end
self.texture_selected = self.texture_selected or math.random(#def.textures)
self.base_texture = def.textures[self.texture_selected] self.base_texture = def.textures[self.texture_selected]
self.base_mesh = def.mesh self.base_mesh = def.mesh
self.base_size = self.visual_size self.base_size = self.visual_size
@ -181,9 +142,7 @@ function mob_class:mob_activate(staticdata, def, dtime)
self.base_selbox = self.selectionbox self.base_selbox = self.selectionbox
end end
if not self.base_selbox then self.base_selbox = self.base_selbox or self.selectionbox or self.base_colbox
self.base_selbox = self.selectionbox or self.base_colbox
end
local textures = self.base_texture local textures = self.base_texture
local mesh = self.base_mesh local mesh = self.base_mesh
@ -191,26 +150,11 @@ function mob_class:mob_activate(staticdata, def, dtime)
local colbox = self.base_colbox local colbox = self.base_colbox
local selbox = self.base_selbox local selbox = self.base_selbox
if self.gotten == true if self.gotten and def.gotten_texture then textures = def.gotten_texture end
and def.gotten_texture then if self.gotten and def.gotten_mesh then mesh = def.gotten_mesh end
textures = def.gotten_texture if self.child then
end vis_size = { x = self.base_size.x * .5, y = self.base_size.y * .5 }
if def.child_texture then textures = def.child_texture[1] end
if self.gotten == true
and def.gotten_mesh then
mesh = def.gotten_mesh
end
if self.child == true then
vis_size = {
x = self.base_size.x * .5,
y = self.base_size.y * .5,
}
if def.child_texture then
textures = def.child_texture[1]
end
colbox = { colbox = {
self.base_colbox[1] * .5, self.base_colbox[1] * .5,
@ -230,16 +174,12 @@ function mob_class:mob_activate(staticdata, def, dtime)
} }
end end
if self.health == 0 then if self.health == 0 then self.health = math.random(self.hp_min, self.hp_max) end
self.health = math.random (self.hp_min, self.hp_max) if self.breath == nil then self.breath = self.breath_max end
end
if self.breath == nil then
self.breath = self.breath_max
end
self.path = {} self.path = {}
self.path.way = {} -- path to follow, table of positions self.path.way = {} -- path to follow, table of positions
self.path.lastpos = {x = 0, y = 0, z = 0} self.path.lastpos = vector.zero()
self.path.stuck = false self.path.stuck = false
self.path.following = false -- currently following path? self.path.following = false -- currently following path?
self.path.stuck_timer = 0 -- if stuck for too long search for path self.path.stuck_timer = 0 -- if stuck for too long search for path
@ -276,42 +216,22 @@ function mob_class:mob_activate(staticdata, def, dtime)
self.blinktimer = 0 self.blinktimer = 0
self.blinkstatus = false self.blinkstatus = false
if not self.nametag then self.nametag = self.nametag or def.nametag
self.nametag = def.nametag
end
if not self.custom_visual_size then
self.visual_size = nil
self.base_size = self.visual_size
if self.child then
self.visual_size = {
x = self.visual_size.x * 0.5,
y = self.visual_size.y * 0.5,
}
end
end
self.object:set_properties(self) self.object:set_properties(self)
self:set_yaw( (math.random(0, 360) - 180) / 180 * math.pi, 6) self:set_yaw(math.random() * math.pi * 2, 6)
self:update_tag() self:update_tag()
self._current_animation = nil self._current_animation = nil
self:set_animation( "stand") self:set_animation("stand")
if self.riden_by_jock then --- Keep this function before self:on_spawn()
if self.riden_by_jock then --- Keep this function before self.on_spawn() is run.
self.object:remove() self.object:remove()
return return
end end
if self.on_spawn and not self.on_spawn_run and self:on_spawn() then self.on_spawn_run = true end
if self.on_spawn and not self.on_spawn_run then if not self.wears_armor and self.armor_list then self.armor_list = nil end
if self.on_spawn(self) then
self.on_spawn_run = true
end
end
if not self.wears_armor and self.armor_list then
self.armor_list = nil
end
if not self._run_armor_init and self.wears_armor then if not self._run_armor_init and self.wears_armor then
self.armor_list={helmet="",chestplate="",boots="",leggings=""} self.armor_list={helmet="",chestplate="",boots="",leggings=""}
@ -319,15 +239,10 @@ function mob_class:mob_activate(staticdata, def, dtime)
self._run_armor_init = true self._run_armor_init = true
end end
if not self._mcl_potions then if not self._mcl_potions then self._mcl_potions = {} end
self._mcl_potions = {}
end
mcl_potions._load_entity_effects(self) mcl_potions._load_entity_effects(self)
if def.after_activate then def.after_activate(self, staticdata, def, dtime) end
if def.after_activate then
def.after_activate(self, staticdata, def, dtime)
end
end end
-- execute current state (stand, walk, run, attacks) -- execute current state (stand, walk, run, attacks)
@ -346,9 +261,7 @@ function mob_class:do_states(dtime, player_in_active_range)
if self.state == PATHFINDING then if self.state == PATHFINDING then
self:check_gowp(dtime) self:check_gowp(dtime)
elseif self.state == "attack" then elseif self.state == "attack" then
if self:do_states_attack(dtime) then if self:do_states_attack(dtime) then return true end
return true
end
else else
if mcl_util.check_dtime_timer(self, dtime, "onstep_dostates", 1) then if mcl_util.check_dtime_timer(self, dtime, "onstep_dostates", 1) then
if self.state == "stand" then if self.state == "stand" then
@ -364,34 +277,28 @@ end
function mob_class:outside_limits() function mob_class:outside_limits()
local pos = self.object:get_pos() local pos = self.object:get_pos()
if pos then if not pos then return end
local posx = math.abs(pos.x) local posx, posy, posz = math.abs(pos.x), math.abs(pos.y), math.abs(pos.z)
local posy = math.abs(pos.y) if posx > MAPGEN_MOB_LIMIT or posy > MAPGEN_MOB_LIMIT or posz > MAPGEN_MOB_LIMIT then
local posz = math.abs(pos.z) --minetest.log("action", "Getting close to limits of worldgen: " .. minetest.pos_to_string(pos))
if posx > MAPGEN_MOB_LIMIT or posy > MAPGEN_MOB_LIMIT or posz > MAPGEN_MOB_LIMIT then if posx > MAPGEN_LIMIT or posy > MAPGEN_LIMIT or posz > MAPGEN_LIMIT then
--minetest.log("action", "Getting close to limits of worldgen: " .. minetest.pos_to_string(pos)) minetest.log("action", "Warning mob past limits of worldgen: " .. minetest.pos_to_string(pos))
if posx > MAPGEN_LIMIT or posy > MAPGEN_LIMIT or posz > MAPGEN_LIMIT then else
minetest.log("action", "Warning mob past limits of worldgen: " .. minetest.pos_to_string(pos)) if self.state ~= "stand" then
else minetest.log("action", "Warning mob close to limits of worldgen: " .. minetest.pos_to_string(pos))
if self.state ~= "stand" then self.state = "stand"
minetest.log("action", "Warning mob close to limits of worldgen: " .. minetest.pos_to_string(pos)) self:set_animation("stand")
self.state = "stand" self.object:set_acceleration(vector.zero())
self:set_animation("stand") self.object:set_velocity(vector.zero())
self.object:set_acceleration(vector.zero())
self.object:set_velocity(vector.zero())
end
end end
return true
end end
return true
end end
end end
local function on_step_work(self, dtime, moveresult) local function on_step_work(self, dtime, moveresult)
local pos = self.object:get_pos() local pos = self.object:get_pos()
if not pos then return end if not pos then return end
if self:check_despawn(pos, dtime) then return true end if self:check_despawn(pos, dtime) then return true end
if self:outside_limits() then return end if self:outside_limits() then return end
@ -403,29 +310,20 @@ local function on_step_work(self, dtime, moveresult)
end end
if self:falling(pos, moveresult) then return end if self:falling(pos, moveresult) then return end
if self:step_damage (dtime, pos) then return end if self:step_damage(dtime, pos) then return end
if self.state == "die" then return end if self.state == "die" then return end
-- End: Death/damage processing -- End: Death/damage processing
local player_in_active_range = self:player_in_active_range() local player_in_active_range = self:player_in_active_range()
self:check_suspend(player_in_active_range) self:check_suspend(player_in_active_range)
self:check_water_flow() self:check_water_flow()
self._can_jump_cliff = not self._jumping_cliff and self:can_jump_cliff()
if not self._jumping_cliff then
self._can_jump_cliff = self:can_jump_cliff()
else
self._can_jump_cliff = false
end
self:flop() self:flop()
self:check_smooth_rotation(dtime) self:check_smooth_rotation(dtime)
if player_in_active_range then if player_in_active_range then
self:set_animation_speed() -- set animation speed relative to velocity self:set_animation_speed() -- set animation speed relative to velocity
self:check_head_swivel(dtime) self:check_head_swivel(dtime)
if mcl_util.check_dtime_timer(self, dtime, "onstep_engage", 0.2) then if mcl_util.check_dtime_timer(self, dtime, "onstep_engage", 0.2) then
@ -442,91 +340,68 @@ local function on_step_work(self, dtime, moveresult)
end end
if mcl_util.check_dtime_timer(self, dtime, "onstep_occassional", 1) then if mcl_util.check_dtime_timer(self, dtime, "onstep_occassional", 1) then
if player_in_active_range then if player_in_active_range then
self:check_item_pickup() self:check_item_pickup()
self:set_armor_texture() self:set_armor_texture()
self:step_opinion_sound(dtime) self:step_opinion_sound(dtime)
end end
self:check_breeding() self:check_breeding()
end end
self:check_aggro(dtime) self:check_aggro(dtime)
self:check_particlespawners(dtime) self:check_particlespawners(dtime)
if self.do_custom and self.do_custom(self, dtime) == false then return end if self.do_custom and self.do_custom(self, dtime) == false then return end
if self:do_states(dtime, player_in_active_range) then return end if self:do_states(dtime, player_in_active_range) then return end
if mobs_debug then self:update_tag() end if mobs_debug then self:update_tag() end
if not self.object:get_luaentity() then return false end
if not self.object:get_luaentity() then
return false
end
end end
local last_crash_warn_time = 0 local last_crash_warn_time = 0
local function log_error (stack_trace, info, info2) local function log_error(stack_trace, info, info2)
minetest.log("action", "--- Bug report start (please provide a few lines before this also for context) ---") minetest.log("action", "--- Bug report start (please provide a few lines before this also for context) ---")
minetest.log("action", "Error: " .. stack_trace) minetest.log("action", "Error: " .. stack_trace)
minetest.log("action", "Bug info: " .. info) minetest.log("action", "Bug info: " .. info)
if info2 then if info2 then minetest.log("action", "Bug info additional: " .. info2) end
minetest.log("action", "Bug info additional: " .. info2)
end
minetest.log("action", "--- Bug report end ---") minetest.log("action", "--- Bug report end ---")
end end
local function warn_user_error () local function warn_user_error ()
local current_time = os.time() local current_time = os.time()
local time_since_warning = current_time - last_crash_warn_time local time_since_warning = current_time - last_crash_warn_time
--minetest.log("previous_crash_time: " .. current_time) --minetest.log("previous_crash_time: " .. current_time)
--minetest.log("last_crash_time: " .. last_crash_warn_time) --minetest.log("last_crash_time: " .. last_crash_warn_time)
--minetest.log("time_since_warning: " .. time_since_warning) --minetest.log("time_since_warning: " .. time_since_warning)
if time_since_warning > CRASH_WARN_FREQUENCY then if time_since_warning > CRASH_WARN_FREQUENCY then
last_crash_warn_time = current_time last_crash_warn_time = current_time
minetest.log("A game crashing bug was prevented. Please provide debug.log information to VoxeLibre dev team for investigation. (Search for: --- Bug report start)") minetest.log("A game crashing bug was prevented. Please provide debug.log information to VoxeLibre dev team for investigation. (Search for: --- Bug report start)")
end end
end end
local on_step_error_handler = function () local on_step_error_handler = function()
warn_user_error () warn_user_error()
local info = debug.getinfo(1, "SnlufL") local info = debug.getinfo(1, "SnlufL")
log_error(tostring(debug.traceback()), dump(info)) log_error(tostring(debug.traceback()), dump(info))
end end
-- main mob function -- main mob function
function mob_class:on_step(dtime, moveresult) function mob_class:on_step(dtime, moveresult)
if not DEVELOPMENT then -- allow crash in development mode
-- Removed as bundled Lua (5.1 doesn't support xpcall) if DEVELOPMENT then return on_step_work(self, dtime, moveresult) end
--local status, retVal = xpcall(on_step_work, on_step_error_handler, self, dtime) -- Removed as bundled Lua (5.1 doesn't support xpcall)
local status, retVal = pcall(on_step_work, self, dtime, moveresult) --local status, retVal = xpcall(on_step_work, on_step_error_handler, self, dtime)
if status then local status, retVal = pcall(on_step_work, self, dtime, moveresult)
return retVal if status then return retVal end
else warn_user_error()
warn_user_error () local pos = self.object:get_pos()
local pos = self.object:get_pos() if pos then
if pos then local node = minetest.get_node(pos)
local node = minetest.get_node(pos) if node and node.name == "ignore" then minetest.log("warning", "Pos is ignored: " .. dump(pos)) end
if node and node.name == "ignore" then
minetest.log("warning", "Pos is ignored: " .. dump(pos))
end
end
log_error (dump(retVal), dump(pos), dump(self))
end
else
return on_step_work (self, dtime, moveresult)
end end
log_error(dump(retVal), dump(pos), dump(self))
end end
local timer = 0 local timer = 0
local function update_lifetimer(dtime) local function update_lifetimer(dtime)
timer = timer + dtime timer = timer + dtime
if timer < 1 then return end if timer < 1 then return end
@ -547,7 +422,6 @@ minetest.register_globalstep(function(dtime)
update_lifetimer(dtime) update_lifetimer(dtime)
end) end)
minetest.register_chatcommand("clearmobs", { minetest.register_chatcommand("clearmobs", {
privs = { maphack = true }, privs = { maphack = true },
params = "[all|monster|passive|<mob name> [<range>|nametagged|tamed]]", params = "[all|monster|passive|<mob name> [<range>|nametagged|tamed]]",
@ -560,11 +434,7 @@ minetest.register_chatcommand("clearmobs", {
S("Default usage. Clearing hostile mobs. For more options please type: /help clearmobs")) S("Default usage. Clearing hostile mobs. For more options please type: /help clearmobs"))
end end
local mob, unsafe = param:match("^([%w]+)[ ]?([%w%d]*)$") local mob, unsafe = param:match("^([%w]+)[ ]?([%w%d]*)$")
local all, nametagged, tamed = false, false, false
local all = false
local nametagged = false
local tamed = false
local mob_name, mob_type, range local mob_name, mob_type, range
-- Param 1 resolve -- Param 1 resolve
@ -578,12 +448,7 @@ minetest.register_chatcommand("clearmobs", {
end end
--minetest.log ("mob: [" .. mob .. "]") --minetest.log ("mob: [" .. mob .. "]")
else else
--minetest.log("No valid first param") if default then mob_type = "monster" end
if default then
--minetest.log("Use default")
mob_type = "monster"
end
--return
end end
-- Param 2 resolve -- Param 2 resolve
@ -600,7 +465,6 @@ minetest.register_chatcommand("clearmobs", {
end end
local p = minetest.get_player_by_name(player) local p = minetest.get_player_by_name(player)
for _,o in pairs(minetest.luaentities) do for _,o in pairs(minetest.luaentities) do
if o and o.is_mob then if o and o.is_mob then
local mob_match = false local mob_match = false
@ -609,7 +473,6 @@ minetest.register_chatcommand("clearmobs", {
--minetest.log("Match - All mobs specified") --minetest.log("Match - All mobs specified")
mob_match = true mob_match = true
elseif mob_type then elseif mob_type then
--minetest.log("Match - o.type: ".. tostring(o.type)) --minetest.log("Match - o.type: ".. tostring(o.type))
--minetest.log("mob_type: ".. tostring(mob_type)) --minetest.log("mob_type: ".. tostring(mob_type))
if mob_type == "monster" and o.type == mob_type then if mob_type == "monster" and o.type == mob_type then
@ -621,7 +484,6 @@ minetest.register_chatcommand("clearmobs", {
else else
--minetest.log("No match for type.") --minetest.log("No match for type.")
end end
elseif mob_name and (o.name == mob_name or string.find(o.name, mob_name)) then elseif mob_name and (o.name == mob_name or string.find(o.name, mob_name)) then
--minetest.log("Match - mob_name = ".. tostring(o.name)) --minetest.log("Match - mob_name = ".. tostring(o.name))
mob_match = true mob_match = true
@ -632,35 +494,16 @@ minetest.register_chatcommand("clearmobs", {
end end
if mob_match then if mob_match then
local in_range = true local in_range = (not range or range <= 0) or vector.distance(p:get_pos(), o.object:get_pos()) <= range
if (not range or range <= 0 ) then
in_range = true
else
if ( vector.distance(p:get_pos(),o.object:get_pos()) <= range ) then
in_range = true
else
--minetest.log("Out of range")
in_range = false
end
end
--minetest.log("o.nametag: ".. tostring(o.nametag))
if nametagged then if nametagged then
if o.nametag then if o.nametag then o.object:remove() end
--minetest.log("Namedtagged and it has a name tag. Kill it")
o.object:remove()
end
elseif tamed then elseif tamed then
if o.tamed then if o.tamed then o.object:remove() end
--minetest.log("Tamed. Kill it")
o.object:remove()
end
elseif in_range and (not o.nametag or o.nametag == "") and not o.tamed then elseif in_range and (not o.nametag or o.nametag == "") and not o.tamed then
--minetest.log("No nametag or tamed. Kill it")
o.object:remove() o.object:remove()
end end
end end
end end
end end
end}) end
})

View File

@ -63,7 +63,7 @@ function mob_class:feed_tame(clicker, feed_count, breed, tame, notake)
-- make children grow quicker -- make children grow quicker
if not consume_food and self.child == true then if not consume_food and self.child then
consume_food = true consume_food = true
-- deduct 10% of the time to adulthood -- deduct 10% of the time to adulthood
self.hornytimer = self.hornytimer + ((CHILD_GROW_TIME - self.hornytimer) * 0.1) self.hornytimer = self.hornytimer + ((CHILD_GROW_TIME - self.hornytimer) * 0.1)
@ -158,7 +158,7 @@ function mob_class:check_breeding()
--mcl_log("In breed function") --mcl_log("In breed function")
-- child takes a long time before growing into adult -- child takes a long time before growing into adult
if self.child == true then if self.child then
-- When a child, hornytimer is used to count age until adulthood -- When a child, hornytimer is used to count age until adulthood
self.hornytimer = self.hornytimer + 1 self.hornytimer = self.hornytimer + 1

View File

@ -11,39 +11,30 @@ local stuck_path_timeout = 10 -- how long will mob follow path before giving up
local enable_pathfinding = true local enable_pathfinding = true
local TIME_TO_FORGET_TARGET = 15 local TIME_TO_FORGET_TARGET = 15
local PI = math.pi
local atann = math.atan local HALFPI = PI * 0.5
local function atan(x) local random = math.random
if not x or x ~= x then local min = math.min
return 0 local floor = math.floor
else local ceil = math.ceil
return atann(x) local abs = math.abs
end local cos = math.cos
end local sin = math.sin
local atan2 = math.atan2
local vector_offset = vector.offset
local vector_new = vector.new
local vector_copy = vector.copy
local vector_distance = vector.distance
-- check if daytime and also if mob is docile during daylight hours -- check if daytime and also if mob is docile during daylight hours
function mob_class:day_docile() function mob_class:day_docile()
if self.docile_by_day == false then return self.docile_by_day == true and self.time_of_day > 0.2 and self.time_of_day < 0.8
return false
elseif self.docile_by_day == true
and self.time_of_day > 0.2
and self.time_of_day < 0.8 then
return true
end
end end
-- get this mob to attack the object -- get this mob to attack the object
function mob_class:do_attack(object) function mob_class:do_attack(object)
if self.state == "attack" or self.state == "die" then return end
if self.state == "attack" or self.state == "die" then if object:is_player() and not minetest.settings:get_bool("enable_damage") then return end
return
end
if object:is_player() and not minetest.settings:get_bool("enable_damage") then
return
end
self.attack = object self.attack = object
self.state = "attack" self.state = "attack"
@ -55,21 +46,18 @@ function mob_class:do_attack(object)
end end
-- blast damage to entities nearby -- blast damage to entities nearby
local function entity_physics(pos,radius) local function entity_physics(pos, radius)
radius = radius * 2 radius = radius * 2
local objs = minetest.get_objects_inside_radius(pos, radius) local objs = minetest.get_objects_inside_radius(pos, radius)
local obj_pos, dist local obj_pos, dist
for n = 1, #objs do for n = 1, #objs do
obj_pos = objs[n]:get_pos() obj_pos = objs[n]:get_pos()
dist = vector.distance(pos, obj_pos) dist = vector_distance(pos, obj_pos)
if dist < 1 then dist = 1 end if dist < 1 then dist = 1 end
local damage = math.floor((4 / dist) * radius) local damage = floor((4 / dist) * radius)
local ent = objs[n]:get_luaentity() local ent = objs[n]:get_luaentity()
-- punches work on entities AND players -- punches work on entities AND players
@ -87,75 +75,57 @@ local height_switcher = false
-- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3 -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
function mob_class:smart_mobs(s, p, dist, dtime) function mob_class:smart_mobs(s, p, dist, dtime)
local s1 = self.path.lastpos local s1 = self.path.lastpos
local target_pos = self.attack:get_pos() local target_pos = self.attack:get_pos()
-- is it becoming stuck? -- is it becoming stuck?
if math.abs(s1.x - s.x) + math.abs(s1.z - s.z) < .5 then if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
self.path.stuck_timer = self.path.stuck_timer + dtime self.path.stuck_timer = self.path.stuck_timer + dtime
else else
self.path.stuck_timer = 0 self.path.stuck_timer = 0
end end
self.path.lastpos = {x = s.x, y = s.y, z = s.z} self.path.lastpos = vector_copy(s)
local use_pathfind = false local use_pathfind = false
local has_lineofsight = minetest.line_of_sight( local has_lineofsight = minetest.line_of_sight(vector_offset(s, 0, .5, 0), vector_offset(target_pos, 0, 1.5, 0), .2)
{x = s.x, y = (s.y) + .5, z = s.z},
{x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2)
-- im stuck, search for path -- im stuck, search for path
if not has_lineofsight then if not has_lineofsight then
if los_switcher == true then if los_switcher == true then
use_pathfind = true use_pathfind = true
los_switcher = false los_switcher = false
end -- cannot see target! end -- cannot see target!
else else
if los_switcher == false then if los_switcher == false then
los_switcher = true los_switcher = true
use_pathfind = false use_pathfind = false
minetest.after(1, function(self) minetest.after(1, function(self)
if not self.object:get_luaentity() then if not self.object:get_luaentity() then return end
return
end
if has_lineofsight then self.path.following = false end if has_lineofsight then self.path.following = false end
end, self) end, self)
end -- can see target! end -- can see target!
end end
if (self.path.stuck_timer > stuck_timeout and not self.path.following) then if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
use_pathfind = true use_pathfind = true
self.path.stuck_timer = 0 self.path.stuck_timer = 0
minetest.after(1, function(self) minetest.after(1, function(self)
if not self.object:get_luaentity() then if not self.object:get_luaentity() then return end
return
end
if has_lineofsight then self.path.following = false end if has_lineofsight then self.path.following = false end
end, self) end, self)
end end
if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
use_pathfind = true use_pathfind = true
self.path.stuck_timer = 0 self.path.stuck_timer = 0
minetest.after(1, function(self) minetest.after(1, function(self)
if not self.object:get_luaentity() then if not self.object:get_luaentity() then return end
return
end
if has_lineofsight then self.path.following = false end if has_lineofsight then self.path.following = false end
end, self) end, self)
end end
if math.abs(vector.subtract(s,target_pos).y) > self.stepheight then if abs(s.y - target_pos.y) > self.stepheight then
if height_switcher then if height_switcher then
use_pathfind = true use_pathfind = true
height_switcher = false height_switcher = false
@ -174,28 +144,21 @@ function mob_class:smart_mobs(s, p, dist, dtime)
-- round position to center of node to avoid stuck in walls -- round position to center of node to avoid stuck in walls
-- also adjust height for player models! -- also adjust height for player models!
s.x = math.floor(s.x + 0.5) s.x, s.z = floor(s.x + 0.5), floor(s.z + 0.5)
s.z = math.floor(s.z + 0.5)
local ssight, sground = minetest.line_of_sight(s, { local ssight, sground = minetest.line_of_sight(s, vector_offset(s, 0, -4, 0), 1)
x = s.x, y = s.y - 4, z = s.z}, 1)
-- determine node above ground -- determine node above ground
if not ssight then if not ssight then s.y = sground.y + 1 end
s.y = sground.y + 1
end
local p1 = self.attack:get_pos() local p1 = self.attack:get_pos()
p1 = vector_new(floor(p1.x + 0.5), floor(p1.y + 0.5), floor(p1.z + 0.5))
p1.x = math.floor(p1.x + 0.5)
p1.y = math.floor(p1.y + 0.5)
p1.z = math.floor(p1.z + 0.5)
local dropheight = 12 local dropheight = 12
if self.fear_height ~= 0 then dropheight = self.fear_height end if self.fear_height ~= 0 then dropheight = self.fear_height end
local jumpheight = 0 local jumpheight = 0
if self.jump and self.jump_height >= 4 then if self.jump and self.jump_height >= 4 then
jumpheight = math.min(math.ceil(self.jump_height / 4), 4) jumpheight = min(ceil(self.jump_height * 0.25), 4)
elseif self.stepheight > 0.5 then elseif self.stepheight > 0.5 then
jumpheight = 1 jumpheight = 1
end end
@ -206,34 +169,27 @@ function mob_class:smart_mobs(s, p, dist, dtime)
-- no path found, try something else -- no path found, try something else
if not self.path.way then if not self.path.way then
self.path.following = false self.path.following = false
-- lets make way by digging/building if not accessible -- lets make way by digging/building if not accessible
if self.pathfinding == 2 and mobs_griefing then if self.pathfinding == 2 and mobs_griefing then
-- is player higher than mob? -- is player higher than mob?
if s.y < p1.y then if s.y < p1.y then
-- build upwards -- build upwards
if not minetest.is_protected(s, "") then if not minetest.is_protected(s, "") then
local ndef1 = minetest.registered_nodes[self.standing_in] local ndef1 = minetest.registered_nodes[self.standing_in]
if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
minetest.set_node(s, {name = mcl_mobs.fallback_node}) minetest.set_node(s, {name = mcl_mobs.fallback_node})
end end
end end
local sheight = math.ceil(self.collisionbox[5]) + 1 local sheight = ceil(self.collisionbox[5]) + 1
-- assume mob is 2 blocks high so it digs above its head -- assume mob is 2 blocks high so it digs above its head
s.y = s.y + sheight s.y = s.y + sheight
-- remove one block above to make room to jump -- remove one block above to make room to jump
if not minetest.is_protected(s, "") then if not minetest.is_protected(s, "") then
local node1 = node_ok(s, "air").name local node1 = node_ok(s, "air").name
local ndef1 = minetest.registered_nodes[node1] local ndef1 = minetest.registered_nodes[node1]
@ -243,32 +199,21 @@ function mob_class:smart_mobs(s, p, dist, dtime)
and not ndef1.groups.level and not ndef1.groups.level
and not ndef1.groups.unbreakable and not ndef1.groups.unbreakable
and not ndef1.groups.liquid then and not ndef1.groups.liquid then
minetest.set_node(s, {name = "air"}) minetest.set_node(s, {name = "air"})
minetest.add_item(s, ItemStack(node1)) minetest.add_item(s, ItemStack(node1))
end end
end end
s.y = s.y - sheight s.y = s.y - sheight
self.object:set_pos({x = s.x, y = s.y + 2, z = s.z}) self.object:set_pos(vector_offset(s, 0, 2, 0))
else -- dig 2 blocks to make door toward player direction else -- dig 2 blocks to make door toward player direction
local yaw1 = self.object:get_yaw() + HALFPI
local yaw1 = self.object:get_yaw() + math.pi / 2 local p1 = vector_offset(s, cos(yaw1), 0, sin(yaw1))
local p1 = {
x = s.x + math.cos(yaw1),
y = s.y,
z = s.z + math.sin(yaw1)
}
if not minetest.is_protected(p1, "") then if not minetest.is_protected(p1, "") then
local node1 = node_ok(p1, "air").name local node1 = node_ok(p1, "air").name
local ndef1 = minetest.registered_nodes[node1] local ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air" and node1 ~= "ignore"
if node1 ~= "air"
and node1 ~= "ignore"
and ndef1 and ndef1
and not ndef1.groups.level and not ndef1.groups.level
and not ndef1.groups.unbreakable and not ndef1.groups.unbreakable
@ -282,8 +227,7 @@ function mob_class:smart_mobs(s, p, dist, dtime)
node1 = node_ok(p1, "air").name node1 = node_ok(p1, "air").name
ndef1 = minetest.registered_nodes[node1] ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air" if node1 ~= "air" and node1 ~= "ignore"
and node1 ~= "ignore"
and ndef1 and ndef1
and not ndef1.groups.level and not ndef1.groups.level
and not ndef1.groups.unbreakable and not ndef1.groups.unbreakable
@ -317,28 +261,19 @@ end
-- specific attacks -- specific attacks
local specific_attack = function(list, what) local specific_attack = function(list, what)
-- no list so attack default (player, animals etc.) -- no list so attack default (player, animals etc.)
if list == nil then if list == nil then return true end
return true
end
-- found entity on list to attack? -- found entity on list to attack?
for no = 1, #list do for no = 1, #list do
if list[no] == what then return true end
if list[no] == what then
return true
end
end end
return false return false
end end
-- find someone to attack -- find someone to attack
function mob_class:monster_attack() function mob_class:monster_attack()
if not damage_enabled or self.passive ~= false or self.state == "attack" or self:day_docile() then if not damage_enabled or self.passive ~= false or self.state == "attack" or self:day_docile() then return end
return
end
local s = self.object:get_pos() local s = self.object:get_pos()
local p, sp, dist local p, sp, dist
@ -392,7 +327,7 @@ function mob_class:monster_attack()
p = player:get_pos() p = player:get_pos()
sp = s sp = s
dist = vector.distance(p, s) dist = vector_distance(p, s)
-- aim higher to make looking up hills more realistic -- aim higher to make looking up hills more realistic
p.y = p.y + 1 p.y = p.y + 1
@ -414,7 +349,7 @@ function mob_class:monster_attack()
end end
end end
if not min_player and #blacklist_attack > 0 then if not min_player and #blacklist_attack > 0 then
min_player=blacklist_attack[math.random(#blacklist_attack)] min_player=blacklist_attack[random(#blacklist_attack)]
end end
-- attack player -- attack player
if min_player then if min_player then
@ -425,7 +360,6 @@ end
-- npc, find closest monster to attack -- npc, find closest monster to attack
function mob_class:npc_attack() function mob_class:npc_attack()
if self.type ~= "npc" if self.type ~= "npc"
or not self.attacks_monsters or not self.attacks_monsters
or self.state == "attack" then or self.state == "attack" then
@ -444,7 +378,7 @@ function mob_class:npc_attack()
p = obj.object:get_pos() p = obj.object:get_pos()
sp = s sp = s
local dist = vector.distance(p, s) local dist = vector_distance(p, s)
-- aim higher to make looking up hills more realistic -- aim higher to make looking up hills more realistic
p.y = p.y + 1 p.y = p.y + 1
@ -466,19 +400,13 @@ end
-- dogshoot attack switch and counter function -- dogshoot attack switch and counter function
function mob_class:dogswitch(dtime) function mob_class:dogswitch(dtime)
-- switch mode not activated -- switch mode not activated
if not self.dogshoot_switch if not self.dogshoot_switch or not dtime then return 0 end
or not dtime then
return 0
end
self.dogshoot_count = self.dogshoot_count + dtime self.dogshoot_count = self.dogshoot_count + dtime
if (self.dogshoot_switch == 1 if (self.dogshoot_switch == 1 and self.dogshoot_count > self.dogshoot_count_max)
and self.dogshoot_count > self.dogshoot_count_max) or (self.dogshoot_switch == 2 and self.dogshoot_count > self.dogshoot_count2_max) then
or (self.dogshoot_switch == 2
and self.dogshoot_count > self.dogshoot_count2_max) then
self.dogshoot_count = 0 self.dogshoot_count = 0
@ -525,13 +453,9 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
if is_player then if is_player then
-- is mob out of reach? -- is mob out of reach?
if vector.distance(mob_pos, player_pos) > 3 then if vector_distance(mob_pos, player_pos) > 3 then return end
return
end
-- is mob protected? -- is mob protected?
if self.protected and minetest.is_protected(mob_pos, hitter:get_player_name()) then if self.protected and minetest.is_protected(mob_pos, hitter:get_player_name()) then return end
return
end
mcl_potions.update_haste_and_fatigue(hitter) mcl_potions.update_haste_and_fatigue(hitter)
end end
@ -540,13 +464,10 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local time_diff = time_now - self.invul_timestamp local time_diff = time_now - self.invul_timestamp
-- check for invulnerability time in microseconds (0.5 second) -- check for invulnerability time in microseconds (0.5 second)
if time_diff <= 500000 and time_diff >= 0 then if time_diff <= 500000 and time_diff >= 0 then return end
return
end
-- custom punch function -- custom punch function
if self.do_punch then if self.do_punch then
-- when false skip going any further -- when false skip going any further
if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
return return
@ -562,15 +483,11 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local time_now = minetest.get_us_time() local time_now = minetest.get_us_time()
if is_player then if is_player then
if minetest.is_creative_enabled(hitter:get_player_name()) then if minetest.is_creative_enabled(hitter:get_player_name()) then self.health = 0 end
self.health = 0
end
-- set/update 'drop xp' timestamp if hitted by player -- set/update 'drop xp' timestamp if hitted by player
self.xp_timestamp = time_now self.xp_timestamp = time_now
end end
-- punch interval -- punch interval
local weapon = hitter:get_wielded_item() local weapon = hitter:get_wielded_item()
local punch_interval = 1.4 local punch_interval = 1.4
@ -591,18 +508,10 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
end end
for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do for group,_ in pairs((tool_capabilities.damage_groups or {}) ) do
tmp = tflp / (tool_capabilities.full_punch_interval or 1.4) tmp = tflp / (tool_capabilities.full_punch_interval or 1.4)
tmp = tmp < 0 and 0 or (tmp > 1 and 1 or tmp)
if tmp < 0 then damage = damage + (tool_capabilities.damage_groups[group] or 0) * tmp * ((armor[group] or 0) / 100.0)
tmp = 0.0
elseif tmp > 1 then
tmp = 1.0
end
damage = damage + (tool_capabilities.damage_groups[group] or 0)
* tmp * ((armor[group] or 0) / 100.0)
end end
-- strength and weakness effects -- strength and weakness effects
@ -621,9 +530,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
-- check for tool immunity or special damage -- check for tool immunity or special damage
for n = 1, #self.immune_to do for n = 1, #self.immune_to do
if self.immune_to[n][1] == weapon:get_name() then if self.immune_to[n][1] == weapon:get_name() then
damage = self.immune_to[n][2] or 0 damage = self.immune_to[n][2] or 0
break break
end end
@ -631,7 +538,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
-- healing -- healing
if damage <= -1 then if damage <= -1 then
self.health = self.health - math.floor(damage) self.health = self.health - floor(damage)
return return
end end
@ -651,7 +558,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local weapon = hitter:get_wielded_item(player) local weapon = hitter:get_wielded_item(player)
local def = weapon:get_definition() local def = weapon:get_definition()
if def.tool_capabilities and def.tool_capabilities.punch_attack_uses then if def.tool_capabilities and def.tool_capabilities.punch_attack_uses then
local wear = math.floor(65535/tool_capabilities.punch_attack_uses) local wear = floor(65535/tool_capabilities.punch_attack_uses)
weapon:add_wear(wear) weapon:add_wear(wear)
tt.reload_itemstack_description(weapon) -- update tooltip tt.reload_itemstack_description(weapon) -- update tooltip
hitter:set_wielded_item(weapon) hitter:set_wielded_item(weapon)
@ -662,14 +569,12 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local die = false local die = false
if damage >= 0 then if damage >= 0 then
-- only play hit sound and show blood effects if damage is 1 or over; lower to 0.1 to ensure armor works appropriately. -- only play hit sound and show blood effects if damage is 1 or over; lower to 0.1 to ensure armor works appropriately.
if damage >= 0.1 then if damage >= 0.1 then
-- weapon sounds -- weapon sounds
if weapon:get_definition().sounds ~= nil then if weapon:get_definition().sounds ~= nil then
local s = random(0, #weapon:get_definition().sounds)
local s = math.random(0, #weapon:get_definition().sounds)
minetest.sound_play(weapon:get_definition().sounds[s], { minetest.sound_play(weapon:get_definition().sounds[s], {
object = self.object, --hitter, object = self.object, --hitter,
@ -696,27 +601,20 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
end end
end end
-- knock back effect (only on full punch) -- knock back effect (only on full punch)
if self.knock_back if self.knock_back and tflp >= punch_interval then
and tflp >= punch_interval then
-- direction error check -- direction error check
dir = dir or {x = 0, y = 0, z = 0} dir = dir or vector_zero()
local v = self.object:get_velocity() local v = self.object:get_velocity()
if not v then return end if not v then return end
local r = 1.4 - math.min(punch_interval, 1.4) local r = 1.4 - min(punch_interval, 1.4)
local kb = r * (math.abs(v.x)+math.abs(v.z)) local kb = r * (abs(v.x)+abs(v.z))
local up = 2.625 local up = 2.625
if die==true then if die then kb = kb * 1.25 end
kb=kb*1.25
end
-- if already in air then dont go up anymore when hit -- if already in air then dont go up anymore when hit
if math.abs(v.y) > 0.1 if abs(v.y) > 0.1 or self.fly then up = 0 end
or self.fly then
up = 0
end
-- check if tool already has specific knockback value -- check if tool already has specific knockback value
if tool_capabilities.damage_groups["knockback"] then if tool_capabilities.damage_groups["knockback"] then
@ -725,21 +623,17 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
kb = kb * 1.25 kb = kb * 1.25
end end
local luaentity = hitter and hitter:get_luaentity()
local luaentity
if hitter then
luaentity = hitter:get_luaentity()
end
if hitter and is_player then if hitter and is_player then
local wielditem = hitter:get_wielded_item() local wielditem = hitter:get_wielded_item()
kb = kb + 9 * mcl_enchanting.get_enchantment(wielditem, "knockback") kb = kb + 9 * mcl_enchanting.get_enchantment(wielditem, "knockback")
-- add player velocity to mob knockback -- add player velocity to mob knockback
local hv = hitter:get_velocity() local hv = hitter:get_velocity()
local dir_dot = (hv.x * dir.x) + (hv.z * dir.z) local dir_dot = (hv.x * dir.x) + (hv.z * dir.z)
local player_mag = math.sqrt((hv.x * hv.x) + (hv.z * hv.z)) local player_mag = ((hv.x * hv.x) + (hv.z * hv.z))^0.5
local mob_mag = math.sqrt((v.x * v.x) + (v.z * v.z)) local mob_mag = ((v.x * v.x) + (v.z * v.z))^0.5
if dir_dot > 0 and mob_mag <= player_mag * 0.625 then if dir_dot > 0 and mob_mag <= player_mag * 0.625 then
kb = kb + ((math.abs(hv.x) + math.abs(hv.z)) * r) kb = kb + (abs(hv.x) + abs(hv.z)) * r
end end
elseif luaentity and luaentity._knockback and die == false then elseif luaentity and luaentity._knockback and die == false then
kb = kb + luaentity._knockback kb = kb + luaentity._knockback
@ -747,12 +641,12 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
kb = kb + luaentity._knockback * 0.25 kb = kb + luaentity._knockback * 0.25
end end
self._kb_turn = true self._kb_turn = true
self._turn_to=self.object:get_yaw()-1.57 self:turn_by(HALFPI, .1) -- knockback turn
self.frame_speed_multiplier=2.3 self.frame_speed_multiplier=2.3
if self.animation.run_end then if self.animation.run_end then
self:set_animation( "run") self:set_animation("run")
elseif self.animation.walk_end then elseif self.animation.walk_end then
self:set_animation( "walk") self:set_animation("walk")
end end
minetest.after(0.2, function() minetest.after(0.2, function()
if self and self.object then if self and self.object then
@ -760,11 +654,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
self._kb_turn = false self._kb_turn = false
end end
end) end)
self.object:add_velocity({ self.object:add_velocity(vector_new(dir.x * kb, up*2, dir.z * kb ))
x = dir.x * kb,
y = up*2,
z = dir.z * kb
})
self.pause_timer = 0.25 self.pause_timer = 0.25
end end
@ -772,12 +662,15 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
-- if skittish then run away -- if skittish then run away
if hitter and is_player and hitter:get_pos() and not die and self.runaway == true and self.state ~= "flop" then if hitter and is_player and hitter:get_pos() and not die and self.runaway == true and self.state ~= "flop" then
local hp, sp = hitter:get_pos(), self.object:get_pos()
local yaw = self:set_yaw( minetest.dir_to_yaw(vector.direction(hitter:get_pos(), self.object:get_pos()))) self:turn_in_direction(sp.x - hp.x, sp.z - hp.z, 1)
minetest.after(0.2,function() minetest.after(0.2,function()
if self and self.object and self.object:get_pos() and hitter and is_player and hitter:get_pos() then if self and self.object and hitter and is_player then
yaw = self:set_yaw( minetest.dir_to_yaw(vector.direction(hitter:get_pos(), self.object:get_pos()))) local hp, sp = hitter:get_pos(), self.object:get_pos()
self:set_velocity( self.run_velocity) if hp and sp then
self:turn_in_direction(sp.x - hp.x, sp.z - hp.z, 1)
self:set_velocity(self.run_velocity)
end
end end
end) end)
self.state = "runaway" self.state = "runaway"
@ -808,7 +701,6 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local obj = nil local obj = nil
for n = 1, #objs do for n = 1, #objs do
obj = objs[n]:get_luaentity() obj = objs[n]:get_luaentity()
if obj then if obj then
@ -840,11 +732,7 @@ end
function mob_class:check_aggro(dtime) function mob_class:check_aggro(dtime)
if not self._aggro or not self.attack then return end if not self._aggro or not self.attack then return end
if not self._check_aggro_timer then self._check_aggro_timer = 0 end
if not self._check_aggro_timer then
self._check_aggro_timer = 0
end
if self._check_aggro_timer > 5 then if self._check_aggro_timer > 5 then
self._check_aggro_timer = 0 self._check_aggro_timer = 0
@ -852,7 +740,7 @@ function mob_class:check_aggro(dtime)
-- TODO consider removing this in favour of what is done in do_states_attack -- TODO consider removing this in favour of what is done in do_states_attack
-- Attack is dropped in do_states_attack if out of range, so won't even trigger here -- Attack is dropped in do_states_attack if out of range, so won't even trigger here
-- I do not think this code does anything. Are mobs still loaded in at 128? -- I do not think this code does anything. Are mobs still loaded in at 128?
if not self.attack:get_pos() or vector.distance(self.attack:get_pos(),self.object:get_pos()) > 128 then if not self.attack:get_pos() or vector_distance(self.attack:get_pos(),self.object:get_pos()) > 128 then
self._aggro = nil self._aggro = nil
self.attack = nil self.attack = nil
self.state = "stand" self.state = "stand"
@ -878,17 +766,14 @@ local function clear_aggro(self)
self.path.way = nil self.path.way = nil
end end
function mob_class:do_states_attack (dtime) function mob_class:do_states_attack(dtime)
self.timer = self.timer + dtime self.timer = self.timer + dtime
if self.timer > 100 then if self.timer > 100 then self.timer = 1 end
self.timer = 1
end
local s = self.object:get_pos() local s = self.object:get_pos()
if not s then return end if not s then return end
local p = self.attack:get_pos() or s local p = self.attack:get_pos() or s
local yaw = self.object:get_yaw() or 0 local yaw = self.object:get_yaw() or 0
-- stop attacking if player invisible or out of range -- stop attacking if player invisible or out of range
@ -920,20 +805,15 @@ function mob_class:do_states_attack (dtime)
end end
-- calculate distance from mob and enemy -- calculate distance from mob and enemy
local dist = vector.distance(p, s) local dist = vector_distance(p, s)
if self.attack_type == "explode" then if self.attack_type == "explode" then
if target_line_of_sight then if target_line_of_sight then
local vec = { x = p.x - s.x, z = p.z - s.z } self:turn_in_direction(p.x - s.x, p.z - s.z, 1)
yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
if p.x > s.x then yaw = yaw +math.pi end
yaw = self:set_yaw( yaw, 0, dtime)
end end
local node_break_radius = self.explosion_radius or 1 local node_break_radius = self.explosion_radius or 1
local entity_damage_radius = self.explosion_damage_radius local entity_damage_radius = self.explosion_damage_radius or (node_break_radius * 2)
or (node_break_radius * 2)
-- start timer when in reach and line of sight -- start timer when in reach and line of sight
if not self.v_start and dist <= self.reach and target_line_of_sight then if not self.v_start and dist <= self.reach and target_line_of_sight then
@ -960,9 +840,9 @@ function mob_class:do_states_attack (dtime)
end end
if self.animation and self.animation.run_start then if self.animation and self.animation.run_start then
self:set_animation( "run") self:set_animation("run")
else else
self:set_animation( "walk") self:set_animation("walk")
end end
if self.v_start then if self.v_start then
@ -1005,92 +885,50 @@ function mob_class:do_states_attack (dtime)
or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2) and (dist >= self.avoid_distance or not self.shooter_avoid_enemy) or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2) and (dist >= self.avoid_distance or not self.shooter_avoid_enemy)
or (self.attack_type == "dogshoot" and dist <= self.reach and self:dogswitch() == 0) then or (self.attack_type == "dogshoot" and dist <= self.reach and self:dogswitch() == 0) then
if self.fly if self.fly and dist > self.reach then
and dist > self.reach then local p1, p2 = s, p
local me_y, p_y = floor(p1.y), floor(p2.y + 1)
local p1 = s
local me_y = math.floor(p1.y)
local p2 = p
local p_y = math.floor(p2.y + 1)
local v = self.object:get_velocity() local v = self.object:get_velocity()
if self:flight_check( s) then if self:flight_check( s) then
if me_y < p_y then if me_y < p_y then
self.object:set_velocity(vector_new(v.x, 1 * self.walk_velocity, v.z))
self.object:set_velocity({
x = v.x,
y = 1 * self.walk_velocity,
z = v.z
})
elseif me_y > p_y then elseif me_y > p_y then
self.object:set_velocity(vector_new(v.x, -1 * self.walk_velocity, v.z))
self.object:set_velocity({
x = v.x,
y = -1 * self.walk_velocity,
z = v.z
})
end end
else else
if me_y < p_y then if me_y < p_y then
self.object:set_velocity(vector_new(v.x, 0.01, v.z))
self.object:set_velocity({
x = v.x,
y = 0.01,
z = v.z
})
elseif me_y > p_y then elseif me_y > p_y then
self.object:set_velocity(vector_new(v.x, -0.01, v.z))
self.object:set_velocity({
x = v.x,
y = -0.01,
z = v.z
})
end end
end end
end end
-- rnd: new movement direction -- rnd: new movement direction
if self.path.following if self.path.following and self.path.way and self.attack_type ~= "dogshoot" then
and self.path.way
and self.attack_type ~= "dogshoot" then
-- no paths longer than 50 -- no paths longer than 50
if #self.path.way > 50 if #self.path.way > 50 or dist < self.reach then
or dist < self.reach then
self.path.following = false self.path.following = false
return return
end end
local p1 = self.path.way[1] local p1 = self.path.way[1]
if not p1 then if not p1 then
self.path.following = false self.path.following = false
return return
end end
if math.abs(p1.x-s.x) + math.abs(p1.z - s.z) < 0.6 then if abs(p1.x - s.x) + abs(p1.z - s.z) < 0.6 then
-- reached waypoint, remove it from queue -- reached waypoint, remove it from queue
table.remove(self.path.way, 1) table.remove(self.path.way, 1)
end end
-- set new temporary target -- set new temporary target
p = {x = p1.x, y = p1.y, z = p1.z} p = vector_copy(p1)
end end
local vec = { self:turn_in_direction(p.x - s.x, p.z - s.z, 10)
x = p.x - s.x,
z = p.z - s.z
}
yaw = (atan(vec.z / vec.x) + math.pi / 2) - self.rotate
if p.x > s.x then yaw = yaw + math.pi end
yaw = self:set_yaw( yaw, 0, dtime)
-- move towards enemy if beyond mob reach -- move towards enemy if beyond mob reach
if dist > self.reach then if dist > self.reach then
@ -1100,10 +938,9 @@ function mob_class:do_states_attack (dtime)
end end
if self:is_at_cliff_or_danger() then if self:is_at_cliff_or_danger() then
self:set_velocity( 0) self:set_velocity(0)
self:set_animation( "stand") self:set_animation("stand")
local yaw = self.object:get_yaw() or 0 --self:turn_by(PI * (random() - 0.5), 10)
yaw = self:set_yaw( yaw + 0.78, 8)
else else
if self.path.stuck then if self.path.stuck then
self:set_velocity(self.walk_velocity) self:set_velocity(self.walk_velocity)
@ -1129,19 +966,13 @@ function mob_class:do_states_attack (dtime)
self.timer = 0 self.timer = 0
if not self.custom_attack then if not self.custom_attack then
if self.double_melee_attack and math.random(1, 2) == 1 then if self.double_melee_attack and random(1, 2) == 1 then
self:set_animation("punch2") self:set_animation("punch2")
else else
self:set_animation("punch") self:set_animation("punch")
end end
local p2 = p if self:line_of_sight(vector_offset(p, 0, .5, 0), vector_offset(s, 0, .5, 0)) == true then
local s2 = s
p2.y = p2.y + .5
s2.y = s2.y + .5
if self:line_of_sight( p2, s2) == true then
self:mob_sound("attack") self:mob_sound("attack")
-- punch player (or what player is attached to) -- punch player (or what player is attached to)
@ -1167,59 +998,31 @@ function mob_class:do_states_attack (dtime)
elseif self.attack_type == "shoot" elseif self.attack_type == "shoot"
or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1) or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1)
or (self.attack_type == "dogshoot" and (dist > self.reach or dist < self.avoid_distance and self.shooter_avoid_enemy) and self:dogswitch() == 0) then or (self.attack_type == "dogshoot" and (dist > self.reach or dist < self.avoid_distance and self.shooter_avoid_enemy) and self:dogswitch() == 0) then
local vec = vector_new(p.x - s.x, p.y - s.y - 1, p.z - s.z)
p.y = p.y - .5 local dist = (vec.x*vec.x + vec.y*vec.y + vec.z*vec.z)^0.5
s.y = s.y + .5 self:turn_in_direction(vec.x, vec.z, 10)
local dist = vector.distance(p, s)
local vec = {
x = p.x - s.x,
y = p.y - s.y,
z = p.z - s.z
}
yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
if p.x > s.x then yaw = yaw +math.pi end
yaw = self:set_yaw( yaw, 0, dtime)
local stay_away_from_player = vector.zero()
--strafe back and fourth
--stay away from player so as to shoot them
if dist < self.avoid_distance and self.shooter_avoid_enemy then
self:set_animation( "shoot")
stay_away_from_player=vector.multiply(vector.direction(p, s), 0.33)
end
if self.strafes then if self.strafes then
if not self.strafe_direction then if not self.strafe_direction then self.strafe_direction = HALFPI end
self.strafe_direction = 1.57 if random(40) == 1 then self.strafe_direction = self.strafe_direction * -1 end
end
if math.random(40) == 1 then
self.strafe_direction = self.strafe_direction*-1
end
local dir = vector.rotate_around_axis(vector.direction(s, p), vector.new(0,1,0), self.strafe_direction) local dir = -atan2(p.x - s.x, p.z - s.z)
local dir2 = vector.multiply(dir, 0.3 * self.walk_velocity) self.acc = vector_new(-sin(dir + self.strafe_direction) * 0.8, 0, cos(dir + self.strafe_direction) * 0.8)
--stay away from player so as to shoot them
if dir2 and stay_away_from_player then if self.avoid_distance and dist < self.avoid_distance and self.shooter_avoid_enemy then
self.acc = vector.add(dir2, stay_away_from_player) local f = 0.3 * (self.avoid_distance - dist) / self.avoid_distance
self.acc.x, self.acc.z = self.acc.x - sin(dir) * f, self.acc.z + cos(dir) * f
end end
else else
self:set_velocity( 0) self:set_velocity(0)
self:set_animation("stand")
end end
local p = self.object:get_pos() local p = self.object:get_pos()
p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2 p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) * 0.5
if self.shoot_interval
and self.timer > self.shoot_interval
and not minetest.raycast(vector.add(p, vector.new(0,self.shoot_offset,0)), vector.add(self.attack:get_pos(), vector.new(0,1.5,0)), false, false):next()
and math.random(1, 100) <= 60 then
if self.shoot_interval and self.timer > self.shoot_interval and random(1, 100) <= 60
and not minetest.raycast(vector_offset(p, 0, self.shoot_offset, 0), vector_offset(self.attack:get_pos(), 0, 1.5, 0), false, false):next() then
self.timer = 0 self.timer = 0
self:set_animation( "shoot") self:set_animation( "shoot")
@ -1228,7 +1031,6 @@ function mob_class:do_states_attack (dtime)
-- Shoot arrow -- Shoot arrow
if minetest.registered_entities[self.arrow] then if minetest.registered_entities[self.arrow] then
local arrow, ent local arrow, ent
local v = 1 local v = 1
if not self.shoot_arrow then if not self.shoot_arrow then
@ -1238,9 +1040,7 @@ function mob_class:do_states_attack (dtime)
end) end)
arrow = minetest.add_entity(p, self.arrow) arrow = minetest.add_entity(p, self.arrow)
ent = arrow:get_luaentity() ent = arrow:get_luaentity()
if ent.velocity then v = ent.velocity or v
v = ent.velocity
end
ent.switch = 1 ent.switch = 1
ent.owner_id = tostring(self.object) -- add unique owner id to arrow ent.owner_id = tostring(self.object) -- add unique owner id to arrow
@ -1252,12 +1052,9 @@ function mob_class:do_states_attack (dtime)
end end
end end
local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
-- offset makes shoot aim accurate -- offset makes shoot aim accurate
vec.y = vec.y + self.shoot_offset vec.y = vec.y + self.shoot_offset
vec.x = vec.x * (v / amount) vec.x, vec.y, vec.z = vec.x * (v / dist), vec.y * (v / dist), vec.z * (v / dist)
vec.y = vec.y * (v / amount)
vec.z = vec.z * (v / amount)
if self.shoot_arrow then if self.shoot_arrow then
vec = vector.normalize(vec) vec = vector.normalize(vec)
self:shoot_arrow(p, vec) self:shoot_arrow(p, vec)
@ -1266,13 +1063,9 @@ function mob_class:do_states_attack (dtime)
end end
end end
end end
elseif self.attack_type == "custom" and self.attack_state then elseif self.attack_type == "custom" and self.attack_state then
self.attack_state(self, dtime) self.attack_state(self, dtime)
end end
if self.on_attack then if self.on_attack then self.on_attack(self, dtime) end
self.on_attack(self, dtime)
end
end end

View File

@ -5,7 +5,11 @@ local validate_vector = mcl_util.validate_vector
local active_particlespawners = {} local active_particlespawners = {}
local disable_blood = minetest.settings:get_bool("mobs_disable_blood") local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
local DEFAULT_FALL_SPEED = -9.81*1.5 local DEFAULT_FALL_SPEED = -9.81*1.5
local PI_THIRD = math.pi / 3 -- 60 degrees local PI = math.pi
local TWOPI = math.pi * 2
local PI_HALF = math.pi * 0.5 -- 90 degrees
local MAX_PITCH = math.pi * 0.45 -- about 80 degrees
local MAX_YAW = math.pi * 0.66 -- about 120 degrees
local PATHFINDING = "gowp" local PATHFINDING = "gowp"
@ -246,9 +250,7 @@ end
-- set defined animation -- set defined animation
function mob_class:set_animation(anim, fixed_frame) function mob_class:set_animation(anim, fixed_frame)
if not self.animation or not anim then if not self.animation or not anim then return end
return
end
if self.jockey and self.object:get_attach() then if self.jockey and self.object:get_attach() then
anim = "jockey" anim = "jockey"
@ -256,11 +258,7 @@ function mob_class:set_animation(anim, fixed_frame)
self.jockey = nil self.jockey = nil
end end
if self.state == "die" and anim ~= "die" and anim ~= "stand" then if self.state == "die" and anim ~= "die" and anim ~= "stand" then return end
return
end
if self.fly and self:flight_check() and anim == "walk" then anim = "fly" end if self.fly and self:flight_check() and anim == "walk" then anim = "fly" end
@ -275,12 +273,7 @@ function mob_class:set_animation(anim, fixed_frame)
self._current_animation = anim self._current_animation = anim
local a_start = self.animation[anim .. "_start"] local a_start = self.animation[anim .. "_start"]
local a_end local a_end = fixed_frame and a_start or self.animation[anim .. "_end"]
if fixed_frame then
a_end = a_start
else
a_end = self.animation[anim .. "_end"]
end
if a_start and a_end then if a_start and a_end then
self.object:set_animation({ self.object:set_animation({
x = a_start, x = a_start,
@ -290,11 +283,6 @@ function mob_class:set_animation(anim, fixed_frame)
end end
end end
-- above function exported for mount.lua
function mcl_mobs:set_animation(self, anim)
self:set_animation(anim)
end
local function who_are_you_looking_at (self, dtime) local function who_are_you_looking_at (self, dtime)
if self.order == "sleep" then if self.order == "sleep" then
self._locked_object = nil self._locked_object = nil
@ -348,58 +336,62 @@ function mob_class:check_head_swivel(dtime)
local locked_object = self._locked_object local locked_object = self._locked_object
if locked_object and (locked_object:is_player() or locked_object:get_luaentity()) and locked_object:get_hp() > 0 then if locked_object and (locked_object:is_player() or locked_object:get_luaentity()) and locked_object:get_hp() > 0 then
local _locked_object_eye_height = 1.5 local _locked_object_eye_height = (locked_object:is_player() and locked_object:get_properties().eye_height * 0.8) -- food in hands of player
if locked_object:is_player() then or (locked_object:get_luaentity() and locked_object:get_luaentity().head_eye_height) or 1.5
_locked_object_eye_height = locked_object:get_properties().eye_height local self_rot = self.object:get_rotation()
elseif locked_object:get_luaentity() then -- If a mob is attached, should we really be messing with what they are looking at?
_locked_object_eye_height = locked_object:get_luaentity().head_eye_height -- Should this be excluded?
if self.object:get_attach() and self.object:get_attach():get_rotation() then
self_rot = self.object:get_attach():get_rotation()
end end
if _locked_object_eye_height then
local self_rot = self.object:get_rotation()
-- If a mob is attached, should we really be messing with what they are looking at?
-- Should this be excluded?
if self.object:get_attach() and self.object:get_attach():get_rotation() then
self_rot = self.object:get_attach():get_rotation()
end
local ps = self.object:get_pos() local ps = self.object:get_pos()
ps.y = ps.y + self.head_eye_height * .7 ps.y = ps.y + self.head_eye_height -- why here, instead of below? * .7
local pt = locked_object:get_pos() local pt = locked_object:get_pos()
pt.y = pt.y + _locked_object_eye_height pt.y = pt.y + _locked_object_eye_height
local dir = vector.direction(ps, pt) local dir = vector.direction(ps, pt) -- is (pt-ps):normalize()
local mob_yaw = self_rot.y + math.atan2(dir.x, dir.z) + self.head_yaw_offset local mob_yaw = math.atan2(dir.x, dir.z)
local mob_pitch = math.asin(-dir.y) * self.head_pitch_multiplier local mob_pitch = -math.asin(dir.y) * (self.head_pitch_multiplier or 1) -- allow axis inversion
if (mob_yaw < -PI_THIRD or mob_yaw > PI_THIRD) and not (self.attack and self.state == "attack" and not self.runaway) then mob_yaw = mob_yaw + self_rot.y -- to relative orientation
newr = vector.multiply(oldr, 0.9) while mob_yaw > PI do mob_yaw = mob_yaw - TWOPI end
elseif self.attack and self.state == "attack" and not self.runaway then while mob_yaw < -PI do mob_yaw = mob_yaw + TWOPI end
if self.head_yaw == "y" then mob_yaw = mob_yaw * 0.8 -- lessen the effect so it become less staring
newr = vector.new(mob_pitch, mob_yaw, 0) local max_yaw = self.head_max_yaw or MAX_YAW
elseif self.head_yaw == "z" then mob_yaw = (mob_yaw < -max_yaw and -max_yaw) or (mob_yaw < max_yaw and mob_yaw) or max_yaw -- avoid twisting the neck
newr = vector.new(mob_pitch, 0, -mob_yaw)
end mob_pitch = mob_pitch * 0.8 -- make it less obvious that this is computed
else local max_pitch = self.head_max_pitch or MAX_PITCH
if self.head_yaw == "y" then mob_pitch = (mob_pitch < -max_pitch and -max_pitch) or (mob_pitch < max_pitch and mob_pitch) or max_pitch
newr = vector.new((mob_pitch-oldr.x)*.3+oldr.x, (mob_yaw-oldr.y)*.3+oldr.y, 0)
elseif self.head_yaw == "z" then local smoothing = (self.state == "attack" and self.attack and 0.25) or 0.05
newr = vector.new((mob_pitch-oldr.x)*.3+oldr.x, 0, ((mob_yaw-oldr.y)*.3+oldr.y)*-3) local old_pitch = oldr.x
end local old_yaw = (self.head_yaw == "y" and oldr.y or -oldr.z) - self.head_yaw_offset
end -- to -pi:+pi range, so we rotate over 0 when interpolating:
while old_yaw > PI do old_yaw = old_yaw - TWOPI end
while old_yaw < -PI do old_yaw = old_yaw + TWOPI end
mob_pitch, mob_yaw = (mob_pitch-old_pitch)*smoothing+old_pitch, (mob_yaw-old_yaw)*smoothing+old_yaw
-- apply the yaw to the mob
mob_yaw = mob_yaw + self.head_yaw_offset
if self.head_yaw == "y" then
newr = vector.new(mob_pitch, mob_yaw, 0)
elseif self.head_yaw == "z" then
newr = vector.new(mob_pitch, 0, -mob_yaw) -- z yaw is opposite direction
end end
elseif not locked_object and math.abs(oldr.y) > 0.05 and math.abs(oldr.x) < 0.05 then elseif math.abs(oldr.x) + math.abs(oldr.y) + math.abs(oldr.z) > 0.05 then
newr = vector.multiply(oldr, 0.9) newr = vector.multiply(oldr, 0.9) -- smooth stop looking
end end
-- 0.02 is about 1.14 degrees tolerance, to update less often -- 0.02 is about 1.14 degrees tolerance, to update less often
local newp = vector.new(0, self.bone_eye_height, self.horizontal_head_height) if math.abs(oldr.x-newr.x) + math.abs(oldr.y-newr.y) + math.abs(oldr.z-newr.z) < 0.02 and vector.equals(oldp, vector.zero()) then return end
if math.abs(oldr.x-newr.x) + math.abs(oldr.y-newr.y) + math.abs(oldr.z-newr.z) < 0.02 and vector.equals(oldp, newp) then return end
if self.object.get_bone_override then -- minetest >= 5.9 if self.object.get_bone_override then -- minetest >= 5.9
self.object:set_bone_override(self.head_swivel, { self.object:set_bone_override(self.head_swivel, {
position = { vec = newp, absolute = true }, position = { vec = self.head_bone_position, absolute = true },
rotation = { vec = newr, absolute = true } }) rotation = { vec = newr, absolute = true, interpolation = 0.1 } })
else -- minetest < 5.9 else -- minetest < 5.9
-- old API uses degrees not radians -- old API uses degrees not radians and absolute positions
self.object:set_bone_position(self.head_swivel, newp, vector.apply(newr, math.deg)) self.object:set_bone_position(self.head_swivel, self.head_bone_position, vector.apply(newr, math.deg))
end end
end end

View File

@ -6,6 +6,19 @@ local modname = minetest.get_current_modname()
local path = minetest.get_modpath(modname) local path = minetest.get_modpath(modname)
local S = minetest.get_translator(modname) local S = minetest.get_translator(modname)
mcl_mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt" mcl_mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt"
-- used by the libaries below.
-- get node but use fallback for nil or unknown
local node_ok = function(pos, fallback)
fallback = fallback or mcl_mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return minetest.registered_nodes[fallback]
end
mcl_mobs.node_ok = node_ok
--api and helpers --api and helpers
-- effects: sounds and particles mostly -- effects: sounds and particles mostly
dofile(path .. "/effects.lua") dofile(path .. "/effects.lua")
@ -19,10 +32,9 @@ dofile(path .. "/items.lua")
dofile(path .. "/pathfinding.lua") dofile(path .. "/pathfinding.lua")
-- combat: attack logic -- combat: attack logic
dofile(path .. "/combat.lua") dofile(path .. "/combat.lua")
-- the enity functions themselves -- the entity functions themselves
dofile(path .. "/api.lua") dofile(path .. "/api.lua")
--utility functions --utility functions
dofile(path .. "/breeding.lua") dofile(path .. "/breeding.lua")
dofile(path .. "/spawning.lua") dofile(path .. "/spawning.lua")
@ -37,16 +49,6 @@ local old_spawn_icons = minetest.settings:get_bool("mcl_old_spawn_icons",false)
local extended_pet_control = minetest.settings:get_bool("mcl_extended_pet_control",true) local extended_pet_control = minetest.settings:get_bool("mcl_extended_pet_control",true)
local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0 local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0
-- get node but use fallback for nil or unknown
local node_ok = function(pos, fallback)
fallback = fallback or mcl_mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return minetest.registered_nodes[fallback]
end
--#### REGISTER FUNCS --#### REGISTER FUNCS
-- Code to execute before custom on_rightclick handling -- Code to execute before custom on_rightclick handling
@ -114,14 +116,8 @@ function mcl_mobs.register_mob(name, def)
mcl_mobs.spawning_mobs[name] = true mcl_mobs.spawning_mobs[name] = true
mcl_mobs.registered_mobs[name] = def mcl_mobs.registered_mobs[name] = def
local can_despawn local can_despawn = def.can_despawn
if def.can_despawn ~= nil then if def.can_despawn == nil then can_despawn = def.spawn_class ~= "passive" end
can_despawn = def.can_despawn
elseif def.spawn_class == "passive" then
can_despawn = false
else
can_despawn = true
end
local function scale_difficulty(value, default, min, special) local function scale_difficulty(value, default, min, special)
if (not value) or (value == default) or (value == special) then if (not value) or (value == default) or (value == special) then
@ -143,11 +139,12 @@ function mcl_mobs.register_mob(name, def)
head_swivel = def.head_swivel or nil, -- bool to activate this function 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_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 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 1, -- how high approximately the mobs eyes are from 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 hight aproximatly the mobs head is fromm the ground to tell the mob how high to look up at the player head_max_yaw = def.head_max_yaw, -- how far the mob may turn the head
head_max_pitch = def.head_max_pitch, -- how far up and down the mob may pitch the head
head_bone_position = def.head_bone_position or { 0, def.bone_eye_height or 1.4, def.horizontal_head_height or 0},
curiosity = def.curiosity or 1, -- how often mob will look at player on idle 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 head_yaw = def.head_yaw or "y", -- axis to rotate head on
horizontal_head_height = def.horizontal_head_height or 0,
wears_armor = def.wears_armor, -- a number value used to index texture slot for armor wears_armor = def.wears_armor, -- a number value used to index texture slot for armor
stepheight = def.stepheight or 0.6, stepheight = def.stepheight or 0.6,
name = name, name = name,

View File

@ -1,5 +1,5 @@
name = mcl_mobs name = mcl_mobs
author = PilzAdam author = PilzAdam, kno10
description = Adds a mob API for mods to add animals or monsters, etc. description = Adds a mob API for mods to add animals or monsters, etc.
depends = mcl_particles, mcl_luck depends = mcl_particles, mcl_luck
optional_depends = mcl_weather, mcl_explosions, mcl_hunger, mcl_worlds, invisibility, lucky_block, cmi, doc_identifier, mcl_armor, mcl_portals, mcl_experience, mcl_sculk optional_depends = mcl_weather, mcl_explosions, mcl_hunger, mcl_worlds, invisibility, lucky_block, cmi, doc_identifier, mcl_armor, mcl_portals, mcl_experience, mcl_sculk

View File

@ -1,110 +1,41 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class local mob_class = mcl_mobs.mob_class
-- lib_mount by Blert2112 (edited by TenPlus1) -- based on lib_mount by Blert2112 (edited by TenPlus1)
local enable_crash = false local enable_crash = false
local crash_threshold = 6.5 -- ignored if enable_crash=false local crash_threshold = 6.5 -- ignored if enable_crash=false
local GRAVITY = -9.8
------------------------------------------------------------------------------ local node_ok = mcl_mobs.node_ok
local sign = math.sign -- minetest extension
--
-- Helper functions
--
local node_ok = function(pos, fallback)
fallback = fallback or mcl_mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return {name = fallback}
end
local function node_is(pos) local function node_is(pos)
local node = node_ok(pos) local node = node_ok(pos)
if node.name == "air" then return "air" end
if node.name == "air" then local ndef = minetest.registered_nodes[node.name]
return "air" if not ndef then return "other" end -- unknown/ignore
end if ndef.groups.lava then return "lava" end
if ndef.groups.liquid then return "liquid" end
if minetest.get_item_group(node.name, "lava") ~= 0 then if ndef.walkable then return "walkable" end
return "lava"
end
if minetest.get_item_group(node.name, "liquid") ~= 0 then
return "liquid"
end
if minetest.registered_nodes[node.name].walkable == true then
return "walkable"
end
return "other" return "other"
end end
local function get_sign(i)
i = i or 0
if i == 0 then
return 0
else
return i / math.abs(i)
end
end
local function get_velocity(v, yaw, y)
local x = -math.sin(yaw) * v
local z = math.cos(yaw) * v
return {x = x, y = y, z = z}
end
local function get_v(v)
return math.sqrt(v.x * v.x + v.z * v.z)
end
local function force_detach(player) local function force_detach(player)
local attached_to = player:get_attach() local attached_to = player:get_attach()
if not attached_to then return end
if not attached_to then
return
end
local entity = attached_to:get_luaentity() local entity = attached_to:get_luaentity()
if entity.driver and entity.driver == player then entity.driver = nil end
if entity.driver
and entity.driver == player then
entity.driver = nil
end
player:set_detach() player:set_detach()
mcl_player.player_attached[player:get_player_name()] = false mcl_player.player_attached[player:get_player_name()] = false
player:set_eye_offset({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0}) player:set_eye_offset(vector.zero(), vector.zero())
mcl_player.player_set_animation(player, "stand" , 30) mcl_player.player_set_animation(player, "stand" , 30)
player:set_properties({visual_size = {x = 1, y = 1} }) player:set_properties({visual_size = {x = 1, y = 1} })
end end
------------------------------------------------------------------------------- minetest.register_on_leaveplayer(force_detach)
minetest.register_on_leaveplayer(function(player)
force_detach(player)
end)
minetest.register_on_shutdown(function() minetest.register_on_shutdown(function()
local players = minetest.get_connected_players() local players = minetest.get_connected_players()
@ -118,39 +49,24 @@ minetest.register_on_dieplayer(function(player)
return true return true
end) end)
-------------------------------------------------------------------------------
function mcl_mobs.attach(entity, player) function mcl_mobs.attach(entity, player)
entity.player_rotation = entity.player_rotation or vector.zero()
local attach_at, eye_offset entity.driver_attach_at = entity.driver_attach_at or vector.zero()
entity.driver_eye_offset = entity.driver_eye_offset or vector.zero()
entity.player_rotation = entity.player_rotation or {x = 0, y = 0, z = 0}
entity.driver_attach_at = entity.driver_attach_at or {x = 0, y = 0, z = 0}
entity.driver_eye_offset = entity.driver_eye_offset or {x = 0, y = 0, z = 0}
entity.driver_scale = entity.driver_scale or {x = 1, y = 1} entity.driver_scale = entity.driver_scale or {x = 1, y = 1}
local rot_view = 0 local rot_view = entity.player_rotation.y == 90 and math.pi/2 or 0
local attach_at = entity.driver_attach_at
if entity.player_rotation.y == 90 then local eye_offset = entity.driver_eye_offset
rot_view = math.pi/2
end
attach_at = entity.driver_attach_at
eye_offset = entity.driver_eye_offset
entity.driver = player entity.driver = player
force_detach(player) force_detach(player)
player:set_attach(entity.object, "", attach_at, entity.player_rotation) player:set_attach(entity.object, "", attach_at, entity.player_rotation)
mcl_player.player_attached[player:get_player_name()] = true mcl_player.player_attached[player:get_player_name()] = true
player:set_eye_offset(eye_offset, {x = 0, y = 0, z = 0}) player:set_eye_offset(eye_offset, vector.zero())
player:set_properties({ player:set_properties({ visual_size = entity.driver_scale })
visual_size = {
x = entity.driver_scale.x,
y = entity.driver_scale.y
}
})
minetest.after(0.2, function(name) minetest.after(0.2, function(name)
local player = minetest.get_player_by_name(name) local player = minetest.get_player_by_name(name)
@ -164,162 +80,88 @@ end
function mcl_mobs.detach(player, offset) function mcl_mobs.detach(player, offset)
force_detach(player) force_detach(player)
mcl_player.player_set_animation(player, "stand" , 30) mcl_player.player_set_animation(player, "stand" , 30)
player:add_velocity(vector.new(math.random()*12-6,math.random()*3+5,math.random()*12-6)) --throw the rider off
--local pos = player:get_pos()
--pos = {x = pos.x + offset.x, y = pos.y + 0.2 + offset.y, z = pos.z + offset.z}
player:add_velocity(vector.new(math.random(-6,6),math.random(5,8),math.random(-6,6))) --throw the rider off
--[[
minetest.after(0.1, function(name, pos)
local player = minetest.get_player_by_name(name)
if player then
player:set_pos(pos)
end
end, player:get_player_name(), pos)
]]--
end end
function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime) function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
local rot_view = 0
if entity.player_rotation.y == 90 then
rot_view = math.pi/2
end
local acce_y = 0
local velo = entity.object:get_velocity() local velo = entity.object:get_velocity()
local v = math.sqrt(velo.x * velo.x + velo.y * velo.y)
entity.v = get_v(velo) * get_sign(entity.v) local acce_y = GRAVITY
-- process controls -- process controls
if entity.driver then if entity.driver then
local ctrl = entity.driver:get_player_control() local ctrl = entity.driver:get_player_control()
if ctrl.up then -- forward
-- move forwards v = v + entity.accel * 0.1 * entity.run_velocity * 0.385
if ctrl.up then elseif ctrl.down then -- backwards
if entity.max_speed_reverse == 0 and v == 0 then return end
entity.v = entity.v + entity.accel / 10 * entity.run_velocity / 2.6 v = v - entity.accel * 0.1 * entity.run_velocity * 0.385
-- move backwards
elseif ctrl.down then
if entity.max_speed_reverse == 0 and entity.v == 0 then
return
end
entity.v = entity.v - entity.accel / 10
end end
-- fix mob rotation entity:set_yaw(entity.driver:get_look_horizontal() - entity.rotate, 2)
entity.object:set_yaw(entity.driver:get_look_horizontal() - entity.rotate)
if can_fly then if can_fly then
-- FIXME: use acce_y instead?
-- fly up -- fly up
if ctrl.jump then if ctrl.jump then
velo.y = velo.y + 1 velo.y = math.min(velo.y + 1, entity.accel)
if velo.y > entity.accel then velo.y = entity.accel end elseif velo.y > 0.1 then
elseif velo.y > 0 then
velo.y = velo.y - 0.1 velo.y = velo.y - 0.1
if velo.y < 0 then velo.y = 0 end elseif velo.y > 0 then
velo.y = 0
end end
-- fly down -- fly down
if ctrl.sneak then if ctrl.sneak then
velo.y = velo.y - 1 velo.y = math.max(velo.y - 1, -entity.accel)
if velo.y < -entity.accel then velo.y = -entity.accel end elseif velo.y < -0.1 then
elseif velo.y < 0 then
velo.y = velo.y + 0.1 velo.y = velo.y + 0.1
if velo.y > 0 then velo.y = 0 end elseif velo.y < 0 then
velo.y = 0
end end
else else
-- jump -- jump
if ctrl.jump then if ctrl.jump then
if velo.y == 0 then if velo.y == 0 then
velo.y = velo.y + entity.jump_height velo.y = velo.y + entity.jump_height
acce_y = acce_y + (acce_y * 3) + 1 acce_y = acce_y + 1
end end
end end
end end
end end
-- Stop! if math.abs(v) < 0.02 then -- stop
local s = get_sign(entity.v) entity.object:set_velocity(vector.zero())
v = 0
entity.v = entity.v - 0.02 * s else
v = v - 0.02 * sign(v) -- slow down
if s ~= get_sign(entity.v) then
entity.object:set_velocity({x = 0, y = 0, z = 0})
entity.v = 0
return
end end
-- if not moving then set animation and return -- if not moving then set animation and return
if entity.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then if v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then
entity:set_animation(stand_anim)
if stand_anim then
mcl_mobs:set_animation(entity, stand_anim)
end
return return
end else
entity:set_animation(moving_anim)
-- set moving animation
if moving_anim then
mcl_mobs:set_animation(entity, moving_anim)
end end
-- enforce speed limit forward and reverse -- enforce speed limit forward and reverse
local max_spd = entity.max_speed_reverse v = math.max(-entity.max_speed_reverse, math.min(v, entity.max_speed_forward))
if get_sign(entity.v) >= 0 then
max_spd = entity.max_speed_forward
end
if math.abs(entity.v) > max_spd then
entity.v = entity.v - get_sign(entity.v)
end
-- Set position, velocity and acceleration -- Set position, velocity and acceleration
local p = entity.object:get_pos() local p = entity.object:get_pos()
local new_velo
local new_acce = {x = 0, y = -9.8, z = 0}
p.y = p.y - 0.5 p.y = p.y - 0.5
local ni = node_is(p) local ni = node_is(p)
local v = entity.v
if ni == "air" then if ni == "air" then
if can_fly then acce_y = acce_y - GRAVITY end
if can_fly == true then
new_acce.y = 0
end
elseif ni == "liquid" or ni == "lava" then elseif ni == "liquid" or ni == "lava" then
if ni == "lava" and entity.lava_damage ~= 0 then if ni == "lava" and entity.lava_damage ~= 0 then
entity.lava_counter = (entity.lava_counter or 0) + dtime entity.lava_counter = (entity.lava_counter or 0) + dtime
if entity.lava_counter > 1 then if entity.lava_counter > 1 then
minetest.sound_play("default_punch", { minetest.sound_play("default_punch", {
object = entity.object, object = entity.object,
max_hear_distance = 5 max_hear_distance = 5
@ -336,18 +178,15 @@ function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
if entity.terrain_type == 2 if entity.terrain_type == 2
or entity.terrain_type == 3 then or entity.terrain_type == 3 then
acce_y = 0
new_acce.y = 0
p.y = p.y + 1 p.y = p.y + 1
if node_is(p) == "liquid" then if node_is(p) == "liquid" then
if velo.y >= 5 then if velo.y >= 5 then
velo.y = 5 velo.y = 5
elseif velo.y < 0 then elseif velo.y < 0 then
new_acce.y = 20 acce_y = 20
else else
new_acce.y = 5 acce_y = 5
end end
else else
if math.abs(velo.y) < 1 then if math.abs(velo.y) < 1 then
@ -362,75 +201,51 @@ function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
end end
end end
new_velo = get_velocity(v, entity.object:get_yaw() - rot_view, velo.y) local rot_view = entity.player_rotation.y == 90 and math.pi/2 or 0
new_acce.y = new_acce.y + acce_y local new_yaw = entity.object:get_yaw() - rot_view
local new_velo = vector.new(-math.sin(new_yaw) * v, velo.y, math.cos(new_yaw) * v)
entity.object:set_velocity(new_velo) entity.object:set_velocity(new_velo)
entity.object:set_acceleration(new_acce) entity.object:set_acceleration(vector.new(0, acce_y, 0))
-- CRASH!
if enable_crash then if enable_crash then
if v >= crash_threshold then
local intensity = entity.v2 - v
if intensity >= crash_threshold then
entity.object:punch(entity.object, 1.0, { entity.object:punch(entity.object, 1.0, {
full_punch_interval = 1.0, full_punch_interval = 1.0,
damage_groups = {fleshy = intensity} damage_groups = {fleshy = v}
}, nil) }, nil)
end end
end end
entity.v2 = v
end end
-- directional flying routine by D00Med (edited by TenPlus1) -- directional flying routine by D00Med (edited by TenPlus1)
function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim) function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim)
local ctrl = entity.driver:get_player_control() local ctrl = entity.driver:get_player_control()
local velo = entity.object:get_velocity() local velo = entity.object:get_velocity()
local dir = entity.driver:get_look_dir() local dir = entity.driver:get_look_dir()
local yaw = entity.driver:get_look_horizontal() + 1.57 -- offset fix between old and new commands local yaw = entity.driver:get_look_horizontal()
if ctrl.up then if ctrl.up then
entity.object:set_velocity({ entity.object:set_velocity(vector.new(dir.x * speed, dir.y * speed + 2, dir.z * speed))
x = dir.x * speed,
y = dir.y * speed + 2,
z = dir.z * speed
})
elseif ctrl.down then elseif ctrl.down then
entity.object:set_velocity({ entity.object:set_velocity(vector.new(-dir.x * speed, dir.y * speed + 2, -dir.z * speed))
x = -dir.x * speed,
y = dir.y * speed + 2,
z = -dir.z * speed
})
elseif not ctrl.down or ctrl.up or ctrl.jump then elseif not ctrl.down or ctrl.up or ctrl.jump then
entity.object:set_velocity({x = 0, y = -2, z = 0}) entity.object:set_velocity(vector.new(0, -2, 0))
end end
entity.object:set_yaw(yaw + math.pi + math.pi / 2 - entity.rotate) entity:set_yaw(yaw - entity.rotate, 2)
-- firing arrows -- firing arrows
if ctrl.LMB and ctrl.sneak and shoots then if ctrl.LMB and ctrl.sneak and shoots then
local pos = entity.object:get_pos() local pos = entity.object:get_pos()
local obj = minetest.add_entity({ local obj = minetest.add_entity(vector.offset(pos, dir.x * 2.5, 1.5 + dir.y, dir.z * 2.5), arrow)
x = pos.x + 0 + dir.x * 2.5,
y = pos.y + 1.5 + dir.y,
z = pos.z + 0 + dir.z * 2.5}, arrow)
local ent = obj:get_luaentity() local ent = obj:get_luaentity()
if ent then if ent then
ent.switch = 1 -- for mob specific arrows ent.switch = 1 -- for mob specific arrows
ent.owner_id = tostring(entity.object) -- so arrows dont hurt entity you are riding ent.owner_id = tostring(entity.object) -- so arrows dont hurt entity you are riding
local vec = {x = dir.x * 6, y = dir.y * 6, z = dir.z * 6} local vec = vector.new(dir.x * 6, dir.y * 6, dir.z * 6)
local yaw = entity.driver:get_look_horizontal() local yaw = entity.driver:get_look_horizontal()
obj:set_yaw(yaw + math.pi / 2) obj:set_yaw(yaw)
obj:set_velocity(vec) obj:set_velocity(vec)
else else
obj:remove() obj:remove()
@ -439,11 +254,9 @@ function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_an
-- change animation if stopped -- change animation if stopped
if velo.x == 0 and velo.y == 0 and velo.z == 0 then if velo.x == 0 and velo.y == 0 and velo.z == 0 then
entity:set_animation(stand_anim)
mcl_mobs:set_animation(entity, stand_anim)
else else
-- moving animation entity:set_animation(moving_anim)
mcl_mobs:set_animation(entity, moving_anim)
end end
end end
@ -452,12 +265,7 @@ mcl_mobs.mob_class.fly = mcl_mobs.fly
mcl_mobs.mob_class.attach = mcl_mobs.attach mcl_mobs.mob_class.attach = mcl_mobs.attach
function mob_class:on_detach_child(child) function mob_class:on_detach_child(child)
if self.detach_child then if self.detach_child and self.detach_child(self, child) then return end
if self.detach_child(self, child) then if self.driver == child then self.driver = nil end
return
end
end
if self.driver == child then
self.driver = nil
end
end end

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,14 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class local mob_class = mcl_mobs.mob_class
local PATHFINDING_FAIL_THRESHOLD = 100 -- no. of ticks to fail before giving up. 20p/s. 5s helps them get through door local PATHFINDING_FAIL_THRESHOLD = 200 -- no. of ticks to fail before giving up. 20p/s. 5s helps them get through door
local PATHFINDING_FAIL_WAIT = 30 -- how long to wait before trying to path again local PATHFINDING_FAIL_WAIT = 30 -- how long to wait before trying to path again
local PATHING_START_DELAY = 4 -- When doing non-prioritised pathing, how long to wait until last mob pathed local PATHING_START_DELAY = 4 -- When doing non-prioritised pathing, how long to wait until last mob pathed
local PATHFINDING_SEARCH_DISTANCE = 50 -- How big the square is that pathfinding will look local PATHFINDING_SEARCH_DISTANCE = 25 -- How big the square is that pathfinding will look
local PATHFINDING = "gowp" local PATHFINDING = "gowp"
local one_down = vector.new(0,-1,0)
local one_up = vector.new(0,1,0)
local plane_adjacents = { local plane_adjacents = {
vector.new(1,0,0), vector.new(1,0,0),
vector.new(-1,0,0), vector.new(-1,0,0),
@ -20,6 +17,7 @@ local plane_adjacents = {
} }
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_pathfinding",false) local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_pathfinding",false)
local visualize = minetest.settings:get_bool("mcl_mobs_pathfinding_visualize",false)
local LOG_MODULE = "[Mobs Pathfinding]" local LOG_MODULE = "[Mobs Pathfinding]"
local function mcl_log (message) local function mcl_log (message)
@ -42,8 +40,8 @@ function append_paths (wp1, wp2)
mcl_log("Cannot append wp's") mcl_log("Cannot append wp's")
return return
end end
output_table(wp1) --output_table(wp1)
output_table(wp2) --output_table(wp2)
for _,a in pairs (wp2) do for _,a in pairs (wp2) do
table.insert(wp1, a) table.insert(wp1, a)
end end
@ -51,18 +49,13 @@ function append_paths (wp1, wp2)
end end
local function output_enriched (wp_out) local function output_enriched (wp_out)
mcl_log("Output enriched path") --mcl_log("Output enriched path")
local i = 0 local i = 0
for _,outy in pairs (wp_out) do for _,outy in pairs (wp_out) do
i = i + 1 i = i + 1
mcl_log("Pos ".. i ..":" .. minetest.pos_to_string(outy["pos"]))
local action = outy["action"] local action = outy["action"]
if action then if action then
--mcl_log("Pos ".. i ..":" .. minetest.pos_to_string(outy["pos"])) mcl_log("Pos ".. i ..":" .. minetest.pos_to_string(outy["pos"])..", type: " .. action["type"]..", action: " .. action["action"]..", target: " .. minetest.pos_to_string(action["target"]))
mcl_log("type: " .. action["type"])
mcl_log("action: " .. action["action"])
mcl_log("target: " .. minetest.pos_to_string(action["target"]))
end end
--mcl_log("failed attempts: " .. outy["failed_attempts"]) --mcl_log("failed attempts: " .. outy["failed_attempts"])
end end
@ -73,33 +66,22 @@ end
-- an action, such as to open or close a door where we know that pos requires that action -- an action, such as to open or close a door where we know that pos requires that action
local function generate_enriched_path(wp_in, door_open_pos, door_close_pos, cur_door_pos) local function generate_enriched_path(wp_in, door_open_pos, door_close_pos, cur_door_pos)
local wp_out = {} local wp_out = {}
-- TODO Just pass in door position and the index before is open, the index after is close
local current_door_index = -1
for i, cur_pos in pairs(wp_in) do for i, cur_pos in pairs(wp_in) do
local action = nil local action = nil
local cur_pos_to_add = vector.add(cur_pos, one_down) if door_open_pos and vector.equals(cur_pos, door_open_pos) then
if door_open_pos and vector.equals (cur_pos, door_open_pos) then
mcl_log ("Door open match") mcl_log ("Door open match")
action = {type = "door", action = "open", target = cur_door_pos} action = {type = "door", action = "open", target = cur_door_pos}
cur_pos_to_add = vector.add(cur_pos, one_down)
elseif door_close_pos and vector.equals(cur_pos, door_close_pos) then elseif door_close_pos and vector.equals(cur_pos, door_close_pos) then
mcl_log ("Door close match") mcl_log ("Door close match")
action = {type = "door", action = "close", target = cur_door_pos} action = {type = "door", action = "close", target = cur_door_pos}
cur_pos_to_add = vector.add(cur_pos, one_down)
elseif cur_door_pos and vector.equals(cur_pos, cur_door_pos) then elseif cur_door_pos and vector.equals(cur_pos, cur_door_pos) then
mcl_log("Current door pos") mcl_log("Current door pos")
action = {type = "door", action = "open", target = cur_door_pos} action = {type = "door", action = "open", target = cur_door_pos}
cur_pos_to_add = vector.add(cur_pos, one_down)
else
cur_pos_to_add = cur_pos
--mcl_log ("Pos doesn't match")
end end
wp_out[i] = {} wp_out[i] = {}
wp_out[i]["pos"] = cur_pos_to_add wp_out[i]["pos"] = cur_pos
wp_out[i]["failed_attempts"] = 0 wp_out[i]["failed_attempts"] = 0
wp_out[i]["action"] = action wp_out[i]["action"] = action
@ -113,93 +95,82 @@ end
local last_pathing_time = os.time() local last_pathing_time = os.time()
function mob_class:ready_to_path(prioritised) function mob_class:ready_to_path(prioritised)
mcl_log("Check ready to path") -- mcl_log("Check ready to path")
if self._pf_last_failed and (os.time() - self._pf_last_failed) < PATHFINDING_FAIL_WAIT then if self._pf_last_failed and (os.time() - self._pf_last_failed) < PATHFINDING_FAIL_WAIT then
mcl_log("Not ready to path as last fail is less than threshold: " .. (os.time() - self._pf_last_failed)) -- mcl_log("Not ready to path as last fail is less than threshold: " .. (os.time() - self._pf_last_failed))
return false return false
else else
local time_since_path_start = os.time() - last_pathing_time local time_since_path_start = os.time() - last_pathing_time
mcl_log("time_since_path_start: " .. tostring(time_since_path_start))
if prioritised or (time_since_path_start) > PATHING_START_DELAY then if prioritised or (time_since_path_start) > PATHING_START_DELAY then
mcl_log("We are ready to pathfind, no previous fail or we are past threshold") mcl_log("We are ready to pathfind, no previous fail or we are past threshold: "..tostring(time_since_path_start))
return true return true
end end
mcl_log("time_since_path_start: " .. tostring(time_since_path_start))
end end
end end
-- This function is used to see if we can path. We could use to check a route, rather than making people move. -- This function is used to see if we can path. We could use to check a route, rather than making people move.
local function calculate_path_through_door (p, cur_door_pos, t) local function calculate_path_through_door (p, cur_door_pos, t)
if not cur_door_pos then return end
if t then if t then
mcl_log("Plot route through door from pos: " .. minetest.pos_to_string(p) .. ", to target: " .. minetest.pos_to_string(t)) mcl_log("Plot route through door from pos: " .. minetest.pos_to_string(p) .. " through " .. minetest.pos_to_string(cur_door_pos) .. ", to target: " .. minetest.pos_to_string(t))
else else
mcl_log("Plot route through door from pos: " .. minetest.pos_to_string(p)) mcl_log("Plot route through door from pos: " .. minetest.pos_to_string(p) .. " through " .. minetest.pos_to_string(cur_door_pos))
end end
local enriched_path = nil for _,v in pairs(plane_adjacents) do
local wp, prospective_wp local pos_closest_to_door = vector.add(cur_door_pos,v)
local n = minetest.get_node(pos_closest_to_door)
if not n.walkable then
mcl_log("We have open space next to door at: " .. minetest.pos_to_string(pos_closest_to_door))
local pos_closest_to_door = nil local prospective_wp = minetest.find_path(p, pos_closest_to_door, PATHFINDING_SEARCH_DISTANCE, 1, 4)
local other_side_of_door = nil
if cur_door_pos then if prospective_wp then
mcl_log("Found a door near: " .. minetest.pos_to_string(cur_door_pos)) local other_side_of_door = vector.add(cur_door_pos,-v)
mcl_log("Found a path to next to door".. minetest.pos_to_string(pos_closest_to_door))
mcl_log("Opposite is: ".. minetest.pos_to_string(other_side_of_door))
for _,v in pairs(plane_adjacents) do table.insert(prospective_wp, cur_door_pos)
pos_closest_to_door = vector.add(cur_door_pos,v)
other_side_of_door = vector.add(cur_door_pos,-v)
local n = minetest.get_node(pos_closest_to_door) if t then
mcl_log("We have t, lets go from door to target")
local wp_otherside_door_to_target = minetest.find_path(other_side_of_door, t, PATHFINDING_SEARCH_DISTANCE, 1, 4)
if n.name == "air" then if wp_otherside_door_to_target and #wp_otherside_door_to_target > 0 then
mcl_log("We have air space next to door at: " .. minetest.pos_to_string(pos_closest_to_door)) append_paths (prospective_wp, wp_otherside_door_to_target)
mcl_log("We have a path from outside door to target")
prospective_wp = minetest.find_path(p, pos_closest_to_door, PATHFINDING_SEARCH_DISTANCE, 1, 4) return generate_enriched_path(prospective_wp, pos_closest_to_door, other_side_of_door, cur_door_pos)
if prospective_wp then
mcl_log("Found a path to next to door".. minetest.pos_to_string(pos_closest_to_door))
mcl_log("Opposite is: ".. minetest.pos_to_string(other_side_of_door))
table.insert(prospective_wp, cur_door_pos)
if t then
mcl_log("We have t, lets go from door to target")
local wp_otherside_door_to_target = minetest.find_path(other_side_of_door, t, PATHFINDING_SEARCH_DISTANCE, 1, 4)
if wp_otherside_door_to_target and #wp_otherside_door_to_target > 0 then
append_paths (prospective_wp, wp_otherside_door_to_target)
wp = prospective_wp
mcl_log("We have a path from outside door to target")
else
mcl_log("We cannot path from outside door to target")
end
else else
mcl_log("No t, just add other side of door") mcl_log("We cannot path from outside door to target")
table.insert(prospective_wp, other_side_of_door)
wp = prospective_wp
end
if wp then
enriched_path = generate_enriched_path(wp, pos_closest_to_door, other_side_of_door, cur_door_pos)
break
end end
else else
mcl_log("Cannot path to this air block next to door.") mcl_log("No t, just add other side of door")
table.insert(prospective_wp, other_side_of_door)
return generate_enriched_path(prospective_wp, pos_closest_to_door, other_side_of_door, cur_door_pos)
end end
else
mcl_log("Cannot path to this air block next to door.")
end end
end end
else
mcl_log("No door found")
end end
if wp and not enriched_path then
mcl_log("Wp but not enriched")
enriched_path = generate_enriched_path(wp)
end
return enriched_path
end end
-- we treat ignore as solid, as we cannot path there
local function is_solid(pos)
local ndef = minetest.registered_nodes[minetest.get_node(pos).name]
return (not ndef) or ndef.walkable
end
local function find_open_node(pos, radius)
local r = vector.round(pos)
if not is_solid(r) then return r end
local above = vector.offset(r, 0, 1, 0)
if not is_solid(above) then return above, true end -- additional return: drop last
local n = minetest.find_node_near(pos, radius or 1, {"air"})
if n then return n end
return nil
end
function mob_class:gopath(target, callback_arrived, prioritised) function mob_class:gopath(target, callback_arrived, prioritised)
if self.state == PATHFINDING then mcl_log("Already pathfinding, don't set another until done.") return end if self.state == PATHFINDING then mcl_log("Already pathfinding, don't set another until done.") return end
@ -209,8 +180,19 @@ function mob_class:gopath(target, callback_arrived, prioritised)
self.order = nil self.order = nil
local p = self.object:get_pos() -- maybe feet are buried in solid?
local t = vector.offset(target,0,1,0) local start = self.object:get_pos()
local p = find_open_node(start, 1)
if not p then -- buried?
minetest.log("action", "Cannot path from "..minetest.pos_to_string(start).." because it is solid. Nodetype: "..minetest.get_node(start).name)
return
end
-- target might be a job-site that is solid
local t, drop_last_wp = find_open_node(target, 1)
if not t then
minetest.log("action", "Cannot path to "..minetest.pos_to_string(target).." because it is solid. Nodetype: "..minetest.get_node(target).name)
return
end
--Check direct route --Check direct route
local wp = minetest.find_path(p, t, PATHFINDING_SEARCH_DISTANCE, 1, 4) local wp = minetest.find_path(p, t, PATHFINDING_SEARCH_DISTANCE, 1, 4)
@ -218,11 +200,15 @@ function mob_class:gopath(target, callback_arrived, prioritised)
if not wp then if not wp then
mcl_log("### No direct path. Path through door closest to target.") mcl_log("### No direct path. Path through door closest to target.")
local door_near_target = minetest.find_node_near(target, 16, {"group:door"}) local door_near_target = minetest.find_node_near(target, 16, {"group:door"})
local below = door_near_target and vector.offset(door_near_target, 0, -1, 0)
if below and minetest.get_item_group(minetest.get_node(below), "door") > 0 then door_near_target = below end
wp = calculate_path_through_door(p, door_near_target, t) wp = calculate_path_through_door(p, door_near_target, t)
if not wp then if not wp then
mcl_log("### No path though door closest to target. Try door closest to origin.") mcl_log("### No path though door closest to target. Try door closest to origin.")
local door_closest = minetest.find_node_near(p, 16, {"group:door"}) local door_closest = minetest.find_node_near(p, 16, {"group:door"})
local below = door_closest and vector.offset(door_closest, 0, -1, 0)
if below and minetest.get_item_group(minetest.get_node(below), "door") > 0 then door_closest = below end
wp = calculate_path_through_door(p, door_closest, t) wp = calculate_path_through_door(p, door_closest, t)
-- Path through 2 doors -- Path through 2 doors
@ -236,7 +222,7 @@ function mob_class:gopath(target, callback_arrived, prioritised)
local pos_after_door_entry = path_through_closest_door[#path_through_closest_door] local pos_after_door_entry = path_through_closest_door[#path_through_closest_door]
if pos_after_door_entry then if pos_after_door_entry then
local pos_after_door = vector.add(pos_after_door_entry["pos"], one_up) local pos_after_door = pos_after_door_entry["pos"]
mcl_log("pos_after_door: " .. minetest.pos_to_string(pos_after_door)) mcl_log("pos_after_door: " .. minetest.pos_to_string(pos_after_door))
local path_after_door = calculate_path_through_door(pos_after_door, door_near_target, t) local path_after_door = calculate_path_through_door(pos_after_door, door_near_target, t)
if path_after_door and #path_after_door > 1 then if path_after_door and #path_after_door > 1 then
@ -268,26 +254,92 @@ function mob_class:gopath(target, callback_arrived, prioritised)
-- If cannot path, don't immediately try again -- If cannot path, don't immediately try again
end end
-- todo: we would also need to avoid overhangs, but minetest.find_path cannot help us there
-- we really need a better pathfinder overall.
-- try to find a way around fences and walls. This is very barebones, but at least it should
-- help path around very simple fences *IF* there is a detour that does not require jumping or gates.
if wp and #wp > 0 then if wp and #wp > 0 then
local i = 1
while i < #wp do
-- fence or wall underneath?
local bdef = minetest.registered_nodes[minetest.get_node(vector.offset(wp[i].pos, 0, -1, 0)).name]
if not bdef then minetest.log("warning", "There must not be unknown nodes on path") end
-- carpets are fine
if bdef and (bdef.groups.carpet or 0) > 0 then
wp[i].pos = vector.offset(wp[i].pos, 0, -1, 0)
-- target bottom of door
elseif bdef and (bdef.groups.door or 0) > 0 then
wp[i].pos = vector.offset(wp[i].pos, 0, -1, 0)
-- not walkable?
elseif bdef and not bdef.walkable then
wp[i].pos = vector.offset(wp[i].pos, 0, -1, 0)
i = i - 1
-- plan opening fence gates
elseif bdef and (bdef.groups.fence_gate or 0) > 0 then
wp[i].pos = vector.offset(wp[i].pos, 0, -1, 0)
wp[math.max(1,i-1)].action = {type = "door", action = "open", target = wp[i].pos}
if i+1 < #wp then
wp[i+1].action = {type = "door", action = "close", target = wp[i].pos}
end
-- do not jump on fences and walls, but try to walk around
elseif bdef and i > 1 and ((bdef.groups.fence or 0) > 0 or (bdef.groups.wall or 0) > 0) and wp[i].pos.y > wp[i-1].pos.y then
-- find end of wall(s)
local j = i + 1
while j <= #wp do
local below = vector.offset(wp[j].pos, 0, -1, 0)
local bdef = minetest.registered_nodes[minetest.get_node(below).name]
if not bdef or ((bdef.groups.fence or 0) == 0 and (bdef.groups.wall or 0) == 0) then
break
end
j = j + 1
end
-- minetest.log("warning", bdef.name .. " at "..tostring(i).." end at "..(j <= #wp and tostring(j) or "nil"))
if j <= #wp and wp[i-1].pos.y == wp[j].pos.y then
local swp = minetest.find_path(wp[i-1].pos, wp[j].pos, PATHFINDING_SEARCH_DISTANCE, 0, 0)
-- TODO: if we do not find a path here, consider pathing through a fence gate!
if swp and #swp > 0 then
for k = j-1,i,-1 do table.remove(wp, k) end
for k = 2, #swp-1 do table.insert(wp, i-2+k, {pos = swp[k], failed_attempts = 0}) end
--minetest.log("warning", "Monkey patch pathfinding around "..bdef.name.." successful.")
i = i + #swp - 4
else
--minetest.log("warning", "Monkey patch pathfinding around "..bdef.name.." failed.")
end
end
end
i = i + 1
end
end
if wp and drop_last_wp and vector.equals(wp[#wp], t) then table.remove(wp, #wp) end
if wp and #wp > 0 then
if visualize then
for i = 1,#wp do
core.add_particle({pos = wp[i].pos, expirationtime=3+i/3, size=3+2/i, velocity=vector.new(0,-0.02,0),
texture="mcl_copper_anti_oxidation_particle.png"}) -- white stars
end
end
--output_table(wp) --output_table(wp)
self._target = t self._target = t
self.callback_arrived = callback_arrived self.callback_arrived = callback_arrived
local current_location = table.remove(wp,1) self.current_target = table.remove(wp,1)
if current_location and current_location["pos"] then while self.current_target and self.current_target.pos and vector.distance(p, self.current_target.pos) < 0.5 do
mcl_log("Removing first co-ord? " .. tostring(current_location["pos"])) --mcl_log("Skipping close initial waypoint")
else self.current_target = table.remove(wp,1)
mcl_log("Nil pos") end
if self.current_target and self.current_target.pos then
self:turn_in_direction(self.current_target.pos.x - p.x, self.current_target.pos.z - p.z, 2)
self.waypoints = wp
self.state = PATHFINDING
return true
end end
self.current_target = current_location
self.waypoints = wp
self.state = PATHFINDING
return true
else
self.state = "walk"
self.waypoints = nil
self.current_target = nil
-- minetest.log("no path found")
end end
self:turn_in_direction(target.x - p.x, target.z - p.z, 4)
self.state = "walk"
self.waypoints = nil
self.current_target = nil
--minetest.log("no path found")
end end
function mob_class:interact_with_door(action, target) function mob_class:interact_with_door(action, target)
@ -300,19 +352,27 @@ function mob_class:interact_with_door(action, target)
local n = minetest.get_node(target) local n = minetest.get_node(target)
if n.name:find("_b_") or n.name:find("_t_") then if n.name:find("_b_") or n.name:find("_t_") then
mcl_log("Door")
local def = minetest.registered_nodes[n.name] local def = minetest.registered_nodes[n.name]
local closed = n.name:find("_b_1") or n.name:find("_t_1") local meta = minetest.get_meta(target)
--if self.state == PATHFINDING then local closed = meta:get_int("is_open") == 0
if closed and action == "open" and def.on_rightclick then if closed and action == "open" and def.on_rightclick then
mcl_log("Open door") mcl_log("Open door")
def.on_rightclick(target,n,self) def.on_rightclick(target,n,self)
end elseif not closed and action == "close" and def.on_rightclick then
if not closed and action == "close" and def.on_rightclick then mcl_log("Close door")
mcl_log("Close door") def.on_rightclick(target,n,self)
def.on_rightclick(target,n,self) end
end elseif n.name:find("_gate") then
--else local def = minetest.registered_nodes[n.name]
local meta = minetest.get_meta(target)
local closed = meta:get_int("state") == 0
if closed and action == "open" and def.on_rightclick then
mcl_log("Open gate")
def.on_rightclick(target,n,self)
elseif not closed and action == "close" and def.on_rightclick then
mcl_log("Close gate")
def.on_rightclick(target,n,self)
end
else else
mcl_log("Not door") mcl_log("Not door")
end end
@ -333,6 +393,7 @@ function mob_class:do_pathfind_action(action)
end end
if type and type == "door" then if type and type == "door" then
mcl_log("Type is door") mcl_log("Type is door")
self.object:set_velocity(vector.zero())
self:interact_with_door(action_val, target) self:interact_with_door(action_val, target)
end end
end end
@ -343,8 +404,7 @@ function mob_class:check_gowp(dtime)
-- no destination -- no destination
if not p or not self._target then if not p or not self._target then
mcl_log("p: ".. tostring(p)) mcl_log("p: ".. tostring(p)..", self._target: ".. tostring(self._target))
mcl_log("self._target: ".. tostring(self._target))
return return
end end
@ -358,8 +418,8 @@ function mob_class:check_gowp(dtime)
self.current_target = nil self.current_target = nil
self.state = "stand" self.state = "stand"
self.order = "stand" self.order = "stand"
self.object:set_velocity({x = 0, y = 0, z = 0}) self.object:set_velocity(vector.zero())
self.object:set_acceleration({x = 0, y = 0, z = 0}) self.object:set_acceleration(vector.zero())
if self.callback_arrived then return self.callback_arrived(self) end if self.callback_arrived then return self.callback_arrived(self) end
return true return true
elseif not self.current_target then elseif not self.current_target then
@ -368,41 +428,67 @@ function mob_class:check_gowp(dtime)
-- More pathing to be done -- More pathing to be done
local distance_to_current_target = 50 local distance_to_current_target = 50
if self.current_target and self.current_target["pos"] then if self.current_target and self.current_target.pos then
distance_to_current_target = vector.distance(p,self.current_target["pos"]) local dx, dy, dz = self.current_target.pos.x-p.x, self.current_target.pos.y-p.y, self.current_target.pos.z-p.z
distance_to_current_target = (dx*dx+dy*dy*0.5+dz*dz)^0.5 -- reduced weight on y
--distance_to_current_target = vector.distance(p,self.current_target.pos)
end
-- also check next target, maybe we were too fast
local next_target = #self.waypoints > 1 and self.waypoints[1]
if not self.current_target["action"] and next_target and next_target.pos and distance_to_current_target < 1.5 then
local dx, dy, dz = next_target.pos.x-p.x, next_target.pos.y-p.y, next_target.pos.z-p.z
local distance_to_next_target = (dx*dx+dy*dy*0.5+dz*dz)^0.5 -- reduced weight on y
if distance_to_next_target < distance_to_current_target then
mcl_log("Skipped one waypoint.")
self.current_target = table.remove(self.waypoints, 1) -- pop waypoint already
distance_to_current_target = distance_to_next_target
end
end
-- debugging tool
if visualize and self.current_target and self.current_target.pos then
core.add_particle({pos = self.current_target.pos, expirationtime=.1, size=3, velocity=vector.new(0,-0.2,0), texture="mcl_particles_flame.png"})
end end
-- 0.6 is working but too sensitive. sends villager back too frequently. 0.7 is quite good, but not with heights -- 0.6 is working but too sensitive. sends villager back too frequently. 0.7 is quite good, but not with heights
-- 0.8 is optimal for 0.025 frequency checks and also 1... Actually. 0.8 is winning -- 0.8 is optimal for 0.025 frequency checks and also 1... Actually. 0.8 is winning
-- 0.9 and 1.0 is also good. Stick with unless door open or closing issues -- 0.9 and 1.0 is also good. Stick with unless door open or closing issues
if self.waypoints and #self.waypoints > 0 and ( not self.current_target or not self.current_target["pos"] or distance_to_current_target < 0.9 ) then local threshold = self.current_target["action"] and 0.7 or 0.9
if self.waypoints and #self.waypoints > 0 and ( not self.current_target or not self.current_target.pos or distance_to_current_target < threshold ) then
-- We have waypoints, and are at current_target or have no current target. We need a new current_target. -- We have waypoints, and are at current_target or have no current target. We need a new current_target.
self:do_pathfind_action (self.current_target["action"]) self:do_pathfind_action (self.current_target["action"])
local failed_attempts = self.current_target["failed_attempts"] local failed_attempts = self.current_target["failed_attempts"]
mcl_log("There after " .. failed_attempts .. " failed attempts. current target:".. minetest.pos_to_string(self.current_target["pos"]) .. ". Distance: " .. distance_to_current_target) mcl_log("There after " .. failed_attempts .. " failed attempts. current target:".. minetest.pos_to_string(self.current_target.pos) .. ". Distance: " .. distance_to_current_target)
local hurry = (self.order == "sleep" or #self.waypoints > 15) and self.run_velocity or self.walk_velocity
self.current_target = table.remove(self.waypoints, 1) self.current_target = table.remove(self.waypoints, 1)
self:go_to_pos(self.current_target["pos"]) -- use smoothing -- TODO: check for blockers before cutting corners?
if #self.waypoints > 0 and not self.current_target["action"] then
local curwp, nextwp = self.current_target.pos, self.waypoints[1].pos
self:go_to_pos(vector.new(curwp.x*0.7+nextwp.x*0.3,curwp.y,curwp.z*0.7+nextwp.z*0.3), hurry)
return
end
self:go_to_pos(self.current_target.pos, hurry)
--if self.current_target["action"] then self:set_velocity(self.walk_velocity * 0.5) end
return return
elseif self.current_target and self.current_target["pos"] then elseif self.current_target and self.current_target.pos then
-- No waypoints left, but have current target and not close enough. Potentially last waypoint to go to. -- No waypoints left, but have current target and not close enough. Potentially last waypoint to go to.
self.current_target["failed_attempts"] = self.current_target["failed_attempts"] + 1 self.current_target["failed_attempts"] = self.current_target["failed_attempts"] + 1
local failed_attempts = self.current_target["failed_attempts"] local failed_attempts = self.current_target["failed_attempts"]
if failed_attempts >= PATHFINDING_FAIL_THRESHOLD then if failed_attempts >= PATHFINDING_FAIL_THRESHOLD then
mcl_log("Failed to reach position (" .. minetest.pos_to_string(self.current_target["pos"]) .. ") too many times. Abandon route. Times tried: " .. failed_attempts) mcl_log("Failed to reach position " .. minetest.pos_to_string(self.current_target.pos) .. " too many times. At: "..minetest.pos_to_string(p).." Abandon route. Times tried: " .. failed_attempts .. " current distance "..distance_to_current_target)
self.state = "stand" self.state = "stand"
self.current_target = nil self.current_target = nil
self.waypoints = nil self.waypoints = nil
self._target = nil self._target = nil
self._pf_last_failed = os.time() self._pf_last_failed = os.time()
self.object:set_velocity({x = 0, y = 0, z = 0}) self.object:set_velocity(vector.zero())
self.object:set_acceleration({x = 0, y = 0, z = 0}) self.object:set_acceleration(vector.zero())
return return
end end
--mcl_log("Not at pos with failed attempts ".. failed_attempts ..": ".. minetest.pos_to_string(p) .. "self.current_target: ".. minetest.pos_to_string(self.current_target["pos"]) .. ". Distance: ".. distance_to_current_target) --mcl_log("Not at pos with failed attempts ".. failed_attempts ..": ".. minetest.pos_to_string(p) .. "self.current_target: ".. minetest.pos_to_string(self.current_target.pos) .. ". Distance: ".. distance_to_current_target)
self:go_to_pos(self.current_target["pos"]) self:go_to_pos(self.current_target["pos"])
-- Do i just delete current_target, and return so we can find final path. -- Do i just delete current_target, and return so we can find final path.
else else
@ -436,6 +522,7 @@ function mob_class:check_gowp(dtime)
-- I don't think we need the following anymore, but test first. -- I don't think we need the following anymore, but test first.
-- Maybe just need something to path to target if no waypoints left -- Maybe just need something to path to target if no waypoints left
--[[ ok, let's try
if self.current_target and self.current_target["pos"] and (self.waypoints and #self.waypoints == 0) then if self.current_target and self.current_target["pos"] and (self.waypoints and #self.waypoints == 0) then
local updated_p = self.object:get_pos() local updated_p = self.object:get_pos()
local distance_to_cur_targ = vector.distance(updated_p,self.current_target["pos"]) local distance_to_cur_targ = vector.distance(updated_p,self.current_target["pos"])
@ -444,7 +531,7 @@ function mob_class:check_gowp(dtime)
mcl_log("Current p: ".. minetest.pos_to_string(updated_p)) mcl_log("Current p: ".. minetest.pos_to_string(updated_p))
-- 1.6 is good. is 1.9 better? It could fail less, but will it path to door when it isn't after door -- 1.6 is good. is 1.9 better? It could fail less, but will it path to door when it isn't after door
if distance_to_cur_targ > 1.9 then if distance_to_cur_targ > 1.6 then
mcl_log("not close to current target: ".. minetest.pos_to_string(self.current_target["pos"])) mcl_log("not close to current target: ".. minetest.pos_to_string(self.current_target["pos"]))
self:go_to_pos(self._current_target) self:go_to_pos(self._current_target)
else else
@ -454,4 +541,5 @@ function mob_class:check_gowp(dtime)
end end
return return
end end
--]]--
end end

View File

@ -6,6 +6,18 @@ local ENTITY_CRAMMING_MAX = 24
local CRAMMING_DAMAGE = 3 local CRAMMING_DAMAGE = 3
local DEATH_DELAY = 0.5 local DEATH_DELAY = 0.5
local DEFAULT_FALL_SPEED = -9.81*1.5 local DEFAULT_FALL_SPEED = -9.81*1.5
local PI = math.pi
local HALFPI = 0.5 * PI
local TWOPI = 2 * PI -- aka tau, but not very common
local random = math.random
local min = math.min
local max = math.max
local floor = math.floor
local abs = math.abs
local atan2 = math.atan2
local sin = math.sin
local cos = math.cos
local node_ok = mcl_mobs.node_ok
local PATHFINDING = "gowp" local PATHFINDING = "gowp"
local mobs_debug = minetest.settings:get_bool("mobs_debug", false) local mobs_debug = minetest.settings:get_bool("mobs_debug", false)
@ -13,20 +25,6 @@ local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
local mob_active_range = tonumber(minetest.settings:get("mcl_mob_active_range")) or 48 local mob_active_range = tonumber(minetest.settings:get("mcl_mob_active_range")) or 48
local show_health = false local show_health = false
-- get node but use fallback for nil or unknown
local node_ok = function(pos, fallback)
fallback = fallback or mcl_mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return minetest.registered_nodes[fallback]
end
-- check if within physical map limits (-30911 to 30927) -- check if within physical map limits (-30911 to 30927)
local function within_limits(pos, radius) local function within_limits(pos, radius)
local wmin, wmax = -30912, 30928 local wmin, wmax = -30912, 30928
@ -56,9 +54,7 @@ end
-- Return true if object is in view_range -- Return true if object is in view_range
function mob_class:object_in_range(object) function mob_class:object_in_range(object)
if not object then if not object then return false end
return false
end
local factor local factor
-- Apply view range reduction for special player armor -- Apply view range reduction for special player armor
if object:is_player() then if object:is_player() then
@ -110,24 +106,21 @@ function mob_class:item_drop(cooked, looting_level)
local num = 0 local num = 0
local do_common_looting = (looting_level > 0 and looting_type == "common") local do_common_looting = (looting_level > 0 and looting_type == "common")
if math.random() < chance then if random() < chance then
num = math.random(dropdef.min or 1, dropdef.max or 1) num = random(dropdef.min or 1, dropdef.max or 1)
elseif not dropdef.looting_ignore_chance then elseif not dropdef.looting_ignore_chance then
do_common_looting = false do_common_looting = false
end end
if do_common_looting then if do_common_looting then
num = num + math.floor(math.random(0, looting_level) + 0.5) num = num + floor(random(0, looting_level) + 0.5)
end end
if num > 0 then if num > 0 then
item = dropdef.name item = dropdef.name
if cooked then if cooked then
local output = minetest.get_craft_result({method = "cooking", width = 1, items = {item}})
local output = minetest.get_craft_result({
method = "cooking", width = 1, items = {item}})
if output and output.item and not output.item:is_empty() then if output and output.item and not output.item:is_empty() then
item = output.item:get_name() item = output.item:get_name()
end end
@ -135,17 +128,12 @@ function mob_class:item_drop(cooked, looting_level)
for x = 1, num do for x = 1, num do
obj = minetest.add_item(pos, ItemStack(item .. " " .. 1)) obj = minetest.add_item(pos, ItemStack(item .. " " .. 1))
end
if obj and obj:get_luaentity() then if obj and obj:get_luaentity() then
obj:set_velocity(vector.new((random() - 0.5) * 1.5, 6, (random() - 0.5) * 1.5))
obj:set_velocity({ elseif obj then
x = math.random(-10, 10) / 9, obj:remove() -- item does not exist
y = 6, end
z = math.random(-10, 10) / 9,
})
elseif obj then
obj:remove() -- item does not exist
end end
end end
end end
@ -156,32 +144,29 @@ end
-- collision function borrowed amended from jordan4ibanez open_ai mod -- collision function borrowed amended from jordan4ibanez open_ai mod
function mob_class:collision() function mob_class:collision()
local pos = self.object:get_pos() local pos = self.object:get_pos()
if not pos then return {0,0} end if not pos then return 0,0 end
local vel = self.object:get_velocity() local vel = self.object:get_velocity()
local x = 0 local x, z = 0, 0
local z = 0
local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5 local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5
for _,object in pairs(minetest.get_objects_inside_radius(pos, width)) do for _,object in pairs(minetest.get_objects_inside_radius(pos, width)) do
local ent = object:get_luaentity() local ent = object:get_luaentity()
if object:is_player() or (ent and ent.is_mob and object ~= self.object) then if object:is_player() or (ent and ent.is_mob and object ~= self.object) then
if object:is_player() and mcl_burning.is_burning(self.object) then if object:is_player() and mcl_burning.is_burning(self.object) then
mcl_burning.set_on_fire(object, 4) mcl_burning.set_on_fire(object, 4)
end end
local pos2 = object:get_pos() local pos2 = object:get_pos()
local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z} local vx, vz = pos.x - pos2.x, pos.z - pos2.z
local force = (width + 0.5) - vector.distance( local force = width - (vx*vx+vz*vz)^0.5
{x = pos.x, y = 0, z = pos.z}, if force > 0 then
{x = pos2.x, y = 0, z = pos2.z}) 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"))
x = x + (vec.x * force) x = x + vx * force
z = z + (vec.z * force) z = z + vz * force
end
end end
end end
return x, z
return({x,z})
end end
function mob_class:check_death_and_slow_mob() function mob_class:check_death_and_slow_mob()
@ -192,196 +177,113 @@ function mob_class:check_death_and_slow_mob()
local v = self.object:get_velocity() local v = self.object:get_velocity()
if v then if v then
--diffuse object velocity --diffuse object velocity
self.object:set_velocity({x = v.x*d, y = v.y, z = v.z*d}) self.object:set_velocity(vector.new(v.x*d, v.y, v.z*d))
end end
return dying return dying
end end
-- move mob in facing direction -- move mob in facing direction
function mob_class:set_velocity(v) function mob_class:set_velocity(v)
if not v then return end local c_x, c_z = 0, 0
local c_x, c_y = 0, 0
-- can mob be pushed, if so calculate direction -- can mob be pushed, if so calculate direction
if self.pushable then if self.pushable then
c_x, c_y = unpack(self:collision()) c_x, c_z = self:collision()
end end
if v > 0 then
-- halt mob if it has been ordered to stay local yaw = (self.object:get_yaw() or 0) + self.rotate
if self.order == "stand" or self.order == "sit" then local x = ((-sin(yaw) * v) + c_x) * .4
self.acc = vector.zero() local z = (( cos(yaw) * v) + c_z) * .4
return if not self.acc then
end self.acc = vector.new(x, 0, z)
else
local yaw = (self.object:get_yaw() or 0) + self.rotate self.acc.x, self.acc.y, self.acc.z = x, 0, z
local vv = self.object:get_velocity() end
else -- allow standing mobs to be pushed
if vv and yaw then if not self.acc then
self.acc = vector.new(((math.sin(yaw) * -v) + c_x) * .4, 0, ((math.cos(yaw) * v) + c_y) * .4) self.acc = vector.new(c_x * .2, 0, c_z * .2)
else
self.acc.x, self.acc.y, self.acc.z = c_x * .2, 0, c_z * .2
end
end end
end end
-- calculate mob velocity -- calculate mob velocity (2d)
function mob_class:get_velocity() function mob_class:get_velocity()
local v = self.object:get_velocity() local v = self.object:get_velocity()
if v then if not v then return 0 end
return (v.x * v.x + v.z * v.z) ^ 0.5 return (v.x*v.x + v.z*v.z)^0.5
end
return 0
end end
function mob_class:update_roll() function mob_class:update_roll()
local is_Fleckenstein = self.nametag == "Fleckenstein" local is_Fleckenstein = self.nametag == "Fleckenstein"
local was_Fleckenstein = false if not is_Fleckenstein and not self.is_Fleckenstein then return end
local rot = self.object:get_rotation() local rot = self.object:get_rotation()
rot.z = is_Fleckenstein and pi or 0 rot.z = is_Fleckenstein and PI or 0
self.object:set_rotation(rot) self.object:set_rotation(rot)
local cbox = table.copy(self.collisionbox) if is_Fleckenstein ~= self.is_Fleckenstein then
local acbox = self.object:get_properties().collisionbox
if math.abs(cbox[2] - acbox[2]) > 0.1 then
was_Fleckenstein = true
end
if is_Fleckenstein ~= was_Fleckenstein then
local pos = self.object:get_pos() local pos = self.object:get_pos()
pos.y = pos.y + (acbox[2] + acbox[5]) local cbox = is_Fleckenstein and table.copy(self.collisionbox) or self.object:get_properties().collisionbox
pos.y = pos.y + (cbox[2] + cbox[5])
cbox[2], cbox[5] = -cbox[5], -cbox[2]
-- This leads to child mobs having the wrong collisionbox
-- and seeing as it seems to be nothing but an easter egg
-- i've put it inside the if. Which just makes it be upside
-- down lol.
self.object:set_properties({collisionbox = cbox})
self.object:set_pos(pos) self.object:set_pos(pos)
end end
self.is_Fleckenstein = is_Fleckenstein
if is_Fleckenstein then
cbox[2], cbox[5] = -cbox[5], -cbox[2]
self.object:set_properties({collisionbox = cbox})
-- This leads to child mobs having the wrong collisionbox
-- and seeing as it seems to be nothing but an easter egg
-- i've put it inside the if. Which just makes it be upside
-- down lol.
end
end end
local function shortest_term_of_yaw_rotation(self, rot_origin, rot_target, nums) -- Relative turn, primarily for random turning
-- @param dtime deprecated: ignored now, because of smooth rotations
if not rot_origin or not rot_target then function mob_class:turn_by(angle, delay, dtime)
return return self:set_yaw((self.object:get_yaw() or 0) + angle, delay, dtime)
end end
-- Turn into a direction (e.g., to the player, or away)
rot_origin = math.deg(rot_origin) -- @param dtime deprecated: ignored now, because of smooth rotations
rot_target = math.deg(rot_target) function mob_class:turn_in_direction(dx, dz, delay, dtime)
if abs(dx) == 0 and abs(dz) == 0 then return self.object:get_yaw() + self.rotate end
if rot_origin < rot_target then return self:set_yaw(-atan2(dx, dz) - self.rotate, delay, dtime) + self.rotate
if math.abs(rot_origin-rot_target)<180 then
if nums then
return rot_target-rot_origin
else
return 1
end
else
if nums then
return -(rot_origin-(rot_target-360))
else
return -1
end
end
else
if math.abs(rot_origin-rot_target)<180 then
if nums then
return rot_target-rot_origin
else
return -1
end
else
if nums then
return (rot_target-(rot_origin-360))
else
return 1
end
end
end
end end
-- set and return valid yaw -- set and return valid yaw
-- @param dtime deprecated: ignored now, because of smooth rotations
function mob_class:set_yaw(yaw, delay, dtime) function mob_class:set_yaw(yaw, delay, dtime)
if self.noyaw then return end if self.noyaw then return end
if self._kb_turn then return yaw end -- knockback in effect
if not self.object:get_yaw() or not self.object:get_pos() then return end if not self.object:get_yaw() or not self.object:get_pos() then return end
self.delay = delay or 0
if self.state ~= PATHFINDING then self.target_yaw = yaw % TWOPI
self._turn_to = yaw
end
--mcl_log("Yaw is: \t\t" .. tostring(math.deg(yaw)))
--mcl_log("self.object:get_yaw() is: \t" .. tostring(math.deg(self.object:get_yaw())))
--clamp our yaw to a 360 range
if math.deg(self.object:get_yaw()) > 360 then
self.object:set_yaw(math.rad(0))
elseif math.deg(self.object:get_yaw()) < 0 then
self.object:set_yaw(math.rad(360))
end
if math.deg(yaw) > 360 then
yaw=math.rad(math.deg(yaw)%360)
elseif math.deg(yaw) < 0 then
yaw=math.rad(((360*5)-math.deg(yaw))%360)
end
--calculate the shortest way to turn to find our target
local target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), yaw, false)
local target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), yaw, true)
--turn in the shortest path possible toward our target. if we are attacking, don't dance.
if (math.abs(target_shortest_path) > 50 and not self._kb_turn) and (self.attack and self.attack:get_pos() or self.following and self.following:get_pos()) then
if self.following then
target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.following:get_pos())), true)
target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.following:get_pos())), false)
else
target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.attack:get_pos())), true)
target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.attack:get_pos())), false)
end
end
local ddtime = 0.05 --set_tick_rate
if dtime then
ddtime = dtime
end
if math.abs(target_shortest_path_nums) > 10 then
self.object:set_yaw(self.object:get_yaw()+(target_shortest_path*(3.6*ddtime)))
if validate_vector(self.acc) then
self.acc=vector.rotate_around_axis(self.acc,vector.new(0,1,0), target_shortest_path*(3.6*ddtime))
end
end
delay = delay or 0
yaw = self.object:get_yaw()
if delay == 0 then
if self.shaking and dtime then
yaw = yaw + (math.random() * 2 - 1) * 5 * dtime
end
--self:update_roll()
return yaw
end
self.target_yaw = yaw
self.delay = delay
return self.target_yaw return self.target_yaw
end end
-- global function to set mob yaw -- improved smooth rotation
function mcl_mobs.yaw(self, yaw, delay, dtime) function mob_class:check_smooth_rotation(dtime)
return mob_class.set_yaw(self, yaw, delay, dtime) if not self.target_yaw then return end
local delay = self.delay
local initial_yaw = self.object:get_yaw() or 0
local yaw -- resulting yaw for this tick
if delay and delay > 1 then
local dif = (self.target_yaw - initial_yaw + PI) % TWOPI - PI
yaw = (initial_yaw + dif / delay) % TWOPI
self.delay = delay - 1
else
yaw = self.target_yaw
end
if self.shaking then
yaw = yaw + (random() * 2 - 1) / 72 * dtime
end
--[[ needed? if self.acc then
local change = yaw - initial_yaw
local si, co = sin(change), cos(change)
self.acc.x, self.acc.y = co * self.acc.x - si * self.acc.y, si * self.acc.x + co * self.acc.y
end ]]--
self.object:set_yaw(yaw)
self:update_roll()
end end
-- are we flying in what we are suppose to? (taikedz) -- are we flying in what we are suppose to? (taikedz)
@ -489,7 +391,7 @@ function mob_class:check_for_death(cause, cmi_cause)
if ((not self.child) or self.type ~= "animal") and (minetest.get_us_time() - self.xp_timestamp <= math.huge) then if ((not self.child) or self.type ~= "animal") and (minetest.get_us_time() - self.xp_timestamp <= math.huge) then
local pos = self.object:get_pos() local pos = self.object:get_pos()
local xp_amount = math.random(self.xp_min, self.xp_max) local xp_amount = random(self.xp_min, self.xp_max)
if not mcl_sculk.handle_death(pos, xp_amount) then if not mcl_sculk.handle_death(pos, xp_amount) then
--minetest.log("Xp not thrown") --minetest.log("Xp not thrown")
@ -562,7 +464,7 @@ function mob_class:check_for_death(cause, cmi_cause)
elseif self.animation and self.animation.die_start and self.animation.die_end then elseif self.animation and self.animation.die_start and self.animation.die_end then
local frames = self.animation.die_end - self.animation.die_start local frames = self.animation.die_end - self.animation.die_start
local speed = self.animation.die_speed or 15 local speed = self.animation.die_speed or 15
length = math.max(frames / speed, 0) + DEATH_DELAY length = max(frames / speed, 0) + DEATH_DELAY
self:set_animation( "die") self:set_animation( "die")
else else
length = 1 + DEATH_DELAY length = 1 + DEATH_DELAY
@ -672,7 +574,7 @@ function mob_class:do_env_damage()
-- what is mob standing in? -- what is mob standing in?
pos.y = pos.y + y_level + 0.25 -- foot level pos.y = pos.y + y_level + 0.25 -- foot level
local pos2 = {x=pos.x, y=pos.y-1, z=pos.z} local pos2 = vector.new(pos.x, pos.y-1, pos.z)
self.standing_in = node_ok(pos, "air").name self.standing_in = node_ok(pos, "air").name
self.standing_on = node_ok(pos2, "air").name self.standing_on = node_ok(pos2, "air").name
@ -681,7 +583,7 @@ function mob_class:do_env_damage()
-- don't fall when on ignore, just stand still -- don't fall when on ignore, just stand still
if self.standing_in == "ignore" then if self.standing_in == "ignore" then
self.object:set_velocity({x = 0, y = 0, z = 0}) self.object:set_velocity(vector.zero())
-- wither rose effect -- wither rose effect
elseif self.standing_in == "mcl_flowers:wither_rose" then elseif self.standing_in == "mcl_flowers:wither_rose" then
mcl_potions.give_effect_by_level("withering", self.object, 2, 2) mcl_potions.give_effect_by_level("withering", self.object, 2, 2)
@ -799,7 +701,7 @@ function mob_class:do_env_damage()
end end
if drowning then if drowning then
self.breath = math.max(0, self.breath - 1) self.breath = max(0, self.breath - 1)
mcl_mobs.effect(pos, 2, "bubble.png", nil, nil, 1, nil) mcl_mobs.effect(pos, 2, "bubble.png", nil, nil, 1, nil)
if self.breath <= 0 then if self.breath <= 0 then
local dmg local dmg
@ -816,7 +718,7 @@ function mob_class:do_env_damage()
return true return true
end end
else else
self.breath = math.min(self.breath_max, self.breath + 1) self.breath = min(self.breath_max, self.breath + 1)
end end
end end
@ -882,7 +784,7 @@ end
function mob_class:damage_mob(reason,damage) function mob_class:damage_mob(reason,damage)
if not self.health then return end if not self.health then return end
damage = math.floor(damage) damage = floor(damage)
if damage > 0 then if damage > 0 then
self.health = self.health - damage self.health = self.health - damage
@ -928,57 +830,45 @@ end
-- falling and fall damage -- falling and fall damage
-- returns true if mob died -- returns true if mob died
function mob_class:falling(pos, moveresult) function mob_class:falling(pos, moveresult)
if self.fly and self.state ~= "die" then if self.fly and self.state ~= "die" then return end
return
end
if not self.fall_speed then self.fall_speed = DEFAULT_FALL_SPEED end if not self.fall_speed then self.fall_speed = DEFAULT_FALL_SPEED end
-- Gravity
local v = self.object:get_velocity()
if v then
if v.y > 0 or (v.y <= 0 and v.y > self.fall_speed) then
-- fall downwards at set speed
if moveresult and moveresult.touching_ground then
-- when touching ground, retain a minimal gravity to keep the touching_ground flag
-- but also to not get upwards acceleration with large dtime when on bouncy ground
self.object:set_acceleration(vector.new(0, self.fall_speed * 0.01, 0))
else
self.object:set_acceleration(vector.new(0, self.fall_speed, 0))
end
else
-- stop accelerating once max fall speed hit
self.object:set_acceleration(vector.zero())
end
end
if mcl_portals ~= nil then if mcl_portals ~= nil then
if mcl_portals.nether_portal_cooloff(self.object) then if mcl_portals.nether_portal_cooloff(self.object) then
return false -- mob has teleported through Nether portal - it's 99% not falling return false -- mob has teleported through Nether portal - it's 99% not falling
end end
end end
-- floating in water (or falling)
local v = self.object:get_velocity()
if v then
local new_acceleration
if v.y > 0 then
-- apply gravity when moving up
new_acceleration = vector.new(0, DEFAULT_FALL_SPEED, 0)
elseif v.y <= 0 and v.y > self.fall_speed then
-- fall downwards at set speed
if moveresult and moveresult.touching_ground then
-- when touching ground, retain a minimal gravity to keep the touching_ground flag
-- but also to not get upwards acceleration with large dtime when on bouncy ground
new_acceleration = vector.new(0, self.fall_speed * 0.01, 0)
else
new_acceleration = vector.new(0, self.fall_speed, 0)
end
else
-- stop accelerating once max fall speed hit
new_acceleration =vector.zero()
end
self.object:set_acceleration(new_acceleration)
end
local acc = self.object:get_acceleration()
local registered_node = minetest.registered_nodes[node_ok(pos).name] local registered_node = minetest.registered_nodes[node_ok(pos).name]
if registered_node.groups.lava then if registered_node.groups.lava then
if acc and self.floats_on_lava == 1 then if self.floats_on_lava == 1 then
self.object:set_acceleration(vector.new(0, -self.fall_speed / (math.max(1, v.y) ^ 2), 0)) self.object:set_acceleration(vector.new(0, -self.fall_speed / max(1, v.y^2), 0))
end end
end end
-- in water then float up -- in water then float up
if registered_node.groups.water then if registered_node.groups.water then
if acc and self.floats == 1 and minetest.registered_nodes[node_ok(vector.offset(pos,0,self.collisionbox[5] -0.25,0)).name].groups.water then if self.floats == 1 and minetest.registered_nodes[node_ok(vector.offset(pos,0,self.collisionbox[5] -0.25,0)).name].groups.water then
self.object:set_acceleration(vector.new(0, -self.fall_speed / (math.max(1, v.y) ^ 2), 0)) self.object:set_acceleration(vector.new(0, -self.fall_speed / max(1, v.y^2), 0))
end end
else else
-- fall damage onto solid ground -- fall damage onto solid ground
@ -1002,13 +892,9 @@ end
function mob_class:check_water_flow() function mob_class:check_water_flow()
-- Add water flowing for mobs from mcl_item_entity -- Add water flowing for mobs from mcl_item_entity
local p, node, nn, def local p = self.object:get_pos()
p = self.object:get_pos() local node = minetest.get_node_or_nil(p)
node = minetest.get_node_or_nil(p) local def = node and minetest.registered_nodes[node.name]
if node then
nn = node.name
def = minetest.registered_nodes[nn]
end
-- Move item around on flowing liquids -- Move item around on flowing liquids
if def and def.liquidtype == "flowing" then if def and def.liquidtype == "flowing" then
@ -1023,14 +909,12 @@ function mob_class:check_water_flow()
local f = 1.39 local f = 1.39
-- Set new item moving speed into the direciton of the liquid -- Set new item moving speed into the direciton of the liquid
local newv = vector.multiply(vec, f) local newv = vector.multiply(vec, f)
self.object:set_acceleration({x = 0, y = 0, z = 0}) self.object:set_acceleration(vector.zero())
self.object:set_velocity({x = newv.x, y = -0.22, z = newv.z}) self.object:set_velocity(vector.new(newv.x, -0.22, newv.z))
self.physical_state = true self.physical_state = true
self._flowing = true self._flowing = true
self.object:set_properties({ self.object:set_properties({ physical = true })
physical = true
})
return return
end end
elseif self._flowing == true then elseif self._flowing == true then
@ -1044,7 +928,7 @@ function mob_class:check_dying()
if ((self.state and self.state=="die") or self:check_for_death()) and not self.animation.die_end then if ((self.state and self.state=="die") or self:check_for_death()) and not self.animation.die_end then
local rot = self.object:get_rotation() local rot = self.object:get_rotation()
if rot then if rot then
rot.z = ((math.pi/2-rot.z)*.2)+rot.z rot.z = ((HALFPI - rot.z) * .2) + rot.z
self.object:set_rotation(rot) self.object:set_rotation(rot)
end end
return true return true

View File

@ -12,9 +12,8 @@ local axolotl = {
xp_max = 7, xp_max = 7,
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = -1, head_eye_height = 0.5,
head_eye_height = -0.5, head_bone_position = vector.new( 0, -1, 0 ), -- for minetest <= 5.8
horizontal_head_height = 0,
curiosity = 10, curiosity = 10,
head_yaw="z", head_yaw="z",

View File

@ -26,14 +26,14 @@ mcl_mobs.register_mob("mobs_mc:blaze", {
xp_min = 10, xp_min = 10,
xp_max = 10, xp_max = 10,
collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.79, 0.3}, collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.79, 0.3},
rotate = -180, rotate = 180,
head_yaw_offset = 180,
visual = "mesh", visual = "mesh",
mesh = "mobs_mc_blaze.b3d", mesh = "mobs_mc_blaze.b3d",
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 4, head_eye_height = 1.4,
head_eye_height = 3.5, head_bone_position = vector.new( 0, 4, 0 ), -- for minetest <= 5.8
curiosity = 10, curiosity = 10,
head_yaw_offset = 180,
head_pitch_multiplier=-1, head_pitch_multiplier=-1,
textures = { textures = {
{"mobs_mc_blaze.png"}, {"mobs_mc_blaze.png"},

View File

@ -21,9 +21,8 @@ mcl_mobs.register_mob("mobs_mc:chicken", {
collisionbox = {-0.2, -0.01, -0.2, 0.2, 0.69, 0.2}, collisionbox = {-0.2, -0.01, -0.2, 0.2, 0.69, 0.2},
floats = 1, floats = 1,
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 4, head_eye_height = 0.5,
head_eye_height = 1.5, head_bone_position = vector.new(0, 4, -.3), -- for minetest <= 5.8
horizontal_head_height = -.3,
curiosity = 10, curiosity = 10,
head_yaw="z", head_yaw="z",
visual_size = {x=1,y=1}, visual_size = {x=1,y=1},

View File

@ -22,9 +22,8 @@ local cow_def = {
"blank.png", "blank.png",
}, }, }, },
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 10,
head_eye_height = 1.1, head_eye_height = 1.1,
horizontal_head_height=-1.8, head_bone_position = vector.new( 0, 6.3, 0 ), -- for minetest <= 5.8
curiosity = 2, curiosity = 2,
head_yaw="z", head_yaw="z",
makes_footstep_sound = true, makes_footstep_sound = true,

View File

@ -25,7 +25,8 @@ mcl_mobs.register_mob("mobs_mc:iron_golem", {
visual = "mesh", visual = "mesh",
mesh = "mobs_mc_iron_golem.b3d", mesh = "mobs_mc_iron_golem.b3d",
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 3.38, head_eye_height = 2.5,
head_bone_position = vector.new( 0, 3.38, 0 ), -- for minetest <= 5.8
curiosity = 10, curiosity = 10,
textures = { textures = {
{"mobs_mc_iron_golem.png"}, {"mobs_mc_iron_golem.png"},

View File

@ -60,11 +60,10 @@ mcl_mobs.register_mob("mobs_mc:llama", {
spawn_in_group = 4, -- was 6 nerfed until we can cap them properly locally. this is a group size, not a per spawn attempt spawn_in_group = 4, -- was 6 nerfed until we can cap them properly locally. this is a group size, not a per spawn attempt
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 11, head_eye_height = 1.5,
head_eye_height = 3,
horizontal_head_height=0,
curiosity = 60,
head_yaw = "z", head_yaw = "z",
head_bone_position = vector.new( 0, 11, 0 ), -- for minetest <= 5.8
curiosity = 60,
hp_min = 15, hp_min = 15,
hp_max = 30, hp_max = 30,

View File

@ -37,9 +37,8 @@ local ocelot = {
xp_min = 1, xp_min = 1,
xp_max = 3, xp_max = 3,
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 6.2,
head_eye_height = 0.4, head_eye_height = 0.4,
horizontal_head_height=-0, head_bone_position = vector.new( 0, 6.2, 0 ), -- for minetest <= 5.8
head_yaw="z", head_yaw="z",
curiosity = 4, curiosity = 4,
collisionbox = {-0.3, -0.01, -0.3, 0.3, 0.69, 0.3}, collisionbox = {-0.3, -0.01, -0.3, 0.3, 0.69, 0.3},

View File

@ -136,8 +136,7 @@ mcl_mobs.register_mob("mobs_mc:parrot", {
xp_min = 1, xp_min = 1,
xp_max = 3, xp_max = 3,
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 1.1, head_bone_position = vector.new( 0, 1.1, 0 ), -- for minetest <= 5.8
horizontal_head_height=0,
curiosity = 10, curiosity = 10,
collisionbox = {-0.25, -0.01, -0.25, 0.25, 0.89, 0.25}, collisionbox = {-0.25, -0.01, -0.25, 0.25, 0.89, 0.25},
visual = "mesh", visual = "mesh",

View File

@ -20,9 +20,8 @@ mcl_mobs.register_mob("mobs_mc:pig", {
"blank.png", -- saddle "blank.png", -- saddle
}}, }},
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 7.5, head_eye_height = 0.7,
head_eye_height = 0.8, head_bone_position = vector.new( 0, 7.5, -1 ), -- for minetest <= 5.8
horizontal_head_height=-1,
curiosity = 3, curiosity = 3,
head_yaw="z", head_yaw="z",
makes_footstep_sound = true, makes_footstep_sound = true,

View File

@ -252,7 +252,7 @@ local zombified_piglin = {
damage = 9, damage = 9,
reach = 2, reach = 2,
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 2.4, head_bone_position = vector.new( 0, 2.4, 0 ), -- for minetest <= 5.8
head_eye_height = 1.4, head_eye_height = 1.4,
curiosity = 15, curiosity = 15,
collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3}, -- same collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3}, -- same
@ -325,6 +325,7 @@ mcl_mobs.register_mob("mobs_mc:zombified_piglin", zombified_piglin)
local baby_zombified_piglin = table.copy(zombified_piglin) local baby_zombified_piglin = table.copy(zombified_piglin)
baby_zombified_piglin.description = S("Baby Zombie Piglin") baby_zombified_piglin.description = S("Baby Zombie Piglin")
baby_zombified_piglin.collisionbox = {-0.25, -0.01, -0.25, 0.25, 0.94, 0.25} baby_zombified_piglin.collisionbox = {-0.25, -0.01, -0.25, 0.25, 0.94, 0.25}
baby_zombified_piglin.head_eye_height = 0.8
baby_zombified_piglin.xp_min = 13 baby_zombified_piglin.xp_min = 13
baby_zombified_piglin.xp_max = 13 baby_zombified_piglin.xp_max = 13
baby_zombified_piglin.textures = { baby_zombified_piglin.textures = {

View File

@ -1,125 +1,125 @@
local S = minetest.get_translator("mobs_mc") local S = minetest.get_translator("mobs_mc")
local function reload(self) local function reload(self)
if not self.object:get_pos() then return end if not self.object:get_pos() then return end
minetest.sound_play("mcl_bows_crossbow_drawback_1", {object = self.object, max_hear_distance=16}, true) minetest.sound_play("mcl_bows_crossbow_drawback_1", {object = self.object, max_hear_distance=16}, true)
local props = self.object:get_properties() local props = self.object:get_properties()
if not props then return end if not props then return end
props.textures[2] = "mcl_bows_crossbow_3.png^[resize:16x16" props.textures[2] = "mcl_bows_crossbow_3.png^[resize:16x16"
self.object:set_properties(props) self.object:set_properties(props)
end end
local function reset_animation(self, animation) local function reset_animation(self, animation)
if not self.object:get_pos() or self._current_animation ~= animation then return end if not self.object:get_pos() or self._current_animation ~= animation then return end
self._current_animation = "stand_reload" -- Mobs Redo won't set the animation unless we do this self._current_animation = "stand_reload" -- Mobs Redo won't set the animation unless we do this
self:set_animation(animation) self:set_animation(animation)
end end
pillager = { pillager = {
description = S("Pillager"), description = S("Pillager"),
type = "monster", type = "monster",
spawn_class = "hostile", spawn_class = "hostile",
hp_min = 24, hp_min = 24,
hp_max = 24, hp_max = 24,
xp_min = 6, xp_min = 6,
xp_max = 6, xp_max = 6,
breath_max = -1, breath_max = -1,
eye_height = 1.5, eye_height = 1.5,
shoot_interval = 3, shoot_interval = 3,
shoot_offset = 1.5, shoot_offset = 1.5,
armor = {fleshy = 100}, armor = {fleshy = 100},
can_despawn = false, can_despawn = false,
collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.98, 0.3}, collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.98, 0.3},
pathfinding = 1, pathfinding = 1,
group_attack = true, group_attack = true,
visual = "mesh", visual = "mesh",
mesh = "mobs_mc_pillager.b3d", mesh = "mobs_mc_pillager.b3d",
visual_size = {x=2.75, y=2.75}, visual_size = {x=2.75, y=2.75},
makes_footstep_sound = true, makes_footstep_sound = true,
walk_velocity = 1.2, walk_velocity = 1.2,
run_velocity = 4, run_velocity = 4,
view_range = 16, view_range = 16,
fear_height = 4, fear_height = 4,
arrow = "mcl_bows:arrow_entity", arrow = "mcl_bows:arrow_entity",
attack_type = "dogshoot", -- Alternate punching/shooting attack_type = "dogshoot", -- Alternate punching/shooting
attack_npcs = true, attack_npcs = true,
reach = 0, -- Punching max distance reach = 2, -- Punching max distance
damage = 0, -- Punching damage damage = 2, -- Punching damage
dogshoot_switch = 1, -- Start of shooting dogshoot_switch = 1, -- Start of shooting
dogshoot_count_max = 5, -- Max time spent shooting (standing) dogshoot_count_max = 4, -- Max time spent shooting (standing)
dogshoot_count2_max = 1, -- Max time spent punching (running) dogshoot_count2_max = 1, -- Max time spent punching (running)
sounds = { sounds = {
random = "mobs_mc_pillager_grunt2", random = "mobs_mc_pillager_grunt2",
war_cry = "mobs_mc_pillager_grunt1", war_cry = "mobs_mc_pillager_grunt1",
death = "mobs_mc_pillager_ow2", death = "mobs_mc_pillager_ow2",
damage = "mobs_mc_pillager_ow1", damage = "mobs_mc_pillager_ow1",
distance = 16, distance = 16,
}, },
textures = { textures = {
{ {
"mobs_mc_pillager.png", -- Skin "mobs_mc_pillager.png", -- Skin
"mcl_bows_crossbow_3.png^[resize:16x16", -- Wielded item "mcl_bows_crossbow_3.png^[resize:16x16", -- Wielded item
} }
}, },
drops = { drops = {
{ {
name = "mcl_bows:arrow", name = "mcl_bows:arrow",
chance = 1, chance = 1,
min = 0, min = 0,
max = 2, max = 2,
looting = "common", looting = "common",
}, },
{ {
name = "mcl_bows:crossbow", name = "mcl_bows:crossbow",
chance = 100 / 8.5, chance = 100 / 8.5,
min = 1, min = 1,
max = 1, max = 1,
looting = "rare", looting = "rare",
}, },
}, },
animation = { animation = {
unloaded_walk_start = 1, unloaded_walk_end = 40, unloaded_walk_start = 1, unloaded_walk_end = 40,
unloaded_stand_start = 41, unloaded_stand_end = 60, unloaded_stand_start = 41, unloaded_stand_end = 60,
reload_stand_start = 61, reload_stand_end = 100, reload_stand_speed = 20, reload_stand_start = 61, reload_stand_end = 100, reload_stand_speed = 20,
stand_start = 101, stand_end = 109, stand_speed = 6, stand_start = 101, stand_end = 109, stand_speed = 6,
walk_start = 111, walk_end = 150, walk_speed = 30, walk_start = 111, walk_end = 150, walk_speed = 30,
run_start = 111, run_end = 150, run_speed = 50, run_start = 111, run_end = 150, run_speed = 50,
reload_run_start = 151, reload_run_end = 190, reload_run_speed = 20, reload_run_start = 151, reload_run_end = 190, reload_run_speed = 20,
die_start = 191, die_end = 192, die_speed = 15, die_start = 191, die_end = 192, die_speed = 15,
stand_unloaded_start = 40, stand_unloaded_end = 59, stand_unloaded_start = 40, stand_unloaded_end = 59,
die_loop = false, die_loop = false,
}, },
shoot_arrow = function(self, pos, dir) shoot_arrow = function(self, pos, dir)
minetest.sound_play("mcl_bows_crossbow_shoot", {object = self.object, max_hear_distance=16}, true) minetest.sound_play("mcl_bows_crossbow_shoot", {object = self.object, max_hear_distance=16}, true)
local props = self.object:get_properties() local props = self.object:get_properties()
props.textures[2] = "mcl_bows_crossbow_0.png^[resize:16x16" props.textures[2] = "mcl_bows_crossbow_0.png^[resize:16x16"
self.object:set_properties(props) self.object:set_properties(props)
local old_anim = self._current_animation local old_anim = self._current_animation
if old_anim == "run" or old_anim == "walk" then if old_anim == "run" or old_anim == "walk" then
self:set_animation("reload_run") self:set_animation("reload_run")
end end
if old_anim == "stand" then if old_anim == "stand" then
self:set_animation("reload_stand") self:set_animation("reload_stand")
end end
self._current_animation = old_anim -- Mobs Redo will imediately reset the animation otherwise self._current_animation = old_anim -- Mobs Redo will imediately reset the animation otherwise
minetest.after(1, reload, self) minetest.after(1, reload, self)
minetest.after(2, reset_animation, self, old_anim) minetest.after(2, reset_animation, self, old_anim)
-- 2-4 damage per arrow -- 2-4 damage per arrow
local dmg = math.max(4, math.random(2, 8)) local dmg = math.max(4, math.random(2, 8))
mcl_bows_s.shoot_arrow_crossbow("mcl_bows:arrow", pos, dir, self.object:get_yaw(), self.object, nil, dmg) mcl_bows_s.shoot_arrow_crossbow("mcl_bows:arrow", pos, dir, self.object:get_yaw(), self.object, nil, dmg)
-- While we are at it, change the sounds since there is no way to do this in Mobs Redo -- While we are at it, change the sounds since there is no way to do this in Mobs Redo
if self.sounds and self.sounds.random then if self.sounds and self.sounds.random then
self.sounds = table.copy(self.sounds) self.sounds = table.copy(self.sounds)
self.sounds.random = "mobs_mc_pillager_grunt" .. math.random(2) self.sounds.random = "mobs_mc_pillager_grunt" .. math.random(2)
end end
-- Randomize reload time -- Randomize reload time
self.shoot_interval = math.random(3, 4) self.shoot_interval = math.random(3, 4)
end, end,
} }
mcl_mobs.register_mob("mobs_mc:pillager", pillager) mcl_mobs.register_mob("mobs_mc:pillager", pillager)
mcl_mobs.register_egg("mobs_mc:pillager", S("Pillager"), "#532f36", "#959b9b", 0) mcl_mobs.register_egg("mobs_mc:pillager", S("Pillager"), "#532f36", "#959b9b", 0)
mcl_mobs:non_spawn_specific("mobs_mc:pillager","overworld",0,7) mcl_mobs:non_spawn_specific("mobs_mc:pillager","overworld",0,7)

View File

@ -25,9 +25,8 @@ mcl_mobs.register_mob("mobs_mc:polar_bear", {
{"mobs_mc_polarbear.png"}, {"mobs_mc_polarbear.png"},
}, },
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 2.6,
head_eye_height = 1, head_eye_height = 1,
horizontal_head_height = 0, head_bone_position = vector.new( 0, 2.6, 0 ), -- for minetest <= 5.8
curiosity = 20, curiosity = 20,
head_yaw="z", head_yaw="z",
visual_size = {x=3.0, y=3.0}, visual_size = {x=3.0, y=3.0},

View File

@ -16,20 +16,19 @@ local rabbit = {
xp_max = 3, xp_max = 3,
collisionbox = {-0.2, -0.01, -0.2, 0.2, 0.49, 0.2}, collisionbox = {-0.2, -0.01, -0.2, 0.2, 0.49, 0.2},
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 2, head_eye_height = 0.35,
head_eye_height = 0.5, head_bone_position = vector.new( 0, 2, -.3 ), -- for minetest <= 5.8
horizontal_head_height = -.3,
curiosity = 20, curiosity = 20,
head_yaw="z", head_yaw="z",
visual = "mesh", visual = "mesh",
mesh = "mobs_mc_rabbit.b3d", mesh = "mobs_mc_rabbit.b3d",
textures = { textures = {
{"mobs_mc_rabbit_brown.png"}, {"mobs_mc_rabbit_brown.png"},
{"mobs_mc_rabbit_gold.png"}, {"mobs_mc_rabbit_gold.png"},
{"mobs_mc_rabbit_white.png"}, {"mobs_mc_rabbit_white.png"},
{"mobs_mc_rabbit_white_splotched.png"}, {"mobs_mc_rabbit_white_splotched.png"},
{"mobs_mc_rabbit_salt.png"}, {"mobs_mc_rabbit_salt.png"},
{"mobs_mc_rabbit_black.png"}, {"mobs_mc_rabbit_black.png"},
}, },
sounds = { sounds = {
random = "mobs_mc_rabbit_random", random = "mobs_mc_rabbit_random",

View File

@ -65,9 +65,8 @@ mcl_mobs.register_mob("mobs_mc:sheep", {
xp_max = 3, xp_max = 3,
collisionbox = {-0.45, -0.01, -0.45, 0.45, 1.29, 0.45}, collisionbox = {-0.45, -0.01, -0.45, 0.45, 1.29, 0.45},
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 3.3, head_eye_height = 1.0,
head_eye_height = 1.1, head_bone_position = vector.new( 0, 3.3, -.7 ), -- for minetest <= 5.8
horizontal_head_height=-.7,
curiosity = 6, curiosity = 6,
head_yaw="z", head_yaw="z",
visual = "mesh", visual = "mesh",

View File

@ -83,7 +83,6 @@ mcl_mobs.register_mob("mobs_mc:shulker", {
local pos = self.object:get_pos() local pos = self.object:get_pos()
if math.floor(self.object:get_yaw()) ~=0 then if math.floor(self.object:get_yaw()) ~=0 then
self.object:set_yaw(0) self.object:set_yaw(0)
mcl_mobs:yaw(self, 0, 0, dtime)
end end
if self.state == "attack" then if self.state == "attack" then
self:set_animation("run") self:set_animation("run")

View File

@ -26,7 +26,8 @@ local skeleton = {
pathfinding = 1, pathfinding = 1,
group_attack = true, group_attack = true,
head_swivel = "Head_Control", head_swivel = "Head_Control",
bone_eye_height = 2.38, head_eye_height = 1.5,
head_bone_position = vector.new( 0, 2.38, 0 ), -- for minetest <= 5.8
curiosity = 6, curiosity = 6,
visual = "mesh", visual = "mesh",
mesh = "mobs_mc_skeleton.b3d", mesh = "mobs_mc_skeleton.b3d",

View File

@ -25,7 +25,8 @@ mcl_mobs.register_mob("mobs_mc:witherskeleton", {
visual = "mesh", visual = "mesh",
mesh = "mobs_mc_witherskeleton.b3d", mesh = "mobs_mc_witherskeleton.b3d",
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 2.38, head_eye_height = 1.5,
head_bone_position = vector.new( 0, 2.38, 0 ), -- for minetest <= 5.8
curiosity = 60, curiosity = 60,
textures = { textures = {
{ {

View File

@ -63,7 +63,8 @@ local spider = {
end end
end, end,
head_swivel = "Head_Control", head_swivel = "Head_Control",
bone_eye_height = 1, head_eye_height = 0.6,
head_bone_position = vector.new( 0, 1, 0 ), -- for minetest <= 5.8
curiosity = 10, curiosity = 10,
head_yaw="z", head_yaw="z",
collisionbox = {-0.7, -0.01, -0.7, 0.7, 0.89, 0.7}, collisionbox = {-0.7, -0.01, -0.7, 0.7, 0.89, 0.7},

View File

@ -75,8 +75,8 @@ mcl_mobs.register_mob("mobs_mc:stalker", {
visual = "mesh", visual = "mesh",
mesh = "vl_stalker.b3d", mesh = "vl_stalker.b3d",
-- head_swivel = "Head_Control", -- head_swivel = "Head_Control",
bone_eye_height = 2.35, head_eye_height = 1.2;
head_eye_height = 1.8; head_bone_position = vector.new( 0, 2.35, 0 ), -- for minetest <= 5.8
curiosity = 2, curiosity = 2,
textures = { textures = {
{get_texture({}), {get_texture({}),

View File

@ -952,6 +952,9 @@ local function go_home(entity, sleep)
entity.order = nil entity.order = nil
return return
end end
-- in case pathfinding fails, turn into the right direction anyways
local p = entity.object:get_pos()
entity:turn_in_direction(b.x - p.x, b.z - p.z, 8)
entity:gopath(b,function(entity,b) entity:gopath(b,function(entity,b)
local b = entity._bed local b = entity._bed
@ -1331,7 +1334,7 @@ local function do_work (self)
--mcl_log("Jobsite not valid") --mcl_log("Jobsite not valid")
return false return false
end end
if vector.distance(self.object:get_pos(),self._jobsite) < 2 then if vector.distance(self.object:get_pos(),self._jobsite) < 1.5 then
--mcl_log("Made it to work ok callback!") --mcl_log("Made it to work ok callback!")
return true return true
else else
@ -2110,8 +2113,8 @@ mcl_mobs.register_mob("mobs_mc:villager", {
hp_min = 20, hp_min = 20,
hp_max = 20, hp_max = 20,
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 6.3, head_eye_height = 1.5,
head_eye_height = 2.2, head_bone_position = vector.new( 0, 6.3, 0 ), -- for minetest <= 5.8
curiosity = 10, curiosity = 10,
runaway = true, runaway = true,
collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3}, collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3},

View File

@ -25,8 +25,8 @@ mcl_mobs.register_mob("mobs_mc:evoker", {
xp_min = 10, xp_min = 10,
xp_max = 10, xp_max = 10,
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 6.3, head_eye_height = 1.5,
head_eye_height = 2.2, head_bone_position = vector.new( 0, 6.3, 0 ), -- for minetest <= 5.8
curiosity = 10, curiosity = 10,
collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.95, 0.4}, collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.95, 0.4},
visual = "mesh", visual = "mesh",

View File

@ -34,8 +34,8 @@ mcl_mobs.register_mob("mobs_mc:illusioner", {
"mcl_bows_bow.png", "mcl_bows_bow.png",
}, }, }, },
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 2.2, head_eye_height = 1.5,
head_eye_height = 2.2, head_bone_position = vector.new( 0, 2.2, 0 ), -- for minetest <= 5.8
curiosity = 10, curiosity = 10,
sounds = { sounds = {
-- TODO: more sounds -- TODO: more sounds

View File

@ -24,17 +24,17 @@ mcl_mobs.register_mob("mobs_mc:vindicator", {
visual = "mesh", visual = "mesh",
mesh = "mobs_mc_vindicator.b3d", mesh = "mobs_mc_vindicator.b3d",
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 2.2, head_eye_height = 1.5,
head_eye_height = 2.2, head_bone_position = vector.new( 0, 2.2, 0 ), -- for minetest <= 5.8
curiosity = 10, curiosity = 10,
textures = { textures = {
{ {
"mobs_mc_vindicator.png", "mobs_mc_vindicator.png",
"blank.png", --no hat "blank.png", --no hat
"default_tool_steelaxe.png", "default_tool_steelaxe.png",
-- TODO: Glow when attacking (mobs_mc_vindicator.png) -- TODO: Glow when attacking (mobs_mc_vindicator.png)
}, },
}, },
visual_size = {x=2.75, y=2.75}, visual_size = {x=2.75, y=2.75},
makes_footstep_sound = true, makes_footstep_sound = true,
damage = 13, damage = 13,

View File

@ -40,7 +40,7 @@ mcl_mobs.register_mob("mobs_mc:villager_zombie", {
visual = "mesh", visual = "mesh",
mesh = "mobs_mc_villager_zombie.b3d", mesh = "mobs_mc_villager_zombie.b3d",
head_swivel = "Head_Control", head_swivel = "Head_Control",
bone_eye_height = 2.35, head_bone_position = vector.new( 0, 2.35, 0 ), -- for minetest <= 5.8
curiosity = 2, curiosity = 2,
textures = { textures = {
{"mobs_mc_zombie_butcher.png"}, {"mobs_mc_zombie_butcher.png"},

View File

@ -27,8 +27,8 @@ local wolf = {
}, },
makes_footstep_sound = true, makes_footstep_sound = true,
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 3.5, head_eye_height = 0.5,
head_eye_height = 1.1, head_bone_position = vector.new( 0, 3.5, 0 ), -- for minetest <= 5.8
horizontal_head_height=0, horizontal_head_height=0,
curiosity = 3, curiosity = 3,
head_yaw="z", head_yaw="z",

View File

@ -54,8 +54,8 @@ local zombie = {
xp_min = 5, xp_min = 5,
xp_max = 5, xp_max = 5,
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 6.3, head_eye_height = 1.4,
head_eye_height = 2.2, head_bone_position = vector.new( 0, 6.3, 0 ), -- for minetest <= 5.8
curiosity = 7, curiosity = 7,
head_pitch_multiplier=-1, head_pitch_multiplier=-1,
breath_max = -1, breath_max = -1,
@ -110,6 +110,7 @@ mcl_mobs.register_mob("mobs_mc:zombie", zombie)
local baby_zombie = table.copy(zombie) local baby_zombie = table.copy(zombie)
baby_zombie.description = S("Baby Zombie") baby_zombie.description = S("Baby Zombie")
baby_zombie.head_eye_height = 0.8
baby_zombie.collisionbox = {-0.25, -0.01, -0.25, 0.25, 0.98, 0.25} baby_zombie.collisionbox = {-0.25, -0.01, -0.25, 0.25, 0.98, 0.25}
baby_zombie.xp_min = 12 baby_zombie.xp_min = 12
baby_zombie.xp_max = 12 baby_zombie.xp_max = 12

View File

@ -418,6 +418,18 @@ minetest.register_on_joinplayer(function(player)
end end
end) end)
---@param player mt.PlayerObjectRef
local function is_touch_enabled(playername)
-- Minetest < 5.7.0 support
if not minetest.get_player_window_information then
return false
end
local window = minetest.get_player_window_information(playername)
-- Always return a boolean (not nil) to avoid false-negatives when
-- comparing to a boolean later.
return window and window.touch_controls or false
end
---@param player mt.PlayerObjectRef ---@param player mt.PlayerObjectRef
function mcl_inventory.set_creative_formspec(player) function mcl_inventory.set_creative_formspec(player)
local playername = player:get_player_name() local playername = player:get_player_name()
@ -566,8 +578,10 @@ function mcl_inventory.set_creative_formspec(player)
bg_img = "crafting_creative_inactive" .. button_bg_postfix[this_tab] .. ".png" bg_img = "crafting_creative_inactive" .. button_bg_postfix[this_tab] .. ".png"
end end
return table.concat({ return table.concat({
"style[" .. this_tab .. ";border=false;bgimg=;bgimg_pressed=;noclip=true]", "style[" .. this_tab .. ";border=false;bgimg=;bgimg_pressed=]",
"image[" .. offset[this_tab] .. ";1.5,1.44;" .. bg_img .. "]", "style[" .. this_tab .. "_outer;border=false;bgimg=" .. bg_img ..
";bgimg_pressed=" .. bg_img .. "]",
"button[" .. offset[this_tab] .. ";1.5,1.44;" .. this_tab .. "_outer;]",
"item_image_button[" .. boffset[this_tab] .. ";1,1;" .. tab_icon[this_tab] .. ";" .. this_tab .. ";]", "item_image_button[" .. boffset[this_tab] .. ";1,1;" .. tab_icon[this_tab] .. ";" .. this_tab .. ";]",
}) })
end end
@ -577,11 +591,21 @@ function mcl_inventory.set_creative_formspec(player)
caption = "label[0.375,0.375;" .. F(C(mcl_formspec.label_color, filtername[name])) .. "]" caption = "label[0.375,0.375;" .. F(C(mcl_formspec.label_color, filtername[name])) .. "]"
end end
local touch_enabled = is_touch_enabled(playername)
players[playername].last_touch_enabled = touch_enabled
local formspec = table.concat({ local formspec = table.concat({
"formspec_version[6]", "formspec_version[6]",
"size[13,8.75]", -- Original formspec height was 8.75, increased to include tab buttons.
-- This avoids tab buttons going off-screen with high scaling values.
"size[13,11.43]",
-- Use as much space as possible on mobile - the tab buttons are a lot
-- of padding already.
touch_enabled and "padding[-0.015,-0.015]" or "",
"style_type[image;noclip=true]", "no_prepend[]", mcl_vars.gui_nonbg, mcl_vars.gui_bg_color,
"background9[0,1.34;13,8.75;mcl_base_textures_background9.png;;7]",
"container[0,1.34]",
-- Hotbar -- Hotbar
mcl_formspec.get_itemslot_bg_v4(0.375, 7.375, 9, 1), mcl_formspec.get_itemslot_bg_v4(0.375, 7.375, 9, 1),
@ -638,6 +662,7 @@ function mcl_inventory.set_creative_formspec(player)
"set_focus[search;true]", "set_focus[search;true]",
}) })
end end
formspec = formspec .. "container_end[]"
if pagenum then formspec = formspec .. "p" .. tostring(pagenum) end if pagenum then formspec = formspec .. "p" .. tostring(pagenum) end
player:set_inventory_formspec(formspec) player:set_inventory_formspec(formspec)
end end
@ -655,54 +680,54 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local name = player:get_player_name() local name = player:get_player_name()
if fields.blocks then if fields.blocks or fields.blocks_outer then
if players[name].page == "blocks" then return end if players[name].page == "blocks" then return end
set_inv_page("blocks", player) set_inv_page("blocks", player)
page = "blocks" page = "blocks"
elseif fields.deco then elseif fields.deco or fields.deco_outer then
if players[name].page == "deco" then return end if players[name].page == "deco" then return end
set_inv_page("deco", player) set_inv_page("deco", player)
page = "deco" page = "deco"
elseif fields.redstone then elseif fields.redstone or fields.redstone_outer then
if players[name].page == "redstone" then return end if players[name].page == "redstone" then return end
set_inv_page("redstone", player) set_inv_page("redstone", player)
page = "redstone" page = "redstone"
elseif fields.rail then elseif fields.rail or fields.rail_outer then
if players[name].page == "rail" then return end if players[name].page == "rail" then return end
set_inv_page("rail", player) set_inv_page("rail", player)
page = "rail" page = "rail"
elseif fields.misc then elseif fields.misc or fields.misc_outer then
if players[name].page == "misc" then return end if players[name].page == "misc" then return end
set_inv_page("misc", player) set_inv_page("misc", player)
page = "misc" page = "misc"
elseif fields.nix then elseif fields.nix or fields.nix_outer then
set_inv_page("all", player) set_inv_page("all", player)
page = "nix" page = "nix"
elseif fields.food then elseif fields.food or fields.food_outer then
if players[name].page == "food" then return end if players[name].page == "food" then return end
set_inv_page("food", player) set_inv_page("food", player)
page = "food" page = "food"
elseif fields.tools then elseif fields.tools or fields.tools_outer then
if players[name].page == "tools" then return end if players[name].page == "tools" then return end
set_inv_page("tools", player) set_inv_page("tools", player)
page = "tools" page = "tools"
elseif fields.combat then elseif fields.combat or fields.combat_outer then
if players[name].page == "combat" then return end if players[name].page == "combat" then return end
set_inv_page("combat", player) set_inv_page("combat", player)
page = "combat" page = "combat"
elseif fields.mobs then elseif fields.mobs or fields.mobs_outer then
if players[name].page == "mobs" then return end if players[name].page == "mobs" then return end
set_inv_page("mobs", player) set_inv_page("mobs", player)
page = "mobs" page = "mobs"
elseif fields.brew then elseif fields.brew or fields.brew_outer then
if players[name].page == "brew" then return end if players[name].page == "brew" then return end
set_inv_page("brew", player) set_inv_page("brew", player)
page = "brew" page = "brew"
elseif fields.matr then elseif fields.matr or fields.matr_outer then
if players[name].page == "matr" then return end if players[name].page == "matr" then return end
set_inv_page("matr", player) set_inv_page("matr", player)
page = "matr" page = "matr"
elseif fields.inv then elseif fields.inv or fields.inv_outer then
if players[name].page == "inv" then return end if players[name].page == "inv" then return end
page = "inv" page = "inv"
elseif fields.search == "" and not fields.creative_next and not fields.creative_prev then elseif fields.search == "" and not fields.creative_next and not fields.creative_prev then
@ -818,3 +843,19 @@ minetest.register_on_player_inventory_action(function(player, action, inventory,
player:get_inventory():set_stack("main", inventory_info.index, stack) player:get_inventory():set_stack("main", inventory_info.index, stack)
end end
end) end)
-- This is necessary because get_player_window_information may return nil in
-- on_joinplayer.
-- (Also, Minetest plans to add support for toggling touchscreen mode in-game.)
minetest.register_globalstep(function(dtime)
for _, player in pairs(minetest.get_connected_players()) do
local name = player:get_player_name()
if minetest.is_creative_enabled(name) then
local touch_enabled = is_touch_enabled(name)
if touch_enabled ~= players[name].last_touch_enabled then
mcl_inventory.set_creative_formspec(player)
end
end
end
end)

View File

@ -1,6 +1,5 @@
# textdomain: mcl_bone_meal # textdomain: mcl_bone_meal
Bone Meal=Farine d'Os Bone Meal=Farine d'Os
Bone meal is a white dye and also useful as a fertilizer to speed up the growth of many plants.=La farine d'os est une teinture blanche et est également utile comme engrais pour accélérer la croissance de nombreuses plantes. Bone meal is a white dye and also useful as a fertilizer to speed up the growth of many plants.=La farine d'os est une teinture blanche et est également utile comme engrais pour accélérer la croissance de nombreuses plantes.
Rightclick a sheep to turn its wool white. Rightclick a plant to speed up its growth. Note that not all plants can be fertilized like this. When you rightclick a grass block, tall grass and flowers will grow all over the place.= Rightclick a sheep to turn its wool white. Rightclick a plant to speed up its growth. Note that not all plants can be fertilized like this. When you rightclick a grass block, tall grass and flowers will grow all over the place.=Cliquez avec le bouton droit sur un mouton pour blanchir sa laine. Cliquez avec le bouton droit sur une plante pour accélérer sa croissance. Cependant, toutes les plantes ne peuvent pas être fertilisées de cette manière. Lorsque vous cliquez avec le bouton droit sur un bloc d'herbe, les hautes herbes et les fleurs poussent autour.
Cliquez avec le bouton droit sur un mouton pour blanchir sa laine. Cliquez avec le bouton droit sur une plante pour accélérer sa croissance. Cependant, toutes les plantes ne peuvent pas être fertilisées de cette manière. Lorsque vous cliquez avec le bouton droit sur un bloc d'herbe, les hautes herbes et les fleurs poussent autour.
Speeds up plant growth=Accélère la croissance des plantes Speeds up plant growth=Accélère la croissance des plantes

View File

@ -169,6 +169,7 @@ S("The speed and damage of the arrow increases the longer you charge. The regula
return itemstack return itemstack
end, end,
groups = {weapon=1,weapon_ranged=1,bow=1,cannot_block=1,enchantability=1}, groups = {weapon=1,weapon_ranged=1,bow=1,cannot_block=1,enchantability=1},
touch_interaction = "short_dig_long_place",
_mcl_uses = 385, _mcl_uses = 385,
}) })
@ -235,6 +236,7 @@ for level=0, 2 do
on_place = function(itemstack) on_place = function(itemstack)
return itemstack return itemstack
end, end,
touch_interaction = "short_dig_long_place",
_mcl_uses = 385, _mcl_uses = 385,
}) })
end end

View File

@ -159,6 +159,7 @@ S("The speed and damage of the arrow increases the longer you charge. The regula
return itemstack return itemstack
end, end,
groups = {weapon=1,weapon_ranged=1,crossbow=1,cannot_block=1,enchantability=1}, groups = {weapon=1,weapon_ranged=1,crossbow=1,cannot_block=1,enchantability=1},
touch_interaction = "short_dig_long_place",
_mcl_uses = 326, _mcl_uses = 326,
}) })
@ -194,6 +195,7 @@ S("The speed and damage of the arrow increases the longer you charge. The regula
return itemstack return itemstack
end, end,
groups = {weapon=1,weapon_ranged=1,crossbow=1,cannot_block=1,enchantability=1,not_in_creative_inventory=1}, groups = {weapon=1,weapon_ranged=1,crossbow=1,cannot_block=1,enchantability=1,not_in_creative_inventory=1},
touch_interaction = "short_dig_long_place",
_mcl_uses = 326, _mcl_uses = 326,
}) })
@ -257,6 +259,7 @@ for level=0, 2 do
on_place = function(itemstack) on_place = function(itemstack)
return itemstack return itemstack
end, end,
touch_interaction = "short_dig_long_place",
_mcl_uses = 385, _mcl_uses = 385,
}) })
end end

View File

@ -6,6 +6,7 @@ minetest.register_tool("mcl_spyglass:spyglass",{
inventory_image = "mcl_spyglass.png", inventory_image = "mcl_spyglass.png",
stack_max = 1, stack_max = 1,
_mcl_toollike_wield = true, _mcl_toollike_wield = true,
touch_interaction = "short_dig_long_place",
}) })
minetest.register_craft({ minetest.register_craft({