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_meta = {__index = mcl_mobs.mob_class}
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
-- API for Mobs Redo: VoxeLibre Edition
local PATHFINDING = "gowp"
local CRASH_WARN_FREQUENCY = 60
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 DEVELOPMENT = minetest.settings:get_bool("mcl_development",false)
-- Invisibility mod check
mcl_mobs.invis = {}
local remove_far = true
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 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
local spawn_logging = minetest.settings:get_bool("mcl_logging_mobs_spawn", true)
local DEVELOPMENT = minetest.settings:get_bool("mcl_development", false)
-- Peaceful mode message so players will know there are no monsters
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
local tag
if mobs_debug then
local name = self.name
if self.nametag and self.nametag ~= "" then
name = self.nametag
end
local name = self.nametag ~= "" and self.nametag or self.name
tag = "name = '"..tostring(name).."'\n"..
"state = '"..tostring(self.state).."'\n"..
"order = '"..tostring(self.order).."'\n"..
@ -56,9 +48,7 @@ function mob_class:update_tag() --update nametag and/or the debug box
else
tag = self.nametag
end
self.object:set_properties({
nametag = tag,
})
self.object:set_properties({ nametag = tag })
end
function mob_class:jock_to(mob, reletive_pos, rot)
@ -74,19 +64,15 @@ function mob_class:jock_to(mob, reletive_pos, rot)
end
function mob_class:get_staticdata()
for _,p in pairs(minetest.get_connected_players()) do
self:remove_particlespawners(p:get_player_name())
end
-- remove mob when out of range unless tamed
if remove_far
and self:despawn_allowed()
and self.lifetimer <= 20 then
if remove_far and self:despawn_allowed() and self.lifetimer <= 20 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")
end
return "remove"-- nil
end
@ -95,17 +81,9 @@ function mob_class:get_staticdata()
self.state = "stand"
local tmp = {}
for tag, stat in pairs(self) do
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
tmp._mcl_potions = self._mcl_potions
@ -120,10 +98,7 @@ function mob_class:get_staticdata()
end
local function valid_texture(self, def_textures)
if not self.base_texture then
return false
end
if not self.base_texture then return false end
if self.texture_selected then
if #def_textures < self.texture_selected then
self.texture_selected = nil
@ -148,32 +123,18 @@ function mob_class:mob_activate(staticdata, def, dtime)
end
local tmp = minetest.deserialize(staticdata)
if tmp then
-- Patch incorrectly converted mobs
if tmp.base_mesh ~= minetest.registered_entities[self.name].mesh then
mcl_mobs.strip_staticdata(tmp)
end
for _,stat in pairs(tmp) do
self[_] = stat
end
if tmp.base_mesh ~= minetest.registered_entities[self.name].mesh then mcl_mobs.strip_staticdata(tmp) end
for _, stat in pairs(tmp) do self[_] = stat end
end
--If textures in definition change, reload textures
if not valid_texture(self, def.textures) then
-- compatiblity with old simple mobs textures
if type(def.textures[1]) == "string" then
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
if type(def.textures[1]) == "string" then def.textures = {def.textures} end
self.texture_selected = self.texture_selected or math.random(#def.textures)
self.base_texture = def.textures[self.texture_selected]
self.base_mesh = def.mesh
self.base_size = self.visual_size
@ -181,9 +142,7 @@ function mob_class:mob_activate(staticdata, def, dtime)
self.base_selbox = self.selectionbox
end
if not self.base_selbox then
self.base_selbox = self.selectionbox or self.base_colbox
end
self.base_selbox = self.base_selbox or self.selectionbox or self.base_colbox
local textures = self.base_texture
local mesh = self.base_mesh
@ -191,26 +150,11 @@ function mob_class:mob_activate(staticdata, def, dtime)
local colbox = self.base_colbox
local selbox = self.base_selbox
if self.gotten == true
and def.gotten_texture then
textures = def.gotten_texture
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
if self.gotten and def.gotten_texture then textures = def.gotten_texture end
if self.gotten and def.gotten_mesh then mesh = def.gotten_mesh end
if self.child 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 = {
self.base_colbox[1] * .5,
@ -230,16 +174,12 @@ function mob_class:mob_activate(staticdata, def, dtime)
}
end
if self.health == 0 then
self.health = math.random (self.hp_min, self.hp_max)
end
if self.breath == nil then
self.breath = self.breath_max
end
if self.health == 0 then self.health = math.random(self.hp_min, self.hp_max) end
if self.breath == nil then self.breath = self.breath_max end
self.path = {}
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.following = false -- currently following 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.blinkstatus = false
if not self.nametag then
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.nametag = self.nametag or def.nametag
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._current_animation = nil
self:set_animation( "stand")
self:set_animation("stand")
if self.riden_by_jock then --- Keep this function before self.on_spawn() is run.
if self.riden_by_jock then --- Keep this function before self:on_spawn()
self.object:remove()
return
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 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.wears_armor and self.armor_list then self.armor_list = nil end
if not self._run_armor_init and self.wears_armor then
self.armor_list={helmet="",chestplate="",boots="",leggings=""}
@ -319,15 +239,10 @@ function mob_class:mob_activate(staticdata, def, dtime)
self._run_armor_init = true
end
if not self._mcl_potions then
self._mcl_potions = {}
end
if not self._mcl_potions then self._mcl_potions = {} end
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
-- 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
self:check_gowp(dtime)
elseif self.state == "attack" then
if self:do_states_attack(dtime) then
return true
end
if self:do_states_attack(dtime) then return true end
else
if mcl_util.check_dtime_timer(self, dtime, "onstep_dostates", 1) then
if self.state == "stand" then
@ -364,34 +277,28 @@ end
function mob_class:outside_limits()
local pos = self.object:get_pos()
if pos then
local posx = math.abs(pos.x)
local posy = math.abs(pos.y)
local posz = math.abs(pos.z)
if posx > MAPGEN_MOB_LIMIT or posy > MAPGEN_MOB_LIMIT or posz > MAPGEN_MOB_LIMIT then
--minetest.log("action", "Getting close to limits of worldgen: " .. minetest.pos_to_string(pos))
if posx > MAPGEN_LIMIT or posy > MAPGEN_LIMIT or posz > MAPGEN_LIMIT then
minetest.log("action", "Warning mob past limits of worldgen: " .. minetest.pos_to_string(pos))
else
if self.state ~= "stand" then
minetest.log("action", "Warning mob close to limits of worldgen: " .. minetest.pos_to_string(pos))
self.state = "stand"
self:set_animation("stand")
self.object:set_acceleration(vector.zero())
self.object:set_velocity(vector.zero())
end
if not pos then return end
local posx, posy, posz = math.abs(pos.x), math.abs(pos.y), math.abs(pos.z)
if posx > MAPGEN_MOB_LIMIT or posy > MAPGEN_MOB_LIMIT or posz > MAPGEN_MOB_LIMIT then
--minetest.log("action", "Getting close to limits of worldgen: " .. minetest.pos_to_string(pos))
if posx > MAPGEN_LIMIT or posy > MAPGEN_LIMIT or posz > MAPGEN_LIMIT then
minetest.log("action", "Warning mob past limits of worldgen: " .. minetest.pos_to_string(pos))
else
if self.state ~= "stand" then
minetest.log("action", "Warning mob close to limits of worldgen: " .. minetest.pos_to_string(pos))
self.state = "stand"
self:set_animation("stand")
self.object:set_acceleration(vector.zero())
self.object:set_velocity(vector.zero())
end
return true
end
return true
end
end
local function on_step_work(self, dtime, moveresult)
local pos = self.object:get_pos()
if not pos then return end
if self:check_despawn(pos, dtime) then return true end
if self:outside_limits() then return end
@ -403,29 +310,20 @@ local function on_step_work(self, dtime, moveresult)
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
-- End: Death/damage processing
local player_in_active_range = self:player_in_active_range()
self:check_suspend(player_in_active_range)
self:check_water_flow()
if not self._jumping_cliff then
self._can_jump_cliff = self:can_jump_cliff()
else
self._can_jump_cliff = false
end
self._can_jump_cliff = not self._jumping_cliff and self:can_jump_cliff()
self:flop()
self:check_smooth_rotation(dtime)
if player_in_active_range then
self:set_animation_speed() -- set animation speed relative to velocity
self:check_head_swivel(dtime)
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
if mcl_util.check_dtime_timer(self, dtime, "onstep_occassional", 1) then
if player_in_active_range then
self:check_item_pickup()
self:set_armor_texture()
self:step_opinion_sound(dtime)
end
self:check_breeding()
end
self:check_aggro(dtime)
self:check_particlespawners(dtime)
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 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
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", "Error: " .. stack_trace)
minetest.log("action", "Bug info: " .. info)
if info2 then
minetest.log("action", "Bug info additional: " .. info2)
end
if info2 then minetest.log("action", "Bug info additional: " .. info2) end
minetest.log("action", "--- Bug report end ---")
end
local function warn_user_error ()
local current_time = os.time()
local time_since_warning = current_time - last_crash_warn_time
--minetest.log("previous_crash_time: " .. current_time)
--minetest.log("last_crash_time: " .. last_crash_warn_time)
--minetest.log("time_since_warning: " .. time_since_warning)
if time_since_warning > CRASH_WARN_FREQUENCY then
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)")
end
end
local on_step_error_handler = function ()
warn_user_error ()
local on_step_error_handler = function()
warn_user_error()
local info = debug.getinfo(1, "SnlufL")
log_error(tostring(debug.traceback()), dump(info))
end
-- main mob function
function mob_class:on_step(dtime, moveresult)
if not DEVELOPMENT then
-- Removed as bundled Lua (5.1 doesn't support xpcall)
--local status, retVal = xpcall(on_step_work, on_step_error_handler, self, dtime)
local status, retVal = pcall(on_step_work, self, dtime, moveresult)
if status then
return retVal
else
warn_user_error ()
local pos = self.object:get_pos()
if pos then
local node = minetest.get_node(pos)
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)
-- allow crash in development mode
if DEVELOPMENT then return on_step_work(self, dtime, moveresult) end
-- Removed as bundled Lua (5.1 doesn't support xpcall)
--local status, retVal = xpcall(on_step_work, on_step_error_handler, self, dtime)
local status, retVal = pcall(on_step_work, self, dtime, moveresult)
if status then return retVal end
warn_user_error()
local pos = self.object:get_pos()
if pos then
local node = minetest.get_node(pos)
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
local timer = 0
local function update_lifetimer(dtime)
timer = timer + dtime
if timer < 1 then return end
@ -547,7 +422,6 @@ minetest.register_globalstep(function(dtime)
update_lifetimer(dtime)
end)
minetest.register_chatcommand("clearmobs", {
privs = { maphack = true },
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"))
end
local mob, unsafe = param:match("^([%w]+)[ ]?([%w%d]*)$")
local all = false
local nametagged = false
local tamed = false
local all, nametagged, tamed = false, false, false
local mob_name, mob_type, range
-- Param 1 resolve
@ -578,12 +448,7 @@ minetest.register_chatcommand("clearmobs", {
end
--minetest.log ("mob: [" .. mob .. "]")
else
--minetest.log("No valid first param")
if default then
--minetest.log("Use default")
mob_type = "monster"
end
--return
if default then mob_type = "monster" end
end
-- Param 2 resolve
@ -600,7 +465,6 @@ minetest.register_chatcommand("clearmobs", {
end
local p = minetest.get_player_by_name(player)
for _,o in pairs(minetest.luaentities) do
if o and o.is_mob then
local mob_match = false
@ -609,7 +473,6 @@ minetest.register_chatcommand("clearmobs", {
--minetest.log("Match - All mobs specified")
mob_match = true
elseif mob_type then
--minetest.log("Match - o.type: ".. tostring(o.type))
--minetest.log("mob_type: ".. tostring(mob_type))
if mob_type == "monster" and o.type == mob_type then
@ -621,7 +484,6 @@ minetest.register_chatcommand("clearmobs", {
else
--minetest.log("No match for type.")
end
elseif mob_name and (o.name == mob_name or string.find(o.name, mob_name)) then
--minetest.log("Match - mob_name = ".. tostring(o.name))
mob_match = true
@ -632,35 +494,16 @@ minetest.register_chatcommand("clearmobs", {
end
if mob_match then
local in_range = true
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))
local in_range = (not range or range <= 0) or vector.distance(p:get_pos(), o.object:get_pos()) <= range
if nametagged then
if o.nametag then
--minetest.log("Namedtagged and it has a name tag. Kill it")
o.object:remove()
end
if o.nametag then o.object:remove() end
elseif tamed then
if o.tamed then
--minetest.log("Tamed. Kill it")
o.object:remove()
end
if o.tamed then o.object:remove() end
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()
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
if not consume_food and self.child == true then
if not consume_food and self.child then
consume_food = true
-- deduct 10% of the time to adulthood
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")
-- 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
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 TIME_TO_FORGET_TARGET = 15
local atann = math.atan
local function atan(x)
if not x or x ~= x then
return 0
else
return atann(x)
end
end
local PI = math.pi
local HALFPI = PI * 0.5
local random = math.random
local min = math.min
local floor = math.floor
local ceil = math.ceil
local abs = math.abs
local cos = math.cos
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
function mob_class:day_docile()
if self.docile_by_day == false then
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
return self.docile_by_day == true and self.time_of_day > 0.2 and self.time_of_day < 0.8
end
-- get this mob to attack the object
function mob_class:do_attack(object)
if self.state == "attack" or self.state == "die" then
return
end
if object:is_player() and not minetest.settings:get_bool("enable_damage") then
return
end
if self.state == "attack" or self.state == "die" then return end
if object:is_player() and not minetest.settings:get_bool("enable_damage") then return end
self.attack = object
self.state = "attack"
@ -55,21 +46,18 @@ function mob_class:do_attack(object)
end
-- blast damage to entities nearby
local function entity_physics(pos,radius)
local function entity_physics(pos, radius)
radius = radius * 2
local objs = minetest.get_objects_inside_radius(pos, radius)
local obj_pos, dist
for n = 1, #objs do
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
local damage = math.floor((4 / dist) * radius)
local damage = floor((4 / dist) * radius)
local ent = objs[n]:get_luaentity()
-- 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
function mob_class:smart_mobs(s, p, dist, dtime)
local s1 = self.path.lastpos
local target_pos = self.attack:get_pos()
-- 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
else
self.path.stuck_timer = 0
end
self.path.lastpos = {x = s.x, y = s.y, z = s.z}
self.path.lastpos = vector_copy(s)
local use_pathfind = false
local has_lineofsight = minetest.line_of_sight(
{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)
local has_lineofsight = minetest.line_of_sight(vector_offset(s, 0, .5, 0), vector_offset(target_pos, 0, 1.5, 0), .2)
-- im stuck, search for path
if not has_lineofsight then
if los_switcher == true then
use_pathfind = true
los_switcher = false
end -- cannot see target!
else
if los_switcher == false then
los_switcher = true
use_pathfind = false
minetest.after(1, function(self)
if not self.object:get_luaentity() then
return
end
if not self.object:get_luaentity() then return end
if has_lineofsight then self.path.following = false end
end, self)
end -- can see target!
end
if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
use_pathfind = true
self.path.stuck_timer = 0
minetest.after(1, function(self)
if not self.object:get_luaentity() then
return
end
if not self.object:get_luaentity() then return end
if has_lineofsight then self.path.following = false end
end, self)
end
if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
use_pathfind = true
self.path.stuck_timer = 0
minetest.after(1, function(self)
if not self.object:get_luaentity() then
return
end
if not self.object:get_luaentity() then return end
if has_lineofsight then self.path.following = false end
end, self)
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
use_pathfind = true
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
-- also adjust height for player models!
s.x = math.floor(s.x + 0.5)
s.z = math.floor(s.z + 0.5)
s.x, s.z = floor(s.x + 0.5), floor(s.z + 0.5)
local ssight, sground = minetest.line_of_sight(s, {
x = s.x, y = s.y - 4, z = s.z}, 1)
local ssight, sground = minetest.line_of_sight(s, vector_offset(s, 0, -4, 0), 1)
-- determine node above ground
if not ssight then
s.y = sground.y + 1
end
if not ssight then s.y = sground.y + 1 end
local p1 = self.attack:get_pos()
p1.x = math.floor(p1.x + 0.5)
p1.y = math.floor(p1.y + 0.5)
p1.z = math.floor(p1.z + 0.5)
p1 = vector_new(floor(p1.x + 0.5), floor(p1.y + 0.5), floor(p1.z + 0.5))
local dropheight = 12
if self.fear_height ~= 0 then dropheight = self.fear_height end
local jumpheight = 0
if self.jump and self.jump_height >= 4 then
jumpheight = math.min(math.ceil(self.jump_height / 4), 4)
jumpheight = min(ceil(self.jump_height * 0.25), 4)
elseif self.stepheight > 0.5 then
jumpheight = 1
end
@ -206,34 +169,27 @@ function mob_class:smart_mobs(s, p, dist, dtime)
-- no path found, try something else
if not self.path.way then
self.path.following = false
-- lets make way by digging/building if not accessible
if self.pathfinding == 2 and mobs_griefing then
-- is player higher than mob?
if s.y < p1.y then
-- build upwards
if not minetest.is_protected(s, "") then
local ndef1 = minetest.registered_nodes[self.standing_in]
if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
minetest.set_node(s, {name = mcl_mobs.fallback_node})
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
s.y = s.y + sheight
-- remove one block above to make room to jump
if not minetest.is_protected(s, "") then
local node1 = node_ok(s, "air").name
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.unbreakable
and not ndef1.groups.liquid then
minetest.set_node(s, {name = "air"})
minetest.add_item(s, ItemStack(node1))
end
end
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
local yaw1 = self.object:get_yaw() + math.pi / 2
local p1 = {
x = s.x + math.cos(yaw1),
y = s.y,
z = s.z + math.sin(yaw1)
}
local yaw1 = self.object:get_yaw() + HALFPI
local p1 = vector_offset(s, cos(yaw1), 0, sin(yaw1))
if not minetest.is_protected(p1, "") then
local node1 = node_ok(p1, "air").name
local ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air"
and node1 ~= "ignore"
if node1 ~= "air" and node1 ~= "ignore"
and ndef1
and not ndef1.groups.level
and not ndef1.groups.unbreakable
@ -282,8 +227,7 @@ function mob_class:smart_mobs(s, p, dist, dtime)
node1 = node_ok(p1, "air").name
ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air"
and node1 ~= "ignore"
if node1 ~= "air" and node1 ~= "ignore"
and ndef1
and not ndef1.groups.level
and not ndef1.groups.unbreakable
@ -317,28 +261,19 @@ end
-- specific attacks
local specific_attack = function(list, what)
-- no list so attack default (player, animals etc.)
if list == nil then
return true
end
if list == nil then return true end
-- found entity on list to attack?
for no = 1, #list do
if list[no] == what then
return true
end
if list[no] == what then return true end
end
return false
end
-- find someone to attack
function mob_class:monster_attack()
if not damage_enabled or self.passive ~= false or self.state == "attack" or self:day_docile() then
return
end
if not damage_enabled or self.passive ~= false or self.state == "attack" or self:day_docile() then return end
local s = self.object:get_pos()
local p, sp, dist
@ -392,7 +327,7 @@ function mob_class:monster_attack()
p = player:get_pos()
sp = s
dist = vector.distance(p, s)
dist = vector_distance(p, s)
-- aim higher to make looking up hills more realistic
p.y = p.y + 1
@ -414,7 +349,7 @@ function mob_class:monster_attack()
end
end
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
-- attack player
if min_player then
@ -425,7 +360,6 @@ end
-- npc, find closest monster to attack
function mob_class:npc_attack()
if self.type ~= "npc"
or not self.attacks_monsters
or self.state == "attack" then
@ -444,7 +378,7 @@ function mob_class:npc_attack()
p = obj.object:get_pos()
sp = s
local dist = vector.distance(p, s)
local dist = vector_distance(p, s)
-- aim higher to make looking up hills more realistic
p.y = p.y + 1
@ -466,19 +400,13 @@ end
-- dogshoot attack switch and counter function
function mob_class:dogswitch(dtime)
-- switch mode not activated
if not self.dogshoot_switch
or not dtime then
return 0
end
if not self.dogshoot_switch or not dtime then return 0 end
self.dogshoot_count = self.dogshoot_count + dtime
if (self.dogshoot_switch == 1
and self.dogshoot_count > self.dogshoot_count_max)
or (self.dogshoot_switch == 2
and self.dogshoot_count > self.dogshoot_count2_max) then
if (self.dogshoot_switch == 1 and self.dogshoot_count > self.dogshoot_count_max)
or (self.dogshoot_switch == 2 and self.dogshoot_count > self.dogshoot_count2_max) then
self.dogshoot_count = 0
@ -525,13 +453,9 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
if is_player then
-- is mob out of reach?
if vector.distance(mob_pos, player_pos) > 3 then
return
end
if vector_distance(mob_pos, player_pos) > 3 then return end
-- is mob protected?
if self.protected and minetest.is_protected(mob_pos, hitter:get_player_name()) then
return
end
if self.protected and minetest.is_protected(mob_pos, hitter:get_player_name()) then return end
mcl_potions.update_haste_and_fatigue(hitter)
end
@ -540,13 +464,10 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local time_diff = time_now - self.invul_timestamp
-- check for invulnerability time in microseconds (0.5 second)
if time_diff <= 500000 and time_diff >= 0 then
return
end
if time_diff <= 500000 and time_diff >= 0 then return end
-- custom punch function
if self.do_punch then
-- when false skip going any further
if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
return
@ -562,15 +483,11 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local time_now = minetest.get_us_time()
if is_player then
if minetest.is_creative_enabled(hitter:get_player_name()) then
self.health = 0
end
if minetest.is_creative_enabled(hitter:get_player_name()) then self.health = 0 end
-- set/update 'drop xp' timestamp if hitted by player
self.xp_timestamp = time_now
end
-- punch interval
local weapon = hitter:get_wielded_item()
local punch_interval = 1.4
@ -591,18 +508,10 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
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)
if tmp < 0 then
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)
tmp = tmp < 0 and 0 or (tmp > 1 and 1 or tmp)
damage = damage + (tool_capabilities.damage_groups[group] or 0) * tmp * ((armor[group] or 0) / 100.0)
end
-- 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
for n = 1, #self.immune_to do
if self.immune_to[n][1] == weapon:get_name() then
damage = self.immune_to[n][2] or 0
break
end
@ -631,7 +538,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
-- healing
if damage <= -1 then
self.health = self.health - math.floor(damage)
self.health = self.health - floor(damage)
return
end
@ -651,7 +558,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local weapon = hitter:get_wielded_item(player)
local def = weapon:get_definition()
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)
tt.reload_itemstack_description(weapon) -- update tooltip
hitter:set_wielded_item(weapon)
@ -662,14 +569,12 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local die = false
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.
if damage >= 0.1 then
-- weapon sounds
if weapon:get_definition().sounds ~= nil then
local s = math.random(0, #weapon:get_definition().sounds)
local s = random(0, #weapon:get_definition().sounds)
minetest.sound_play(weapon:get_definition().sounds[s], {
object = self.object, --hitter,
@ -696,27 +601,20 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
end
end
-- knock back effect (only on full punch)
if self.knock_back
and tflp >= punch_interval then
if self.knock_back and tflp >= punch_interval then
-- direction error check
dir = dir or {x = 0, y = 0, z = 0}
dir = dir or vector_zero()
local v = self.object:get_velocity()
if not v then return end
local r = 1.4 - math.min(punch_interval, 1.4)
local kb = r * (math.abs(v.x)+math.abs(v.z))
local r = 1.4 - min(punch_interval, 1.4)
local kb = r * (abs(v.x)+abs(v.z))
local up = 2.625
if die==true then
kb=kb*1.25
end
if die then kb = kb * 1.25 end
-- if already in air then dont go up anymore when hit
if math.abs(v.y) > 0.1
or self.fly then
up = 0
end
if abs(v.y) > 0.1 or self.fly then up = 0 end
-- check if tool already has specific knockback value
if tool_capabilities.damage_groups["knockback"] then
@ -725,21 +623,17 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
kb = kb * 1.25
end
local luaentity
if hitter then
luaentity = hitter:get_luaentity()
end
local luaentity = hitter and hitter:get_luaentity()
if hitter and is_player then
local wielditem = hitter:get_wielded_item()
kb = kb + 9 * mcl_enchanting.get_enchantment(wielditem, "knockback")
-- add player velocity to mob knockback
local hv = hitter:get_velocity()
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 mob_mag = math.sqrt((v.x * v.x) + (v.z * v.z))
local player_mag = ((hv.x * hv.x) + (hv.z * hv.z))^0.5
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
kb = kb + ((math.abs(hv.x) + math.abs(hv.z)) * r)
kb = kb + (abs(hv.x) + abs(hv.z)) * r
end
elseif luaentity and luaentity._knockback and die == false then
kb = kb + luaentity._knockback
@ -747,12 +641,12 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
kb = kb + luaentity._knockback * 0.25
end
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
if self.animation.run_end then
self:set_animation( "run")
self:set_animation("run")
elseif self.animation.walk_end then
self:set_animation( "walk")
self:set_animation("walk")
end
minetest.after(0.2, function()
if self and self.object then
@ -760,11 +654,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
self._kb_turn = false
end
end)
self.object:add_velocity({
x = dir.x * kb,
y = up*2,
z = dir.z * kb
})
self.object:add_velocity(vector_new(dir.x * kb, up*2, dir.z * kb ))
self.pause_timer = 0.25
end
@ -772,12 +662,15 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
-- 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
local 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:turn_in_direction(sp.x - hp.x, sp.z - hp.z, 1)
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
yaw = self:set_yaw( minetest.dir_to_yaw(vector.direction(hitter:get_pos(), self.object:get_pos())))
self:set_velocity( self.run_velocity)
if self and self.object and hitter and is_player then
local hp, sp = hitter:get_pos(), self.object:get_pos()
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)
self.state = "runaway"
@ -808,7 +701,6 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local obj = nil
for n = 1, #objs do
obj = objs[n]:get_luaentity()
if obj then
@ -840,11 +732,7 @@ end
function mob_class:check_aggro(dtime)
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
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
-- 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?
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.attack = nil
self.state = "stand"
@ -878,17 +766,14 @@ local function clear_aggro(self)
self.path.way = nil
end
function mob_class:do_states_attack (dtime)
function mob_class:do_states_attack(dtime)
self.timer = self.timer + dtime
if self.timer > 100 then
self.timer = 1
end
if self.timer > 100 then self.timer = 1 end
local s = self.object:get_pos()
if not s then return end
local p = self.attack:get_pos() or s
local yaw = self.object:get_yaw() or 0
-- stop attacking if player invisible or out of range
@ -920,20 +805,15 @@ function mob_class:do_states_attack (dtime)
end
-- 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 target_line_of_sight then
local vec = { 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)
self:turn_in_direction(p.x - s.x, p.z - s.z, 1)
end
local node_break_radius = self.explosion_radius or 1
local entity_damage_radius = self.explosion_damage_radius
or (node_break_radius * 2)
local entity_damage_radius = self.explosion_damage_radius or (node_break_radius * 2)
-- start timer when in reach and line of sight
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
if self.animation and self.animation.run_start then
self:set_animation( "run")
self:set_animation("run")
else
self:set_animation( "walk")
self:set_animation("walk")
end
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 dist <= self.reach and self:dogswitch() == 0) then
if self.fly
and dist > self.reach then
local p1 = s
local me_y = math.floor(p1.y)
local p2 = p
local p_y = math.floor(p2.y + 1)
if self.fly and dist > self.reach then
local p1, p2 = s, p
local me_y, p_y = floor(p1.y), floor(p2.y + 1)
local v = self.object:get_velocity()
if self:flight_check( s) then
if me_y < p_y then
self.object:set_velocity({
x = v.x,
y = 1 * self.walk_velocity,
z = v.z
})
self.object:set_velocity(vector_new(v.x, 1 * self.walk_velocity, v.z))
elseif me_y > p_y then
self.object:set_velocity({
x = v.x,
y = -1 * self.walk_velocity,
z = v.z
})
self.object:set_velocity(vector_new(v.x, -1 * self.walk_velocity, v.z))
end
else
if me_y < p_y then
self.object:set_velocity({
x = v.x,
y = 0.01,
z = v.z
})
self.object:set_velocity(vector_new(v.x, 0.01, v.z))
elseif me_y > p_y then
self.object:set_velocity({
x = v.x,
y = -0.01,
z = v.z
})
self.object:set_velocity(vector_new(v.x, -0.01, v.z))
end
end
end
-- rnd: new movement direction
if self.path.following
and self.path.way
and self.attack_type ~= "dogshoot" then
if self.path.following and self.path.way and self.attack_type ~= "dogshoot" then
-- no paths longer than 50
if #self.path.way > 50
or dist < self.reach then
if #self.path.way > 50 or dist < self.reach then
self.path.following = false
return
end
local p1 = self.path.way[1]
if not p1 then
self.path.following = false
return
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
table.remove(self.path.way, 1)
end
-- set new temporary target
p = {x = p1.x, y = p1.y, z = p1.z}
p = vector_copy(p1)
end
local vec = {
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)
self:turn_in_direction(p.x - s.x, p.z - s.z, 10)
-- move towards enemy if beyond mob reach
if dist > self.reach then
@ -1100,10 +938,9 @@ function mob_class:do_states_attack (dtime)
end
if self:is_at_cliff_or_danger() then
self:set_velocity( 0)
self:set_animation( "stand")
local yaw = self.object:get_yaw() or 0
yaw = self:set_yaw( yaw + 0.78, 8)
self:set_velocity(0)
self:set_animation("stand")
--self:turn_by(PI * (random() - 0.5), 10)
else
if self.path.stuck then
self:set_velocity(self.walk_velocity)
@ -1129,19 +966,13 @@ function mob_class:do_states_attack (dtime)
self.timer = 0
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")
else
self:set_animation("punch")
end
local p2 = p
local s2 = s
p2.y = p2.y + .5
s2.y = s2.y + .5
if self:line_of_sight( p2, s2) == true then
if self:line_of_sight(vector_offset(p, 0, .5, 0), vector_offset(s, 0, .5, 0)) == true then
self:mob_sound("attack")
-- punch player (or what player is attached to)
@ -1167,59 +998,31 @@ function mob_class:do_states_attack (dtime)
elseif self.attack_type == "shoot"
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
p.y = p.y - .5
s.y = s.y + .5
local dist = vector.distance(p, s)
local vec = {
x = p.x - s.x,
y = p.y - s.y,
z = p.z - s.z
}
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
local vec = vector_new(p.x - s.x, p.y - s.y - 1, p.z - s.z)
local dist = (vec.x*vec.x + vec.y*vec.y + vec.z*vec.z)^0.5
self:turn_in_direction(vec.x, vec.z, 10)
if self.strafes then
if not self.strafe_direction then
self.strafe_direction = 1.57
end
if math.random(40) == 1 then
self.strafe_direction = self.strafe_direction*-1
end
if not self.strafe_direction then self.strafe_direction = HALFPI end
if random(40) == 1 then self.strafe_direction = self.strafe_direction * -1 end
local dir = vector.rotate_around_axis(vector.direction(s, p), vector.new(0,1,0), self.strafe_direction)
local dir2 = vector.multiply(dir, 0.3 * self.walk_velocity)
if dir2 and stay_away_from_player then
self.acc = vector.add(dir2, stay_away_from_player)
local dir = -atan2(p.x - s.x, p.z - s.z)
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 self.avoid_distance and dist < self.avoid_distance and self.shooter_avoid_enemy then
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
else
self:set_velocity( 0)
self:set_velocity(0)
self:set_animation("stand")
end
local p = self.object:get_pos()
p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2
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
p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) * 0.5
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:set_animation( "shoot")
@ -1228,7 +1031,6 @@ function mob_class:do_states_attack (dtime)
-- Shoot arrow
if minetest.registered_entities[self.arrow] then
local arrow, ent
local v = 1
if not self.shoot_arrow then
@ -1238,9 +1040,7 @@ function mob_class:do_states_attack (dtime)
end)
arrow = minetest.add_entity(p, self.arrow)
ent = arrow:get_luaentity()
if ent.velocity then
v = ent.velocity
end
v = ent.velocity or v
ent.switch = 1
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
local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
-- offset makes shoot aim accurate
vec.y = vec.y + self.shoot_offset
vec.x = vec.x * (v / amount)
vec.y = vec.y * (v / amount)
vec.z = vec.z * (v / amount)
vec.x, vec.y, vec.z = vec.x * (v / dist), vec.y * (v / dist), vec.z * (v / dist)
if self.shoot_arrow then
vec = vector.normalize(vec)
self:shoot_arrow(p, vec)
@ -1266,13 +1063,9 @@ function mob_class:do_states_attack (dtime)
end
end
end
elseif self.attack_type == "custom" and self.attack_state then
self.attack_state(self, dtime)
end
if self.on_attack then
self.on_attack(self, dtime)
end
if self.on_attack then self.on_attack(self, dtime) end
end

View File

@ -5,7 +5,11 @@ local validate_vector = mcl_util.validate_vector
local active_particlespawners = {}
local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
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"
@ -246,9 +250,7 @@ end
-- set defined animation
function mob_class:set_animation(anim, fixed_frame)
if not self.animation or not anim then
return
end
if not self.animation or not anim then return end
if self.jockey and self.object:get_attach() then
anim = "jockey"
@ -256,11 +258,7 @@ function mob_class:set_animation(anim, fixed_frame)
self.jockey = nil
end
if self.state == "die" and anim ~= "die" and anim ~= "stand" then
return
end
if self.state == "die" and anim ~= "die" and anim ~= "stand" then return 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
local a_start = self.animation[anim .. "_start"]
local a_end
if fixed_frame then
a_end = a_start
else
a_end = self.animation[anim .. "_end"]
end
local a_end = fixed_frame and a_start or self.animation[anim .. "_end"]
if a_start and a_end then
self.object:set_animation({
x = a_start,
@ -290,11 +283,6 @@ function mob_class:set_animation(anim, fixed_frame)
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)
if self.order == "sleep" then
self._locked_object = nil
@ -348,58 +336,62 @@ function mob_class:check_head_swivel(dtime)
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
local _locked_object_eye_height = 1.5
if locked_object:is_player() then
_locked_object_eye_height = locked_object:get_properties().eye_height
elseif locked_object:get_luaentity() then
_locked_object_eye_height = locked_object:get_luaentity().head_eye_height
local _locked_object_eye_height = (locked_object:is_player() and locked_object:get_properties().eye_height * 0.8) -- food in hands of player
or (locked_object:get_luaentity() and locked_object:get_luaentity().head_eye_height) or 1.5
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
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()
ps.y = ps.y + self.head_eye_height * .7
local pt = locked_object:get_pos()
pt.y = pt.y + _locked_object_eye_height
local dir = vector.direction(ps, pt)
local mob_yaw = self_rot.y + math.atan2(dir.x, dir.z) + self.head_yaw_offset
local mob_pitch = math.asin(-dir.y) * self.head_pitch_multiplier
local ps = self.object:get_pos()
ps.y = ps.y + self.head_eye_height -- why here, instead of below? * .7
local pt = locked_object:get_pos()
pt.y = pt.y + _locked_object_eye_height
local dir = vector.direction(ps, pt) -- is (pt-ps):normalize()
local mob_yaw = math.atan2(dir.x, dir.z)
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
newr = vector.multiply(oldr, 0.9)
elseif self.attack and self.state == "attack" and not self.runaway then
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)
end
else
if self.head_yaw == "y" then
newr = vector.new((mob_pitch-oldr.x)*.3+oldr.x, (mob_yaw-oldr.y)*.3+oldr.y, 0)
elseif self.head_yaw == "z" then
newr = vector.new((mob_pitch-oldr.x)*.3+oldr.x, 0, ((mob_yaw-oldr.y)*.3+oldr.y)*-3)
end
end
mob_yaw = mob_yaw + self_rot.y -- to relative orientation
while mob_yaw > PI do mob_yaw = mob_yaw - TWOPI end
while mob_yaw < -PI do mob_yaw = mob_yaw + TWOPI end
mob_yaw = mob_yaw * 0.8 -- lessen the effect so it become less staring
local max_yaw = self.head_max_yaw or MAX_YAW
mob_yaw = (mob_yaw < -max_yaw and -max_yaw) or (mob_yaw < max_yaw and mob_yaw) or max_yaw -- avoid twisting the neck
mob_pitch = mob_pitch * 0.8 -- make it less obvious that this is computed
local max_pitch = self.head_max_pitch or MAX_PITCH
mob_pitch = (mob_pitch < -max_pitch and -max_pitch) or (mob_pitch < max_pitch and mob_pitch) or max_pitch
local smoothing = (self.state == "attack" and self.attack and 0.25) or 0.05
local old_pitch = oldr.x
local old_yaw = (self.head_yaw == "y" and oldr.y or -oldr.z) - self.head_yaw_offset
-- 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
elseif not locked_object and math.abs(oldr.y) > 0.05 and math.abs(oldr.x) < 0.05 then
newr = vector.multiply(oldr, 0.9)
elseif math.abs(oldr.x) + math.abs(oldr.y) + math.abs(oldr.z) > 0.05 then
newr = vector.multiply(oldr, 0.9) -- smooth stop looking
end
-- 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, newp) 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, vector.zero()) then return end
if self.object.get_bone_override then -- minetest >= 5.9
self.object:set_bone_override(self.head_swivel, {
position = { vec = newp, absolute = true },
rotation = { vec = newr, absolute = true } })
position = { vec = self.head_bone_position, absolute = true },
rotation = { vec = newr, absolute = true, interpolation = 0.1 } })
else -- minetest < 5.9
-- old API uses degrees not radians
self.object:set_bone_position(self.head_swivel, newp, vector.apply(newr, math.deg))
-- old API uses degrees not radians and absolute positions
self.object:set_bone_position(self.head_swivel, self.head_bone_position, vector.apply(newr, math.deg))
end
end

View File

@ -6,6 +6,19 @@ local modname = minetest.get_current_modname()
local path = minetest.get_modpath(modname)
local S = minetest.get_translator(modname)
mcl_mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt"
-- 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
-- effects: sounds and particles mostly
dofile(path .. "/effects.lua")
@ -19,10 +32,9 @@ dofile(path .. "/items.lua")
dofile(path .. "/pathfinding.lua")
-- combat: attack logic
dofile(path .. "/combat.lua")
-- the enity functions themselves
-- the entity functions themselves
dofile(path .. "/api.lua")
--utility functions
dofile(path .. "/breeding.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 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
-- 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.registered_mobs[name] = def
local can_despawn
if def.can_despawn ~= nil then
can_despawn = def.can_despawn
elseif def.spawn_class == "passive" then
can_despawn = false
else
can_despawn = true
end
local can_despawn = def.can_despawn
if def.can_despawn == nil then can_despawn = def.spawn_class ~= "passive" end
local function scale_difficulty(value, default, min, special)
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_yaw_offset = math.rad(def.head_yaw_offset or 0), -- for wonkey model bones
head_pitch_multiplier = def.head_pitch_multiplier or 1, --for inverted pitch
bone_eye_height = def.bone_eye_height or 1.4, -- head bone offset
head_eye_height = def.head_eye_height or def.bone_eye_height or 0, -- how hight aproximatly the mobs head is fromm the ground to tell the mob how high to look up at the player
head_eye_height = def.head_eye_height or 1, -- how high approximately the mobs eyes are from 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
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
stepheight = def.stepheight or 0.6,
name = name,

View File

@ -1,5 +1,5 @@
name = mcl_mobs
author = PilzAdam
author = PilzAdam, kno10
description = Adds a mob API for mods to add animals or monsters, etc.
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

View File

@ -1,110 +1,41 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
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 crash_threshold = 6.5 -- ignored if enable_crash=false
local GRAVITY = -9.8
------------------------------------------------------------------------------
--
-- 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 node_ok = mcl_mobs.node_ok
local sign = math.sign -- minetest extension
local function node_is(pos)
local node = node_ok(pos)
if node.name == "air" then
return "air"
end
if minetest.get_item_group(node.name, "lava") ~= 0 then
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
if node.name == "air" then return "air" end
local ndef = minetest.registered_nodes[node.name]
if not ndef then return "other" end -- unknown/ignore
if ndef.groups.lava then return "lava" end
if ndef.groups.liquid then return "liquid" end
if ndef.walkable then return "walkable" end
return "other"
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 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()
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()
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)
player:set_properties({visual_size = {x = 1, y = 1} })
end
-------------------------------------------------------------------------------
minetest.register_on_leaveplayer(function(player)
force_detach(player)
end)
minetest.register_on_leaveplayer(force_detach)
minetest.register_on_shutdown(function()
local players = minetest.get_connected_players()
@ -118,39 +49,24 @@ minetest.register_on_dieplayer(function(player)
return true
end)
-------------------------------------------------------------------------------
function mcl_mobs.attach(entity, player)
local attach_at, eye_offset
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.player_rotation = entity.player_rotation or vector.zero()
entity.driver_attach_at = entity.driver_attach_at or vector.zero()
entity.driver_eye_offset = entity.driver_eye_offset or vector.zero()
entity.driver_scale = entity.driver_scale or {x = 1, y = 1}
local rot_view = 0
if entity.player_rotation.y == 90 then
rot_view = math.pi/2
end
attach_at = entity.driver_attach_at
eye_offset = entity.driver_eye_offset
local rot_view = entity.player_rotation.y == 90 and math.pi/2 or 0
local attach_at = entity.driver_attach_at
local eye_offset = entity.driver_eye_offset
entity.driver = player
force_detach(player)
player:set_attach(entity.object, "", attach_at, entity.player_rotation)
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({
visual_size = {
x = entity.driver_scale.x,
y = entity.driver_scale.y
}
})
player:set_properties({ visual_size = entity.driver_scale })
minetest.after(0.2, function(name)
local player = minetest.get_player_by_name(name)
@ -164,162 +80,88 @@ end
function mcl_mobs.detach(player, offset)
force_detach(player)
mcl_player.player_set_animation(player, "stand" , 30)
--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)
]]--
player:add_velocity(vector.new(math.random()*12-6,math.random()*3+5,math.random()*12-6)) --throw the rider off
end
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()
entity.v = get_v(velo) * get_sign(entity.v)
local v = math.sqrt(velo.x * velo.x + velo.y * velo.y)
local acce_y = GRAVITY
-- process controls
if entity.driver then
local ctrl = entity.driver:get_player_control()
-- move forwards
if ctrl.up then
entity.v = entity.v + entity.accel / 10 * entity.run_velocity / 2.6
-- 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
if ctrl.up then -- forward
v = v + entity.accel * 0.1 * entity.run_velocity * 0.385
elseif ctrl.down then -- backwards
if entity.max_speed_reverse == 0 and v == 0 then return end
v = v - entity.accel * 0.1 * entity.run_velocity * 0.385
end
-- fix mob rotation
entity.object:set_yaw(entity.driver:get_look_horizontal() - entity.rotate)
entity:set_yaw(entity.driver:get_look_horizontal() - entity.rotate, 2)
if can_fly then
-- FIXME: use acce_y instead?
-- fly up
if ctrl.jump then
velo.y = velo.y + 1
if velo.y > entity.accel then velo.y = entity.accel end
elseif velo.y > 0 then
velo.y = math.min(velo.y + 1, entity.accel)
elseif velo.y > 0.1 then
velo.y = velo.y - 0.1
if velo.y < 0 then velo.y = 0 end
elseif velo.y > 0 then
velo.y = 0
end
-- fly down
if ctrl.sneak then
velo.y = velo.y - 1
if velo.y < -entity.accel then velo.y = -entity.accel end
elseif velo.y < 0 then
velo.y = math.max(velo.y - 1, -entity.accel)
elseif velo.y < -0.1 then
velo.y = velo.y + 0.1
if velo.y > 0 then velo.y = 0 end
elseif velo.y < 0 then
velo.y = 0
end
else
-- jump
if ctrl.jump then
if velo.y == 0 then
velo.y = velo.y + entity.jump_height
acce_y = acce_y + (acce_y * 3) + 1
acce_y = acce_y + 1
end
end
end
end
-- Stop!
local s = get_sign(entity.v)
entity.v = entity.v - 0.02 * s
if s ~= get_sign(entity.v) then
entity.object:set_velocity({x = 0, y = 0, z = 0})
entity.v = 0
return
if math.abs(v) < 0.02 then -- stop
entity.object:set_velocity(vector.zero())
v = 0
else
v = v - 0.02 * sign(v) -- slow down
end
-- 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 stand_anim then
mcl_mobs:set_animation(entity, stand_anim)
end
if v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then
entity:set_animation(stand_anim)
return
end
-- set moving animation
if moving_anim then
mcl_mobs:set_animation(entity, moving_anim)
else
entity:set_animation(moving_anim)
end
-- enforce speed limit forward and reverse
local max_spd = entity.max_speed_reverse
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
v = math.max(-entity.max_speed_reverse, math.min(v, entity.max_speed_forward))
-- Set position, velocity and acceleration
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
local ni = node_is(p)
local v = entity.v
if ni == "air" then
if can_fly == true then
new_acce.y = 0
end
if can_fly then acce_y = acce_y - GRAVITY end
elseif ni == "liquid" or ni == "lava" then
if ni == "lava" and entity.lava_damage ~= 0 then
entity.lava_counter = (entity.lava_counter or 0) + dtime
if entity.lava_counter > 1 then
minetest.sound_play("default_punch", {
object = entity.object,
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
or entity.terrain_type == 3 then
new_acce.y = 0
acce_y = 0
p.y = p.y + 1
if node_is(p) == "liquid" then
if velo.y >= 5 then
velo.y = 5
elseif velo.y < 0 then
new_acce.y = 20
acce_y = 20
else
new_acce.y = 5
acce_y = 5
end
else
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
new_velo = get_velocity(v, entity.object:get_yaw() - rot_view, velo.y)
new_acce.y = new_acce.y + acce_y
local rot_view = entity.player_rotation.y == 90 and math.pi/2 or 0
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_acceleration(new_acce)
entity.object:set_acceleration(vector.new(0, acce_y, 0))
-- CRASH!
if enable_crash then
local intensity = entity.v2 - v
if intensity >= crash_threshold then
if v >= crash_threshold then
entity.object:punch(entity.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = intensity}
damage_groups = {fleshy = v}
}, nil)
end
end
entity.v2 = v
end
-- directional flying routine by D00Med (edited by TenPlus1)
function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim)
local ctrl = entity.driver:get_player_control()
local velo = entity.object:get_velocity()
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
entity.object:set_velocity({
x = dir.x * speed,
y = dir.y * speed + 2,
z = dir.z * speed
})
entity.object:set_velocity(vector.new(dir.x * speed, dir.y * speed + 2, dir.z * speed))
elseif ctrl.down then
entity.object:set_velocity({
x = -dir.x * speed,
y = dir.y * speed + 2,
z = -dir.z * speed
})
entity.object:set_velocity(vector.new(-dir.x * speed, dir.y * speed + 2, -dir.z * speed))
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
entity.object:set_yaw(yaw + math.pi + math.pi / 2 - entity.rotate)
entity:set_yaw(yaw - entity.rotate, 2)
-- firing arrows
if ctrl.LMB and ctrl.sneak and shoots then
local pos = entity.object:get_pos()
local obj = minetest.add_entity({
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 obj = minetest.add_entity(vector.offset(pos, dir.x * 2.5, 1.5 + dir.y, dir.z * 2.5), arrow)
local ent = obj:get_luaentity()
if ent then
ent.switch = 1 -- for mob specific arrows
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()
obj:set_yaw(yaw + math.pi / 2)
obj:set_yaw(yaw)
obj:set_velocity(vec)
else
obj:remove()
@ -439,11 +254,9 @@ function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_an
-- change animation if stopped
if velo.x == 0 and velo.y == 0 and velo.z == 0 then
mcl_mobs:set_animation(entity, stand_anim)
entity:set_animation(stand_anim)
else
-- moving animation
mcl_mobs:set_animation(entity, moving_anim)
entity:set_animation(moving_anim)
end
end
@ -452,12 +265,7 @@ mcl_mobs.mob_class.fly = mcl_mobs.fly
mcl_mobs.mob_class.attach = mcl_mobs.attach
function mob_class:on_detach_child(child)
if self.detach_child then
if self.detach_child(self, child) then
return
end
end
if self.driver == child then
self.driver = nil
end
if self.detach_child and self.detach_child(self, child) then return end
if self.driver == child then self.driver = nil 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 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 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 one_down = vector.new(0,-1,0)
local one_up = vector.new(0,1,0)
local plane_adjacents = {
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 visualize = minetest.settings:get_bool("mcl_mobs_pathfinding_visualize",false)
local LOG_MODULE = "[Mobs Pathfinding]"
local function mcl_log (message)
@ -42,8 +40,8 @@ function append_paths (wp1, wp2)
mcl_log("Cannot append wp's")
return
end
output_table(wp1)
output_table(wp2)
--output_table(wp1)
--output_table(wp2)
for _,a in pairs (wp2) do
table.insert(wp1, a)
end
@ -51,18 +49,13 @@ function append_paths (wp1, wp2)
end
local function output_enriched (wp_out)
mcl_log("Output enriched path")
--mcl_log("Output enriched path")
local i = 0
for _,outy in pairs (wp_out) do
i = i + 1
mcl_log("Pos ".. i ..":" .. minetest.pos_to_string(outy["pos"]))
local action = outy["action"]
if action then
--mcl_log("Pos ".. i ..":" .. minetest.pos_to_string(outy["pos"]))
mcl_log("type: " .. action["type"])
mcl_log("action: " .. action["action"])
mcl_log("target: " .. minetest.pos_to_string(action["target"]))
mcl_log("Pos ".. i ..":" .. minetest.pos_to_string(outy["pos"])..", type: " .. action["type"]..", action: " .. action["action"]..", target: " .. minetest.pos_to_string(action["target"]))
end
--mcl_log("failed attempts: " .. outy["failed_attempts"])
end
@ -73,33 +66,22 @@ end
-- 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 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
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")
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
mcl_log ("Door close match")
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
mcl_log("Current 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
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]["action"] = action
@ -113,93 +95,82 @@ end
local last_pathing_time = os.time()
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
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
else
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
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
end
mcl_log("time_since_path_start: " .. tostring(time_since_path_start))
end
end
-- 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)
if not cur_door_pos then return end
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
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
local enriched_path = nil
local wp, prospective_wp
for _,v in pairs(plane_adjacents) do
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 other_side_of_door = nil
local prospective_wp = minetest.find_path(p, pos_closest_to_door, PATHFINDING_SEARCH_DISTANCE, 1, 4)
if cur_door_pos then
mcl_log("Found a door near: " .. minetest.pos_to_string(cur_door_pos))
if prospective_wp then
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
pos_closest_to_door = vector.add(cur_door_pos,v)
other_side_of_door = vector.add(cur_door_pos,-v)
table.insert(prospective_wp, cur_door_pos)
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
mcl_log("We have air space next to door at: " .. minetest.pos_to_string(pos_closest_to_door))
prospective_wp = minetest.find_path(p, pos_closest_to_door, PATHFINDING_SEARCH_DISTANCE, 1, 4)
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
if wp_otherside_door_to_target and #wp_otherside_door_to_target > 0 then
append_paths (prospective_wp, wp_otherside_door_to_target)
mcl_log("We have a path from outside door to target")
return generate_enriched_path(prospective_wp, pos_closest_to_door, other_side_of_door, cur_door_pos)
else
mcl_log("No t, just add other side of door")
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
mcl_log("We cannot path from outside door to target")
end
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
else
mcl_log("Cannot path to this air block next to door.")
end
end
else
mcl_log("No door found")
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
-- 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)
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
local p = self.object:get_pos()
local t = vector.offset(target,0,1,0)
-- maybe feet are buried in solid?
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
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
mcl_log("### No direct path. Path through door closest to target.")
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)
if not wp then
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 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)
-- 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]
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))
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
@ -268,26 +254,92 @@ function mob_class:gopath(target, callback_arrived, prioritised)
-- If cannot path, don't immediately try again
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
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)
self._target = t
self.callback_arrived = callback_arrived
local current_location = table.remove(wp,1)
if current_location and current_location["pos"] then
mcl_log("Removing first co-ord? " .. tostring(current_location["pos"]))
else
mcl_log("Nil pos")
self.current_target = table.remove(wp,1)
while self.current_target and self.current_target.pos and vector.distance(p, self.current_target.pos) < 0.5 do
--mcl_log("Skipping close initial waypoint")
self.current_target = table.remove(wp,1)
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
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
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
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)
if n.name:find("_b_") or n.name:find("_t_") then
mcl_log("Door")
local def = minetest.registered_nodes[n.name]
local closed = n.name:find("_b_1") or n.name:find("_t_1")
--if self.state == PATHFINDING then
if closed and action == "open" and def.on_rightclick then
mcl_log("Open door")
def.on_rightclick(target,n,self)
end
if not closed and action == "close" and def.on_rightclick then
mcl_log("Close door")
def.on_rightclick(target,n,self)
end
--else
local meta = minetest.get_meta(target)
local closed = meta:get_int("is_open") == 0
if closed and action == "open" and def.on_rightclick then
mcl_log("Open door")
def.on_rightclick(target,n,self)
elseif not closed and action == "close" and def.on_rightclick then
mcl_log("Close door")
def.on_rightclick(target,n,self)
end
elseif n.name:find("_gate") then
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
mcl_log("Not door")
end
@ -333,6 +393,7 @@ function mob_class:do_pathfind_action(action)
end
if type and type == "door" then
mcl_log("Type is door")
self.object:set_velocity(vector.zero())
self:interact_with_door(action_val, target)
end
end
@ -343,8 +404,7 @@ function mob_class:check_gowp(dtime)
-- no destination
if not p or not self._target then
mcl_log("p: ".. tostring(p))
mcl_log("self._target: ".. tostring(self._target))
mcl_log("p: ".. tostring(p)..", self._target: ".. tostring(self._target))
return
end
@ -358,8 +418,8 @@ function mob_class:check_gowp(dtime)
self.current_target = nil
self.state = "stand"
self.order = "stand"
self.object:set_velocity({x = 0, y = 0, z = 0})
self.object:set_acceleration({x = 0, y = 0, z = 0})
self.object:set_velocity(vector.zero())
self.object:set_acceleration(vector.zero())
if self.callback_arrived then return self.callback_arrived(self) end
return true
elseif not self.current_target then
@ -368,41 +428,67 @@ function mob_class:check_gowp(dtime)
-- More pathing to be done
local distance_to_current_target = 50
if self.current_target and self.current_target["pos"] then
distance_to_current_target = vector.distance(p,self.current_target["pos"])
if self.current_target and self.current_target.pos then
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
-- 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.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.
self:do_pathfind_action (self.current_target["action"])
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: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
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.
self.current_target["failed_attempts"] = self.current_target["failed_attempts"] + 1
local failed_attempts = self.current_target["failed_attempts"]
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.current_target = nil
self.waypoints = nil
self._target = nil
self._pf_last_failed = os.time()
self.object:set_velocity({x = 0, y = 0, z = 0})
self.object:set_acceleration({x = 0, y = 0, z = 0})
self.object:set_velocity(vector.zero())
self.object:set_acceleration(vector.zero())
return
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"])
-- Do i just delete current_target, and return so we can find final path.
else
@ -436,6 +522,7 @@ function mob_class:check_gowp(dtime)
-- I don't think we need the following anymore, but test first.
-- 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
local updated_p = self.object:get_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))
-- 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"]))
self:go_to_pos(self._current_target)
else
@ -454,4 +541,5 @@ function mob_class:check_gowp(dtime)
end
return
end
--]]--
end

