Compare commits
34 Commits
3e214793aa
...
95bcf86cbd
Author | SHA1 | Date |
---|---|---|
kno10 | 95bcf86cbd | |
kno10 | 1bc24999b4 | |
kno10 | 3adbd63fd6 | |
kno10 | 76d67ba941 | |
kno10 | 304b83d7ac | |
kno10 | 73a303ac83 | |
kno10 | f629a92e92 | |
kno10 | 1e5139a09d | |
kno10 | 3ff229224b | |
kno10 | 9b2d0d1b32 | |
kno10 | de60029d1a | |
kno10 | cf274dba4a | |
kno10 | fa9bde370b | |
kno10 | 935fc15770 | |
kno10 | 63198c6f9b | |
kno10 | 92aad6fd9a | |
kno10 | b58e5dedee | |
kno10 | 71fe8bf146 | |
kno10 | cf73d64550 | |
kno10 | a0bce0d2fd | |
kno10 | 6f4a82cf9e | |
kno10 | 995972bb40 | |
kno10 | f0add79a8d | |
kno10 | d060ec65a9 | |
kno10 | 4b47eb304e | |
kno10 | 6d7ceb37ce | |
kno10 | 2d4f5a4518 | |
marro | 4dc5d0939c | |
the-real-herowl | 32b334322b | |
grorp | 88c3c4558b | |
grorp | 3954acdfb7 | |
grorp | 02b354f54a | |
grorp | cb624fe1d9 | |
grorp | bd9ab16762 |
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 = {
|
||||
{
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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({}),
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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({
|
||||
|
|
Loading…
Reference in New Issue