View File

@ -6,6 +6,18 @@ local ENTITY_CRAMMING_MAX = 24
local CRAMMING_DAMAGE = 3
local DEATH_DELAY = 0.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 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 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)
local function within_limits(pos, radius)
local wmin, wmax = -30912, 30928
@ -56,9 +54,7 @@ end
-- Return true if object is in view_range
function mob_class:object_in_range(object)
if not object then
return false
end
if not object then return false end
local factor
-- Apply view range reduction for special player armor
if object:is_player() then
@ -110,24 +106,21 @@ function mob_class:item_drop(cooked, looting_level)
local num = 0
local do_common_looting = (looting_level > 0 and looting_type == "common")
if math.random() < chance then
num = math.random(dropdef.min or 1, dropdef.max or 1)
if random() < chance then
num = random(dropdef.min or 1, dropdef.max or 1)
elseif not dropdef.looting_ignore_chance then
do_common_looting = false
end
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
if num > 0 then
item = dropdef.name
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
item = output.item:get_name()
end
@ -135,17 +128,12 @@ function mob_class:item_drop(cooked, looting_level)
for x = 1, num do
obj = minetest.add_item(pos, ItemStack(item .. " " .. 1))
end
if obj and obj:get_luaentity() then
obj:set_velocity({
x = math.random(-10, 10) / 9,
y = 6,
z = math.random(-10, 10) / 9,
})
elseif obj then
obj:remove() -- item does not exist
if obj and obj:get_luaentity() then
obj:set_velocity(vector.new((random() - 0.5) * 1.5, 6, (random() - 0.5) * 1.5))
elseif obj then
obj:remove() -- item does not exist
end
end
end
end
@ -156,32 +144,29 @@ end
-- collision function borrowed amended from jordan4ibanez open_ai mod
function mob_class:collision()
local pos = self.object:get_pos()
if not pos then return {0,0} end
if not pos then return 0,0 end
local vel = self.object:get_velocity()
local x = 0
local z = 0
local x, z = 0, 0
local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5
for _,object in pairs(minetest.get_objects_inside_radius(pos, width)) do
local ent = object:get_luaentity()
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
mcl_burning.set_on_fire(object, 4)
end
local pos2 = object:get_pos()
local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z}
local force = (width + 0.5) - vector.distance(
{x = pos.x, y = 0, z = pos.z},
{x = pos2.x, y = 0, z = pos2.z})
x = x + (vec.x * force)
z = z + (vec.z * force)
local vx, vz = pos.x - pos2.x, pos.z - pos2.z
local force = width - (vx*vx+vz*vz)^0.5
if force > 0 then
force = force * force * (object:is_player() and 2 or 1) -- players push more
-- minetest.log("mob push force "..force.." "..tostring(self.name).." by "..tostring(ent and ent.name or "player"))
x = x + vx * force
z = z + vz * force
end
end
end
return({x,z})
return x, z
end
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()
if v then
--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
return dying
end
-- move mob in facing direction
function mob_class:set_velocity(v)
if not v then return end
local c_x, c_y = 0, 0
local c_x, c_z = 0, 0
-- can mob be pushed, if so calculate direction
if self.pushable then
c_x, c_y = unpack(self:collision())
c_x, c_z = self:collision()
end
-- halt mob if it has been ordered to stay
if self.order == "stand" or self.order == "sit" then
self.acc = vector.zero()
return
end
local yaw = (self.object:get_yaw() or 0) + self.rotate
local vv = self.object:get_velocity()
if vv and yaw then
self.acc = vector.new(((math.sin(yaw) * -v) + c_x) * .4, 0, ((math.cos(yaw) * v) + c_y) * .4)
if v > 0 then
local yaw = (self.object:get_yaw() or 0) + self.rotate
local x = ((-sin(yaw) * v) + c_x) * .4
local z = (( cos(yaw) * v) + c_z) * .4
if not self.acc then
self.acc = vector.new(x, 0, z)
else
self.acc.x, self.acc.y, self.acc.z = x, 0, z
end
else -- allow standing mobs to be pushed
if not self.acc then
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
-- calculate mob velocity
-- calculate mob velocity (2d)
function mob_class:get_velocity()
local v = self.object:get_velocity()
if v then
return (v.x * v.x + v.z * v.z) ^ 0.5
end
return 0
if not v then return 0 end
return (v.x*v.x + v.z*v.z)^0.5
end
function mob_class:update_roll()
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()
rot.z = is_Fleckenstein and pi or 0
rot.z = is_Fleckenstein and PI or 0
self.object:set_rotation(rot)
local cbox = table.copy(self.collisionbox)
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
if is_Fleckenstein ~= self.is_Fleckenstein then
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)
end
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
self.is_Fleckenstein = is_Fleckenstein
end
local function shortest_term_of_yaw_rotation(self, rot_origin, rot_target, nums)
if not rot_origin or not rot_target then
return
end
rot_origin = math.deg(rot_origin)
rot_target = math.deg(rot_target)
if rot_origin < rot_target then
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
-- Relative turn, primarily for random turning
-- @param dtime deprecated: ignored now, because of smooth rotations
function mob_class:turn_by(angle, delay, dtime)
return self:set_yaw((self.object:get_yaw() or 0) + angle, delay, dtime)
end
-- Turn into a direction (e.g., to the player, or away)
-- @param dtime deprecated: ignored now, because of smooth rotations
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
return self:set_yaw(-atan2(dx, dz) - self.rotate, delay, dtime) + self.rotate
end
-- set and return valid yaw
-- @param dtime deprecated: ignored now, because of smooth rotations
function mob_class:set_yaw(yaw, delay, dtime)
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 self.state ~= PATHFINDING then
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
self.delay = delay or 0
self.target_yaw = yaw % TWOPI
return self.target_yaw
end
-- global function to set mob yaw
function mcl_mobs.yaw(self, yaw, delay, dtime)
return mob_class.set_yaw(self, yaw, delay, dtime)
-- improved smooth rotation
function mob_class:check_smooth_rotation(dtime)
if not self.target_yaw then return end
local delay = self.delay
local initial_yaw = self.object:get_yaw() or 0
local 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
-- 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
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
--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
local frames = self.animation.die_end - self.animation.die_start
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")
else
length = 1 + DEATH_DELAY
@ -672,7 +574,7 @@ function mob_class:do_env_damage()
-- what is mob standing in?
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_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
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
elseif self.standing_in == "mcl_flowers:wither_rose" then
mcl_potions.give_effect_by_level("withering", self.object, 2, 2)
@ -799,7 +701,7 @@ function mob_class:do_env_damage()
end
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)
if self.breath <= 0 then
local dmg
@ -816,7 +718,7 @@ function mob_class:do_env_damage()
return true
end
else
self.breath = math.min(self.breath_max, self.breath + 1)
self.breath = min(self.breath_max, self.breath + 1)
end
end
@ -882,7 +784,7 @@ end
function mob_class:damage_mob(reason,damage)
if not self.health then return end
damage = math.floor(damage)
damage = floor(damage)
if damage > 0 then
self.health = self.health - damage
@ -928,57 +830,45 @@ end
-- falling and fall damage
-- returns true if mob died
function mob_class:falling(pos, moveresult)
if self.fly and self.state ~= "die" then
return
end
if self.fly and self.state ~= "die" then return 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.nether_portal_cooloff(self.object) then
return false -- mob has teleported through Nether portal - it's 99% not falling
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]
if registered_node.groups.lava then
if acc and self.floats_on_lava == 1 then
self.object:set_acceleration(vector.new(0, -self.fall_speed / (math.max(1, v.y) ^ 2), 0))
if self.floats_on_lava == 1 then
self.object:set_acceleration(vector.new(0, -self.fall_speed / max(1, v.y^2), 0))
end
end
-- in water then float up
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
self.object:set_acceleration(vector.new(0, -self.fall_speed / (math.max(1, v.y) ^ 2), 0))
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 / max(1, v.y^2), 0))
end
else
-- fall damage onto solid ground
@ -1002,13 +892,9 @@ end
function mob_class:check_water_flow()
-- Add water flowing for mobs from mcl_item_entity
local p, node, nn, def
p = self.object:get_pos()
node = minetest.get_node_or_nil(p)
if node then
nn = node.name
def = minetest.registered_nodes[nn]
end
local p = self.object:get_pos()
local node = minetest.get_node_or_nil(p)
local def = node and minetest.registered_nodes[node.name]
-- Move item around on flowing liquids
if def and def.liquidtype == "flowing" then
@ -1023,14 +909,12 @@ function mob_class:check_water_flow()
local f = 1.39
-- Set new item moving speed into the direciton of the liquid
local newv = vector.multiply(vec, f)
self.object:set_acceleration({x = 0, y = 0, z = 0})
self.object:set_velocity({x = newv.x, y = -0.22, z = newv.z})
self.object:set_acceleration(vector.zero())
self.object:set_velocity(vector.new(newv.x, -0.22, newv.z))
self.physical_state = true
self._flowing = true
self.object:set_properties({
physical = true
})
self.object:set_properties({ physical = true })
return
end
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
local rot = self.object:get_rotation()
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)
end
return true

View File

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

View File

@ -26,14 +26,14 @@ mcl_mobs.register_mob("mobs_mc:blaze", {
xp_min = 10,
xp_max = 10,
collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.79, 0.3},
rotate = -180,
rotate = 180,
head_yaw_offset = 180,
visual = "mesh",
mesh = "mobs_mc_blaze.b3d",
head_swivel = "head.control",
bone_eye_height = 4,
head_eye_height = 3.5,
head_eye_height = 1.4,
head_bone_position = vector.new( 0, 4, 0 ), -- for minetest <= 5.8
curiosity = 10,
head_yaw_offset = 180,
head_pitch_multiplier=-1,
textures = {
{"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},
floats = 1,
head_swivel = "head.control",
bone_eye_height = 4,
head_eye_height = 1.5,
horizontal_head_height = -.3,
head_eye_height = 0.5,
head_bone_position = vector.new(0, 4, -.3), -- for minetest <= 5.8
curiosity = 10,
head_yaw="z",
visual_size = {x=1,y=1},

View File

@ -22,9 +22,8 @@ local cow_def = {
"blank.png",
}, },
head_swivel = "head.control",
bone_eye_height = 10,
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,
head_yaw="z",
makes_footstep_sound = true,

View File

@ -25,7 +25,8 @@ mcl_mobs.register_mob("mobs_mc:iron_golem", {
visual = "mesh",
mesh = "mobs_mc_iron_golem.b3d",
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,
textures = {
{"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
head_swivel = "head.control",
bone_eye_height = 11,
head_eye_height = 3,
horizontal_head_height=0,
curiosity = 60,
head_eye_height = 1.5,
head_yaw = "z",
head_bone_position = vector.new( 0, 11, 0 ), -- for minetest <= 5.8
curiosity = 60,
hp_min = 15,
hp_max = 30,

View File

@ -37,9 +37,8 @@ local ocelot = {
xp_min = 1,
xp_max = 3,
head_swivel = "head.control",
bone_eye_height = 6.2,
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",
curiosity = 4,
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_max = 3,
head_swivel = "head.control",
bone_eye_height = 1.1,
horizontal_head_height=0,
head_bone_position = vector.new( 0, 1.1, 0 ), -- for minetest <= 5.8
curiosity = 10,
collisionbox = {-0.25, -0.01, -0.25, 0.25, 0.89, 0.25},
visual = "mesh",

View File

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

View File

@ -252,7 +252,7 @@ local zombified_piglin = {
damage = 9,
reach = 2,
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,
curiosity = 15,
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)
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.head_eye_height = 0.8
baby_zombified_piglin.xp_min = 13
baby_zombified_piglin.xp_max = 13
baby_zombified_piglin.textures = {

View File

@ -43,10 +43,10 @@ pillager = {
arrow = "mcl_bows:arrow_entity",
attack_type = "dogshoot", -- Alternate punching/shooting
attack_npcs = true,
reach = 0, -- Punching max distance
damage = 0, -- Punching damage
reach = 2, -- Punching max distance
damage = 2, -- Punching damage
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)
sounds = {
random = "mobs_mc_pillager_grunt2",

View File

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

View File

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

View File

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

View File

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

View File

@ -26,7 +26,8 @@ local skeleton = {
pathfinding = 1,
group_attack = true,
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,
visual = "mesh",
mesh = "mobs_mc_skeleton.b3d",

View File

@ -25,7 +25,8 @@ mcl_mobs.register_mob("mobs_mc:witherskeleton", {
visual = "mesh",
mesh = "mobs_mc_witherskeleton.b3d",
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,
textures = {
{

View File

@ -63,7 +63,8 @@ local spider = {
end
end,
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,
head_yaw="z",
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",
mesh = "vl_stalker.b3d",
-- head_swivel = "Head_Control",
bone_eye_height = 2.35,
head_eye_height = 1.8;
head_eye_height = 1.2;
head_bone_position = vector.new( 0, 2.35, 0 ), -- for minetest <= 5.8
curiosity = 2,
textures = {
{get_texture({}),

View File

@ -952,6 +952,9 @@ local function go_home(entity, sleep)
entity.order = nil
return
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)
local b = entity._bed
@ -1331,7 +1334,7 @@ local function do_work (self)
--mcl_log("Jobsite not valid")
return false
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!")
return true
else
@ -2110,8 +2113,8 @@ mcl_mobs.register_mob("mobs_mc:villager", {
hp_min = 20,
hp_max = 20,
head_swivel = "head.control",
bone_eye_height = 6.3,
head_eye_height = 2.2,
head_eye_height = 1.5,
head_bone_position = vector.new( 0, 6.3, 0 ), -- for minetest <= 5.8
curiosity = 10,
runaway = true,
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_max = 10,
head_swivel = "head.control",
bone_eye_height = 6.3,
head_eye_height = 2.2,
head_eye_height = 1.5,
head_bone_position = vector.new( 0, 6.3, 0 ), -- for minetest <= 5.8
curiosity = 10,
collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.95, 0.4},
visual = "mesh",

View File

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

View File

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

View File

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

View File

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

View File

@ -54,8 +54,8 @@ local zombie = {
xp_min = 5,
xp_max = 5,
head_swivel = "head.control",
bone_eye_height = 6.3,
head_eye_height = 2.2,
head_eye_height = 1.4,
head_bone_position = vector.new( 0, 6.3, 0 ), -- for minetest <= 5.8
curiosity = 7,
head_pitch_multiplier=-1,
breath_max = -1,
@ -110,6 +110,7 @@ mcl_mobs.register_mob("mobs_mc:zombie", zombie)
local baby_zombie = table.copy(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.xp_min = 12
baby_zombie.xp_max = 12

View File

@ -418,6 +418,18 @@ minetest.register_on_joinplayer(function(player)
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
function mcl_inventory.set_creative_formspec(player)
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"
end
return table.concat({
"style[" .. this_tab .. ";border=false;bgimg=;bgimg_pressed=;noclip=true]",
"image[" .. offset[this_tab] .. ";1.5,1.44;" .. bg_img .. "]",
"style[" .. this_tab .. ";border=false;bgimg=;bgimg_pressed=]",
"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 .. ";]",
})
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])) .. "]"
end
local touch_enabled = is_touch_enabled(playername)
players[playername].last_touch_enabled = touch_enabled
local formspec = table.concat({
"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
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]",
})
end
formspec = formspec .. "container_end[]"
if pagenum then formspec = formspec .. "p" .. tostring(pagenum) end
player:set_inventory_formspec(formspec)
end
@ -655,54 +680,54 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
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
set_inv_page("blocks", player)
page = "blocks"
elseif fields.deco then
elseif fields.deco or fields.deco_outer then
if players[name].page == "deco" then return end
set_inv_page("deco", player)
page = "deco"
elseif fields.redstone then
elseif fields.redstone or fields.redstone_outer then
if players[name].page == "redstone" then return end
set_inv_page("redstone", player)
page = "redstone"
elseif fields.rail then
elseif fields.rail or fields.rail_outer then
if players[name].page == "rail" then return end
set_inv_page("rail", player)
page = "rail"
elseif fields.misc then
elseif fields.misc or fields.misc_outer then
if players[name].page == "misc" then return end
set_inv_page("misc", player)
page = "misc"
elseif fields.nix then
elseif fields.nix or fields.nix_outer then
set_inv_page("all", player)
page = "nix"
elseif fields.food then
elseif fields.food or fields.food_outer then
if players[name].page == "food" then return end
set_inv_page("food", player)
page = "food"
elseif fields.tools then
elseif fields.tools or fields.tools_outer then
if players[name].page == "tools" then return end
set_inv_page("tools", player)
page = "tools"
elseif fields.combat then
elseif fields.combat or fields.combat_outer then
if players[name].page == "combat" then return end
set_inv_page("combat", player)
page = "combat"
elseif fields.mobs then
elseif fields.mobs or fields.mobs_outer then
if players[name].page == "mobs" then return end
set_inv_page("mobs", player)
page = "mobs"
elseif fields.brew then
elseif fields.brew or fields.brew_outer then
if players[name].page == "brew" then return end
set_inv_page("brew", player)
page = "brew"
elseif fields.matr then
elseif fields.matr or fields.matr_outer then
if players[name].page == "matr" then return end
set_inv_page("matr", player)
page = "matr"
elseif fields.inv then
elseif fields.inv or fields.inv_outer then
if players[name].page == "inv" then return end
page = "inv"
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)
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
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.
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.
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.
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
end,
groups = {weapon=1,weapon_ranged=1,bow=1,cannot_block=1,enchantability=1},
touch_interaction = "short_dig_long_place",
_mcl_uses = 385,
})
@ -235,6 +236,7 @@ for level=0, 2 do
on_place = function(itemstack)
return itemstack
end,
touch_interaction = "short_dig_long_place",
_mcl_uses = 385,
})
end

View File

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

View File

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