Compare commits
44 Commits
658ac47d71
...
4c3b08c68d
Author | SHA1 | Date |
---|---|---|
kno10 | 4c3b08c68d | |
kno10 | 98600c87de | |
kno10 | a5cfaed843 | |
kno10 | 8c38745f6c | |
kno10 | 19c1863100 | |
kno10 | 250fc73e92 | |
kno10 | 9ea8945ff4 | |
kno10 | 488f61ba1b | |
kno10 | 16e17ba1ed | |
kno10 | 8ae66b5fad | |
kno10 | 8842e4224c | |
kno10 | a94b3a8d4a | |
kno10 | 8fce90111b | |
kno10 | 645f884e7f | |
kno10 | 46f7c29451 | |
kno10 | ddd180f5be | |
kno10 | b473d55644 | |
kno10 | 265dce301b | |
kno10 | 3538efbf52 | |
kno10 | ab673cdf8b | |
kno10 | 455353de27 | |
kno10 | b99e1a4b08 | |
kno10 | 0730cce236 | |
kno10 | 98f899fd67 | |
kno10 | 4b9e8aabb2 | |
kno10 | 936e47e5f9 | |
kno10 | 46b5416359 | |
kno10 | 72f825ade3 | |
kno10 | ec7e7c14bc | |
kno10 | f8a6da10cb | |
kno10 | 2e5b0bb3c7 | |
Mikita Wiśniewski | 41b188caea | |
kno10 | ae7995d195 | |
kno10 | e293cbe631 | |
the-real-herowl | fd6cac5f0c | |
teknomunk | e864cc19ed | |
teknomunk | 66c3c014a1 | |
teknomunk | 7807093b50 | |
teknomunk | f6c3f4bd16 | |
teknomunk | 96a03b1923 | |
teknomunk | 2145470f63 | |
teknomunk | 2ca0ccd8fe | |
teknomunk | 614518c6cd | |
kno10 | 253a06fa08 |
|
@ -1,29 +1,23 @@
|
|||
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
|
||||
|
@ -33,13 +27,21 @@ if minetest.settings:get_bool("only_peaceful_mobs", false) then
|
|||
end)
|
||||
end
|
||||
|
||||
-- not used yet
|
||||
function mob_class:safe_remove()
|
||||
self._removed = true
|
||||
minetest.after(0,function(obj)
|
||||
if obj and obj:get_pos() then
|
||||
mcl_burning.extinguish(obj)
|
||||
obj:remove()
|
||||
end
|
||||
end,self.object)
|
||||
end
|
||||
|
||||
function mob_class:update_tag() --update nametag and/or the debug box
|
||||
local tag
|
||||
if mobs_debug then
|
||||
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 +58,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 +74,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 +91,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 +108,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
|
||||
|
@ -136,44 +121,27 @@ end
|
|||
|
||||
function mob_class:mob_activate(staticdata, def, dtime)
|
||||
if not self.object:get_pos() or staticdata == "remove" then
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
self:safe_remove()
|
||||
return
|
||||
end
|
||||
if self.type == "monster"
|
||||
and minetest.settings:get_bool("only_peaceful_mobs", false) then
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
if self.type == "monster" and minetest.settings:get_bool("only_peaceful_mobs", false) then
|
||||
self:safe_remove()
|
||||
return
|
||||
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,38 +149,19 @@ 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
|
||||
local vis_size = self.base_size
|
||||
local colbox = self.base_colbox
|
||||
local selbox = self.base_selbox
|
||||
self.textures = self.gotten and def.gotten_texture or self.base_texture
|
||||
self.mesh = self.gotten and def.gotten_mesh or self.base_mesh
|
||||
self.visual_size = self.base_size
|
||||
self.collisionbox = self.base_colbox
|
||||
self.selectionbox = self.base_selbox
|
||||
|
||||
if self.gotten == true
|
||||
and def.gotten_texture then
|
||||
textures = def.gotten_texture
|
||||
end
|
||||
if self.child then
|
||||
self.visual_size = { x = self.base_size.x * .5, y = self.base_size.y * .5 }
|
||||
self.textures = def.child_texture and def.child_texture[1] or self.textures
|
||||
|
||||
if self.gotten == true
|
||||
and def.gotten_mesh then
|
||||
mesh = def.gotten_mesh
|
||||
end
|
||||
|
||||
if self.child == true then
|
||||
|
||||
vis_size = {
|
||||
x = self.base_size.x * .5,
|
||||
y = self.base_size.y * .5,
|
||||
}
|
||||
|
||||
if def.child_texture then
|
||||
textures = def.child_texture[1]
|
||||
end
|
||||
|
||||
colbox = {
|
||||
self.collisionbox = {
|
||||
self.base_colbox[1] * .5,
|
||||
self.base_colbox[2] * .5,
|
||||
self.base_colbox[3] * .5,
|
||||
|
@ -220,7 +169,7 @@ function mob_class:mob_activate(staticdata, def, dtime)
|
|||
self.base_colbox[5] * .5,
|
||||
self.base_colbox[6] * .5
|
||||
}
|
||||
selbox = {
|
||||
self.selectionbox = {
|
||||
self.base_selbox[1] * .5,
|
||||
self.base_selbox[2] * .5,
|
||||
self.base_selbox[3] * .5,
|
||||
|
@ -230,16 +179,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
|
||||
self.health = (self.health and self.health > 0 and self.health) or math.random(self.hp_min, self.hp_max)
|
||||
self.breath = self.breath or self.breath_max
|
||||
|
||||
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
|
||||
|
@ -258,13 +203,11 @@ function mob_class:mob_activate(staticdata, def, dtime)
|
|||
self.old_y = self.object:get_pos().y
|
||||
self.old_health = self.health
|
||||
self.sounds.distance = self.sounds.distance or 10
|
||||
self.textures = textures
|
||||
self.mesh = mesh
|
||||
self.collisionbox = colbox
|
||||
self.selectionbox = selbox
|
||||
self.visual_size = vis_size
|
||||
self.standing_in = "ignore"
|
||||
self.standing_on = "ignore"
|
||||
self.standing_in = mcl_mobs.NODE_IGNORE
|
||||
self.standing_on = mcl_mobs.NODE_IGNORE
|
||||
self.standing_under = mcl_mobs.NODE_IGNORE
|
||||
self.standing_depth = 0
|
||||
self.state = self.state or "stand"
|
||||
self.jump_sound_cooloff = 0 -- used to prevent jump sound from being played too often in short time
|
||||
self.opinion_sound_cooloff = 0 -- used to prevent sound spam of particular sound types
|
||||
|
||||
|
@ -276,42 +219,24 @@ 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.acceleration = vector.zero()
|
||||
|
||||
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 +244,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 +266,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,68 +282,46 @@ 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
|
||||
end
|
||||
return true
|
||||
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
|
||||
self:turn_in_direction(-posx, -posz, 1) -- turn to world spawn
|
||||
self.state = "walk"
|
||||
self:set_animation("walk")
|
||||
self:set_velocity(self.walk_velocity)
|
||||
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 not pos or self._removed then return end
|
||||
if self:outside_limits() then return end
|
||||
if self:check_despawn(pos, dtime) then return end
|
||||
|
||||
-- Start: Death/damage processing
|
||||
-- All damage needs to be undertaken at the start. We need to exit processing if the mob dies.
|
||||
if self:check_death_and_slow_mob() then
|
||||
--minetest.log("action", "Mob is dying: ".. tostring(self.name))
|
||||
-- Do we abandon out of here now?
|
||||
end
|
||||
|
||||
if self:falling(pos, moveresult) then return end
|
||||
if self:step_damage (dtime, pos) then return end
|
||||
pos = self:limit_vel_acc_for_large_dtime(pos, dtime, moveresult) -- limit maximum movement to reduce lag effects
|
||||
self:update_standing(pos, moveresult) -- update what we know of the mobs environment for physics and movement
|
||||
local player_in_active_range = self:player_in_active_range()
|
||||
-- The following functions return true when the mob died and we should stop processing
|
||||
if self:check_suspend(player_in_active_range) then return end
|
||||
-- initializes self.acceleration:
|
||||
if self:gravity_and_floating(pos, dtime, moveresult) then return end -- keep early, for gravity!
|
||||
if self:check_dying() then return end
|
||||
if self:step_damage(dtime, pos) then return end
|
||||
self:check_water_flow(dtime, pos)
|
||||
|
||||
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)
|
||||
self: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 +338,76 @@ 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_custom and self:do_custom(dtime) == false then return end
|
||||
if self:do_states(dtime, player_in_active_range) then return end
|
||||
|
||||
self:smooth_acceleration(dtime)
|
||||
local cx, cz = self:collision()
|
||||
self.object:add_velocity(vector.new(cx, 0, cz))
|
||||
self:update_vel_acc(dtime) -- applies self.acceleration
|
||||
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
|
||||
-- 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
|
||||
if xpcall then
|
||||
status, retVal = xpcall(on_step_work, on_step_error_handler, self, dtime, moveresult)
|
||||
else
|
||||
return on_step_work (self, dtime, moveresult)
|
||||
status, retVal = pcall(on_step_work, self, dtime, moveresult)
|
||||
end
|
||||
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
|
||||
|
@ -543,10 +424,7 @@ local function update_lifetimer(dtime)
|
|||
timer = 0
|
||||
end
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
update_lifetimer(dtime)
|
||||
end)
|
||||
|
||||
minetest.register_globalstep(update_lifetimer)
|
||||
|
||||
minetest.register_chatcommand("clearmobs", {
|
||||
privs = { maphack = true },
|
||||
|
@ -560,11 +438,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 +452,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 +469,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 +477,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
|
||||
|
@ -618,49 +485,29 @@ minetest.register_chatcommand("clearmobs", {
|
|||
elseif mob_type == "passive" and o.type ~= "monster" and o.type ~= "npc" then
|
||||
--minetest.log("Match - passive")
|
||||
mob_match = true
|
||||
else
|
||||
--minetest.log("No match for type.")
|
||||
--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
|
||||
else
|
||||
--minetest.log("No match - o.type = ".. tostring(o.type))
|
||||
--minetest.log("No match - mob_name = ".. tostring(o.name))
|
||||
--minetest.log("No match - mob_type = ".. tostring(mob_name))
|
||||
--else
|
||||
-- minetest.log("No match - o.type = ".. tostring(o.type))
|
||||
-- minetest.log("No match - mob_name = ".. tostring(o.name))
|
||||
-- minetest.log("No match - mob_type = ".. tostring(mob_name))
|
||||
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
|
||||
})
|
||||
|
|
|
@ -58,6 +58,7 @@ functions needed for the mob to work properly which contains the following:
|
|||
that are not liquids
|
||||
'runaway' if true causes animals to turn and run away when hit.
|
||||
'view_range' how many nodes in distance the mob can see a player.
|
||||
'see_through_opaque' override whether the mob can see through opaque nodes
|
||||
'damage' how many health points the mob does to a player or another
|
||||
mob when melee attacking.
|
||||
'knock_back' when true has mobs falling backwards when hit, the greater
|
||||
|
|
|
@ -5,7 +5,7 @@ local HORNY_TIME = 30
|
|||
local HORNY_AGAIN_TIME = 30 -- was 300 or 15*20
|
||||
local CHILD_GROW_TIME = 60
|
||||
|
||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager",false)
|
||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager", false)
|
||||
|
||||
local LOG_MODULE = "[mcl_mobs]"
|
||||
local function mcl_log (message)
|
||||
|
@ -29,18 +29,13 @@ end
|
|||
|
||||
-- feeding, taming and breeding (thanks blert2112)
|
||||
function mob_class:feed_tame(clicker, feed_count, breed, tame, notake)
|
||||
if not self.follow then
|
||||
return false
|
||||
end
|
||||
if clicker:get_wielded_item():get_definition()._mcl_not_consumable then
|
||||
return false
|
||||
end
|
||||
if not self.follow then return false end
|
||||
if clicker:get_wielded_item():get_definition()._mcl_not_consumable then return false end
|
||||
-- can eat/tame with item in hand
|
||||
if self.nofollow or self:follow_holding(clicker) then
|
||||
local consume_food = false
|
||||
|
||||
-- tame if not still a baby
|
||||
|
||||
if tame and not self.child then
|
||||
if not self.owner or self.owner == "" then
|
||||
self.tamed = true
|
||||
|
@ -50,27 +45,20 @@ function mob_class:feed_tame(clicker, feed_count, breed, tame, notake)
|
|||
end
|
||||
|
||||
-- increase health
|
||||
|
||||
if self.health < self.hp_max and not consume_food then
|
||||
consume_food = true
|
||||
self.health = math.min(self.health + 4, self.hp_max)
|
||||
|
||||
if self.htimer < 1 then
|
||||
self.htimer = 5
|
||||
end
|
||||
self.object:set_hp(self.health)
|
||||
end
|
||||
|
||||
-- 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)
|
||||
end
|
||||
|
||||
-- breed animals
|
||||
|
||||
if breed and not consume_food and self.hornytimer == 0 and not self.horny then
|
||||
self.food = (self.food or 0) + 1
|
||||
consume_food = true
|
||||
|
@ -105,25 +93,16 @@ end
|
|||
|
||||
-- Spawn a child
|
||||
function mcl_mobs.spawn_child(pos, mob_type)
|
||||
local child = mcl_mobs.spawn(pos, mob_type)
|
||||
if not child then
|
||||
return
|
||||
end
|
||||
local child = minetest.add_entity(pos, mob_type)
|
||||
if not child then return end
|
||||
|
||||
local ent = child:get_luaentity()
|
||||
mcl_mobs.effect(pos, 15, "mcl_particles_smoke.png", 1, 2, 2, 15, 5)
|
||||
|
||||
ent.child = true
|
||||
|
||||
local textures
|
||||
-- using specific child texture (if found)
|
||||
if ent.child_texture then
|
||||
textures = ent.child_texture[1]
|
||||
end
|
||||
mcl_mobs.effect(pos, 15, "mcl_particles_smoke.png", 1, 2, 2, 15, 5)
|
||||
|
||||
-- and resize to half height
|
||||
child:set_properties({
|
||||
textures = textures,
|
||||
textures = ent.child_texture and ent.child_texture[1],
|
||||
visual_size = {
|
||||
x = ent.base_size.x * .5,
|
||||
y = ent.base_size.y * .5,
|
||||
|
@ -155,16 +134,12 @@ end
|
|||
|
||||
-- find two animals of same type and breed if nearby and horny
|
||||
function mob_class:check_breeding()
|
||||
|
||||
--mcl_log("In breed function")
|
||||
-- child takes a long time before growing into adult
|
||||
if self.child == true then
|
||||
|
||||
-- When a child, hornytimer is used to count age until adulthood
|
||||
self.hornytimer = self.hornytimer + 1
|
||||
|
||||
if self.hornytimer >= CHILD_GROW_TIME then
|
||||
|
||||
self.child = false
|
||||
self.hornytimer = 0
|
||||
|
||||
|
@ -181,11 +156,7 @@ function mob_class:check_breeding()
|
|||
self.on_grown(self)
|
||||
else
|
||||
-- jump when fully grown so as not to fall into ground
|
||||
self.object:set_velocity({
|
||||
x = 0,
|
||||
y = self.jump_height,
|
||||
z = 0
|
||||
})
|
||||
self.object:set_velocity(vector.new(0, self.jump_height, 0))
|
||||
end
|
||||
|
||||
self.animation = nil
|
||||
|
@ -193,121 +164,79 @@ function mob_class:check_breeding()
|
|||
self._current_animation = nil -- Mobs Redo does nothing otherwise
|
||||
self:set_animation(anim)
|
||||
end
|
||||
|
||||
return
|
||||
else
|
||||
-- horny animal can mate for HORNY_TIME seconds,
|
||||
-- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
|
||||
if self.horny == true then
|
||||
self.hornytimer = self.hornytimer + 1
|
||||
end
|
||||
-- horny animal can mate for HORNY_TIME seconds,
|
||||
-- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
|
||||
if self.horny == true then
|
||||
self.hornytimer = self.hornytimer + 1
|
||||
|
||||
if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
|
||||
self.hornytimer = 0
|
||||
self.horny = false
|
||||
end
|
||||
if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
|
||||
self.hornytimer = 0
|
||||
self.horny = false
|
||||
end
|
||||
end
|
||||
|
||||
-- find another same animal who is also horny and mate if nearby
|
||||
if self.horny == true
|
||||
and self.hornytimer <= HORNY_TIME then
|
||||
|
||||
if self.horny and self.hornytimer <= HORNY_TIME then
|
||||
mcl_log("In breed function. All good. Do the magic.")
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
mcl_mobs.effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
|
||||
mcl_mobs.effect(vector.new(pos.x, pos.y + 1, pos.z), 8, "heart.png", 3, 4, 1, 0.1)
|
||||
|
||||
local objs = minetest.get_objects_inside_radius(pos, 3)
|
||||
local num = 0
|
||||
local ent = nil
|
||||
|
||||
for n = 1, #objs do
|
||||
|
||||
ent = objs[n]:get_luaentity()
|
||||
local ent = objs[n]:get_luaentity()
|
||||
|
||||
-- check for same animal with different colour
|
||||
local canmate = false
|
||||
|
||||
if ent then
|
||||
|
||||
if ent.name == self.name then
|
||||
canmate = true
|
||||
else
|
||||
local entname = string.split(ent.name,":")
|
||||
local selfname = string.split(self.name,":")
|
||||
|
||||
if entname[1] == selfname[1] then
|
||||
entname = string.split(entname[2],"_")
|
||||
selfname = string.split(selfname[2],"_")
|
||||
|
||||
if entname[1] == selfname[1] then
|
||||
canmate = true
|
||||
end
|
||||
if entname[1] == selfname[1] then canmate = true end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if canmate then mcl_log("In breed function. Can mate.") end
|
||||
|
||||
if ent
|
||||
and canmate == true
|
||||
and ent.horny == true
|
||||
and ent.hornytimer <= HORNY_TIME then
|
||||
if ent and canmate and ent.horny and ent.hornytimer <= HORNY_TIME then
|
||||
num = num + 1
|
||||
end
|
||||
|
||||
-- found your mate? then have a baby
|
||||
if num > 1 then
|
||||
|
||||
self.hornytimer = HORNY_TIME + 1
|
||||
ent.hornytimer = HORNY_TIME + 1
|
||||
|
||||
-- spawn baby
|
||||
|
||||
|
||||
minetest.after(5, function(parent1, parent2, pos)
|
||||
if not parent1.object:get_luaentity() then
|
||||
return
|
||||
end
|
||||
if not parent2.object:get_luaentity() then
|
||||
return
|
||||
end
|
||||
if not parent1.object:get_luaentity() then return end
|
||||
if not parent2.object:get_luaentity() then return end
|
||||
|
||||
mcl_experience.throw_xp(pos, math.random(1, 7) + (parent1._luck or 0) + (parent2._luck or 0))
|
||||
|
||||
-- custom breed function
|
||||
if parent1.on_breed then
|
||||
-- when false, skip going any further
|
||||
if parent1.on_breed(parent1, parent2) == false then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if parent1.on_breed and not parent1.on_breed(parent1, parent2) then return end
|
||||
pos = vector.round(pos)
|
||||
local child = mcl_mobs.spawn_child(pos, parent1.name)
|
||||
if not child then return end
|
||||
|
||||
local ent_c = child:get_luaentity()
|
||||
|
||||
|
||||
-- Use texture of one of the parents
|
||||
local p = math.random(1, 2)
|
||||
if p == 1 then
|
||||
ent_c.base_texture = parent1.base_texture
|
||||
else
|
||||
ent_c.base_texture = parent2.base_texture
|
||||
end
|
||||
child:set_properties({
|
||||
textures = ent_c.base_texture
|
||||
})
|
||||
ent_c.base_texture = math.random(1, 2) == 1 and parent1.base_texture or parent2.base_texture
|
||||
child:set_properties({ textures = ent_c.base_texture })
|
||||
|
||||
-- tamed and owned by parents' owner
|
||||
ent_c.tamed = true
|
||||
ent_c.owner = parent1.owner
|
||||
end, self, ent, pos)
|
||||
|
||||
num = 0
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
@ -315,9 +244,7 @@ function mob_class:check_breeding()
|
|||
end
|
||||
|
||||
function mob_class:toggle_sit(clicker,p)
|
||||
if not self.tamed or self.child or self.owner ~= clicker:get_player_name() then
|
||||
return
|
||||
end
|
||||
if not self.tamed or self.child or self.owner ~= clicker:get_player_name() then return end
|
||||
local pos = self.object:get_pos()
|
||||
local particle
|
||||
if not self.order or self.order == "" or self.order == "sit" then
|
||||
|
@ -345,7 +272,7 @@ function mob_class:toggle_sit(clicker,p)
|
|||
-- Display icon to show current order (sit or roam)
|
||||
minetest.add_particle({
|
||||
pos = vector.add(pos, pp),
|
||||
velocity = {x=0,y=0.2,z=0},
|
||||
velocity = vector.new(0, 0.2, 0),
|
||||
expirationtime = 1,
|
||||
size = 4,
|
||||
texture = particle,
|
||||
|
|
|
@ -3,6 +3,7 @@ local mob_class = mcl_mobs.mob_class
|
|||
|
||||
local damage_enabled = minetest.settings:get_bool("enable_damage")
|
||||
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
|
||||
local mobs_see_through_opaque = mcl_mobs.see_through_opaque
|
||||
|
||||
-- pathfinding settings
|
||||
local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
|
||||
|
@ -11,39 +12,33 @@ 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 sqrt = math.sqrt
|
||||
local vector_offset = vector.offset
|
||||
local vector_new = vector.new
|
||||
local vector_copy = vector.copy
|
||||
local vector_distance = vector.distance
|
||||
local vector_zero = vector.zero
|
||||
local node_ok = mcl_mobs.node_ok -- TODO: remove
|
||||
|
||||
-- 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 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,22 +50,19 @@ 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 ent = objs[n]:get_luaentity()
|
||||
local damage = floor((4 / dist) * radius)
|
||||
--local ent = objs[n]:get_luaentity()
|
||||
|
||||
-- punches work on entities AND players
|
||||
objs[n]:punch(objs[n], 1.0, {
|
||||
|
@ -87,75 +79,58 @@ 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 = self:line_of_sight(vector_offset(s, 0, .5, 0), vector_offset(target_pos, 0, 1.5, 0),
|
||||
self.see_through_opaque or mobs_see_through_opaque, false)
|
||||
|
||||
-- im stuck, search for path
|
||||
if not has_lineofsight then
|
||||
|
||||
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
|
||||
|
@ -170,35 +145,23 @@ function mob_class:smart_mobs(s, p, dist, dtime)
|
|||
if use_pathfind then
|
||||
-- lets try find a path, first take care of positions
|
||||
-- since pathfinder is very sensitive
|
||||
local sheight = self.collisionbox[5] - self.collisionbox[2]
|
||||
--local sheight = self.collisionbox[5] - self.collisionbox[2]
|
||||
|
||||
-- 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)
|
||||
elseif self.stepheight > 0.5 then
|
||||
jumpheight = 1
|
||||
end
|
||||
local jumpheight = self.jump and floor(self.jump_height + 0.1) or 0
|
||||
self.path.way = minetest.find_path(s, p1, 16, jumpheight, dropheight, "A*_noprefetch")
|
||||
|
||||
self.state = ""
|
||||
|
@ -206,34 +169,26 @@ 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})
|
||||
if self.standing_in.buildable_to or self.standing_in.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 +198,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 +226,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 +260,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 +326,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 +348,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 +359,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,13 +377,13 @@ 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
|
||||
sp.y = sp.y + 1
|
||||
|
||||
if dist < min_dist and self:line_of_sight( sp, p, 2) == true then
|
||||
if dist < min_dist and self:line_of_sight(sp, p, 2) == true then
|
||||
min_dist = dist
|
||||
min_player = obj.object
|
||||
end
|
||||
|
@ -466,19 +399,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 +452,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 +463,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 +482,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 +507,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 +529,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 +537,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 +557,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 +568,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 +600,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 * sqrt(v.x*v.x+v.z*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,34 +622,30 @@ 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 dir_dot = hv.x * dir.x + hv.z * dir.z
|
||||
local player_mag = sqrt(hv.x * hv.x + hv.z * hv.z)
|
||||
local mob_mag = sqrt(v.x * v.x + v.z * v.z)
|
||||
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
|
||||
kb = kb + luaentity._knockback * 0.25
|
||||
elseif luaentity and luaentity._knockback and die == true then
|
||||
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 +653,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, dir.z * kb ))
|
||||
|
||||
self.pause_timer = 0.25
|
||||
end
|
||||
|
@ -772,12 +661,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"
|
||||
|
@ -805,12 +697,8 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
|
|||
local alert_pos = hitter:get_pos()
|
||||
if alert_pos then
|
||||
local objs = minetest.get_objects_inside_radius(alert_pos, self.view_range)
|
||||
local obj = nil
|
||||
|
||||
for n = 1, #objs do
|
||||
|
||||
obj = objs[n]:get_luaentity()
|
||||
|
||||
local obj = objs[n]:get_luaentity()
|
||||
if obj then
|
||||
-- only alert members of same mob or friends
|
||||
if obj.group_attack
|
||||
|
@ -840,11 +728,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 +736,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,25 +762,21 @@ 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()
|
||||
if not s or not p 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
|
||||
if not self.attack
|
||||
or not self.attack:get_pos()
|
||||
or not self:object_in_range(self.attack)
|
||||
or self.attack:get_hp() <= 0
|
||||
or (self.attack:is_player() and mcl_mobs.invis[ self.attack:get_player_name() ]) then
|
||||
or (self.attack:is_player() and mcl_mobs.invis[self.attack:get_player_name()]) then
|
||||
|
||||
clear_aggro(self)
|
||||
return
|
||||
|
@ -920,20 +800,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 +835,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 +880,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, 1)
|
||||
|
||||
-- move towards enemy if beyond mob reach
|
||||
if dist > self.reach then
|
||||
|
@ -1100,10 +933,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)
|
||||
|
@ -1121,7 +953,7 @@ function mob_class:do_states_attack (dtime)
|
|||
self.path.stuck_timer = 0
|
||||
self.path.following = false -- not stuck anymore
|
||||
|
||||
self:set_velocity( 0)
|
||||
self:set_velocity(0)
|
||||
|
||||
local attack_frequency = self.attack_frequency or 1
|
||||
|
||||
|
@ -1129,19 +961,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,68 +993,43 @@ 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 = sqrt(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z)
|
||||
local dir = -atan2(p.x - s.x, p.z - s.z)
|
||||
self:set_yaw(dir, 4)
|
||||
|
||||
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 = math.random(0, 1) * 2 - 1 end
|
||||
if random(50) == 1 then self.strafe_direction = -self.strafe_direction 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)
|
||||
--stay away from player so as to shoot them
|
||||
if self.avoid_distance and self.shooter_avoid_enemy then
|
||||
local f = (dist - self.avoid_distance) / self.avoid_distance
|
||||
f = math.max(-1, math.min(1, f))
|
||||
f = f * math.abs(f)
|
||||
self:set_velocity(f * self.walk_velocity, (1 - math.abs(f)) * self.strafe_direction * self.walk_velocity * 1.5)
|
||||
elseif dist > 1 then
|
||||
self:set_velocity(self.walk_velocity)
|
||||
else
|
||||
self:set_velocity(0)
|
||||
end
|
||||
else
|
||||
self:set_velocity( 0)
|
||||
self:set_velocity(0)
|
||||
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")
|
||||
self:set_animation("shoot")
|
||||
|
||||
-- play shoot attack sound
|
||||
self:mob_sound("shoot_attack")
|
||||
|
||||
-- Shoot arrow
|
||||
if minetest.registered_entities[self.arrow] then
|
||||
|
||||
local arrow, ent
|
||||
local v = 1
|
||||
if not self.shoot_arrow then
|
||||
|
@ -1238,9 +1039,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 +1051,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 +1062,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
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
local math, tonumber, vector, minetest, mcl_mobs = math, tonumber, vector, minetest, mcl_mobs
|
||||
local mob_class = mcl_mobs.mob_class
|
||||
local validate_vector = mcl_util.validate_vector
|
||||
--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 PATHFINDING = "gowp"
|
||||
|
||||
|
@ -13,51 +14,31 @@ if player_transfer_distance == 0 then player_transfer_distance = math.huge end
|
|||
|
||||
-- custom particle effects
|
||||
function mcl_mobs.effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, go_down)
|
||||
|
||||
radius = radius or 2
|
||||
min_size = min_size or 0.5
|
||||
max_size = max_size or 1
|
||||
gravity = gravity or DEFAULT_FALL_SPEED
|
||||
glow = glow or 0
|
||||
go_down = go_down or false
|
||||
|
||||
local ym
|
||||
if go_down then
|
||||
ym = 0
|
||||
else
|
||||
ym = -radius
|
||||
end
|
||||
|
||||
minetest.add_particlespawner({
|
||||
amount = amount,
|
||||
time = 0.25,
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = {x = -radius, y = ym, z = -radius},
|
||||
maxvel = {x = radius, y = radius, z = radius},
|
||||
minacc = {x = 0, y = gravity, z = 0},
|
||||
maxacc = {x = 0, y = gravity, z = 0},
|
||||
minvel = vector.new(-radius, go_down and 0 or -radius, -radius),
|
||||
maxvel = vector.new(radius, radius, radius),
|
||||
minacc = vector.new(0, gravity or DEFAULT_FALL_SPEED, 0),
|
||||
maxacc = vector.new(0, gravity or DEFAULT_FALL_SPEED, 0),
|
||||
minexptime = 0.1,
|
||||
maxexptime = 1,
|
||||
minsize = min_size,
|
||||
maxsize = max_size,
|
||||
minsize = min_size or 0.5,
|
||||
maxsize = max_size or 1,
|
||||
texture = texture,
|
||||
glow = glow,
|
||||
glow = glow or 0,
|
||||
})
|
||||
end
|
||||
|
||||
function mcl_mobs.death_effect(pos, yaw, collisionbox, rotate)
|
||||
local min, max
|
||||
if collisionbox then
|
||||
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
|
||||
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
|
||||
else
|
||||
min = { x = -0.5, y = 0, z = -0.5 }
|
||||
max = { x = 0.5, y = 0.5, z = 0.5 }
|
||||
end
|
||||
local min = collisionbox and vector.new(collisionbox[1], collisionbox[2], collisionbox[3]) or vector.new(-0.5, 0, -0.5)
|
||||
local max = collisionbox and vector.new(collisionbox[4], collisionbox[5], collisionbox[6]) or vector.new(0.5, 0.5, 0.5)
|
||||
if rotate then
|
||||
min = vector.rotate(min, {x=0, y=yaw, z=math.pi/2})
|
||||
max = vector.rotate(max, {x=0, y=yaw, z=math.pi/2})
|
||||
min = vector.rotate(min, vector.new(0, yaw, math.pi/2))
|
||||
max = vector.rotate(max, vector.new(0, yaw, math.pi/2))
|
||||
min, max = vector.sort(min, max)
|
||||
min = vector.multiply(min, 0.5)
|
||||
max = vector.multiply(max, 0.5)
|
||||
|
@ -89,57 +70,35 @@ end
|
|||
|
||||
-- play sound
|
||||
function mob_class:mob_sound(soundname, is_opinion, fixed_pitch)
|
||||
|
||||
local soundinfo
|
||||
if self.sounds_child and self.child then
|
||||
soundinfo = self.sounds_child
|
||||
elseif self.sounds then
|
||||
soundinfo = self.sounds
|
||||
end
|
||||
if not soundinfo then
|
||||
return
|
||||
end
|
||||
local soundinfo = self.child and self.sounds_child or self.sounds
|
||||
if not soundinfo then return end
|
||||
local sound = soundinfo[soundname]
|
||||
if sound then
|
||||
if is_opinion and self.opinion_sound_cooloff > 0 then
|
||||
return
|
||||
end
|
||||
local pitch
|
||||
if not fixed_pitch then
|
||||
local base_pitch = soundinfo.base_pitch
|
||||
if not base_pitch then
|
||||
base_pitch = 1
|
||||
end
|
||||
if self.child and (not self.sounds_child) then
|
||||
-- Children have higher pitch
|
||||
pitch = base_pitch * 1.5
|
||||
else
|
||||
pitch = base_pitch
|
||||
end
|
||||
-- randomize the pitch a bit
|
||||
pitch = pitch + math.random(-10, 10) * 0.005
|
||||
end
|
||||
-- Should be 0.1 to 0.2 for mobs. Cow and zombie farms loud. At least have cool down.
|
||||
minetest.sound_play(sound, {
|
||||
object = self.object,
|
||||
gain = 1.0,
|
||||
max_hear_distance = self.sounds.distance,
|
||||
pitch = pitch,
|
||||
}, true)
|
||||
self.opinion_sound_cooloff = 1
|
||||
if not sound then return end
|
||||
if is_opinion and self.opinion_sound_cooloff > 0 then return end
|
||||
local pitch
|
||||
if not fixed_pitch then
|
||||
pitch = soundinfo.base_pitch or 1
|
||||
if self.child and not self.sounds_child then pitch = pitch * 1.5 end
|
||||
pitch = pitch + (math.random() - 0.5) * 0.2
|
||||
end
|
||||
-- Should be 0.1 to 0.2 for mobs. Cow and zombie farms loud. At least have cool down.
|
||||
minetest.sound_play(sound, {
|
||||
object = self.object,
|
||||
gain = 1.0,
|
||||
max_hear_distance = self.sounds.distance,
|
||||
pitch = pitch,
|
||||
}, true)
|
||||
self.opinion_sound_cooloff = 1
|
||||
end
|
||||
|
||||
function mob_class:step_opinion_sound(dtime)
|
||||
if self.state ~= "attack" and self.state ~= PATHFINDING then
|
||||
|
||||
if self.opinion_sound_cooloff > 0 then
|
||||
self.opinion_sound_cooloff = self.opinion_sound_cooloff - dtime
|
||||
end
|
||||
-- mob plays random sound at times. Should be 120. Zombie and mob farms are ridiculous
|
||||
if math.random(1, 70) == 1 then
|
||||
self:mob_sound("random", true)
|
||||
end
|
||||
if self.state == "attack" or self.state == PATHFINDING then return end
|
||||
if self.opinion_sound_cooloff > 0 then
|
||||
self.opinion_sound_cooloff = self.opinion_sound_cooloff - dtime
|
||||
end
|
||||
-- mob plays random sound at times. Should be 120. Zombie and mob farms are ridiculous
|
||||
if math.random(1, 70) == 1 then
|
||||
self:mob_sound("random", true)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -169,33 +128,29 @@ function mob_class:remove_texture_mod(mod)
|
|||
table.insert(remove, i)
|
||||
end
|
||||
end
|
||||
for i=#remove, 1 do
|
||||
for i=#remove, 1, -1 do
|
||||
table.remove(self.texture_mods, remove[i])
|
||||
end
|
||||
self.object:set_texture_mod(full_mod)
|
||||
end
|
||||
|
||||
function mob_class:damage_effect(damage)
|
||||
-- damage particles
|
||||
if (not disable_blood) and damage > 0 then
|
||||
if disable_blood or damage <= 0 then return end
|
||||
local amount_large = math.floor(damage / 2)
|
||||
local amount_small = damage % 2
|
||||
|
||||
local amount_large = math.floor(damage / 2)
|
||||
local amount_small = damage % 2
|
||||
local pos = self.object:get_pos()
|
||||
pos.y = pos.y + (self.collisionbox[5] - self.collisionbox[2]) * .5
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
pos.y = pos.y + (self.collisionbox[5] - self.collisionbox[2]) * .5
|
||||
|
||||
local texture = "mobs_blood.png"
|
||||
-- full heart damage (one particle for each 2 HP damage)
|
||||
if amount_large > 0 then
|
||||
mcl_mobs.effect(pos, amount_large, texture, 2, 2, 1.75, 0, nil, true)
|
||||
end
|
||||
-- half heart damage (one additional particle if damage is an odd number)
|
||||
if amount_small > 0 then
|
||||
-- TODO: Use "half heart"
|
||||
mcl_mobs.effect(pos, amount_small, texture, 1, 1, 1.75, 0, nil, true)
|
||||
end
|
||||
local texture = "mobs_blood.png"
|
||||
-- full heart damage (one particle for each 2 HP damage)
|
||||
if amount_large > 0 then
|
||||
mcl_mobs.effect(pos, amount_large, texture, 2, 2, 1.75, 0, nil, true)
|
||||
end
|
||||
-- half heart damage (one additional particle if damage is an odd number)
|
||||
if amount_small > 0 then
|
||||
-- TODO: Use "half heart"
|
||||
mcl_mobs.effect(pos, amount_small, texture, 1, 1, 1.75, 0, nil, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -245,21 +200,15 @@ 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"
|
||||
elseif not self.object:get_attach() then
|
||||
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
|
||||
|
||||
|
@ -274,12 +223,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,
|
||||
|
@ -289,91 +233,67 @@ 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 dir_to_pitch(dir)
|
||||
--local dir2 = vector.normalize(dir)
|
||||
local xz = math.abs(dir.x) + math.abs(dir.z)
|
||||
return -math.atan2(-dir.y, xz)
|
||||
end
|
||||
|
||||
local function who_are_you_looking_at (self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
if self.order == "sleep" then
|
||||
self._locked_object = nil
|
||||
return
|
||||
end
|
||||
|
||||
local stop_look_at_player_chance = math.random(833/self.curiosity)
|
||||
-- was 10000 - div by 12 for avg entities as outside loop
|
||||
|
||||
local stop_look_at_player = stop_look_at_player_chance == 1
|
||||
local stop_look_at_player = math.random() * 833 <= self.curiosity
|
||||
|
||||
if self.attack then
|
||||
if not self.target_time_lost then
|
||||
self._locked_object = self.attack
|
||||
else
|
||||
self._locked_object = nil
|
||||
end
|
||||
self._locked_object = not self.target_time_lost and self.attack or nil
|
||||
elseif self.following then
|
||||
self._locked_object = self.following
|
||||
elseif self._locked_object then
|
||||
if stop_look_at_player then
|
||||
--minetest.log("Stop look: ".. self.name)
|
||||
self._locked_object = nil
|
||||
end
|
||||
if stop_look_at_player then self._locked_object = nil end
|
||||
elseif not self._locked_object then
|
||||
if mcl_util.check_dtime_timer(self, dtime, "step_look_for_someone", 0.2) then
|
||||
--minetest.log("Change look check: ".. self.name)
|
||||
|
||||
-- For the wither this was 20/60=0.33, so probably need to rebalance and divide rates.
|
||||
-- but frequency of check isn't good as it is costly. Making others too infrequent requires testing
|
||||
local chance = 150/self.curiosity
|
||||
|
||||
if chance < 1 then chance = 1 end
|
||||
local look_at_player_chance = math.random(chance)
|
||||
|
||||
-- was 5000 but called in loop based on entities. so div by 12 as estimate avg of entities found,
|
||||
-- then div by 20 as less freq lookup
|
||||
|
||||
local look_at_player = look_at_player_chance == 1
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 8)) do
|
||||
if obj:is_player() and vector.distance(pos,obj:get_pos()) < 4 then
|
||||
--minetest.log("Change look to player: ".. self.name)
|
||||
if obj:is_player() and vector.distance(pos, obj:get_pos()) < 4 then
|
||||
self._locked_object = obj
|
||||
break
|
||||
elseif obj:is_player() or (obj:get_luaentity() and obj:get_luaentity().name == self.name and self ~= obj:get_luaentity()) then
|
||||
if look_at_player then
|
||||
--minetest.log("Change look to mob: ".. self.name)
|
||||
elseif obj:is_player() or (obj:get_luaentity() and self ~= obj:get_luaentity() and obj:get_luaentity().name == self.name) then
|
||||
-- For the wither this was 20/60=0.33, so probably need to rebalance and divide rates.
|
||||
-- but frequency of check isn't good as it is costly. Making others too infrequent requires testing
|
||||
-- was 5000 but called in loop based on entities. so div by 12 as estimate avg of entities found,
|
||||
-- then div by 20 as less freq lookup
|
||||
if math.random() * 150 <= self.curiosity then
|
||||
self._locked_object = obj
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
function mob_class:check_head_swivel(dtime)
|
||||
if not self.head_swivel or type(self.head_swivel) ~= "string" then return end
|
||||
|
||||
who_are_you_looking_at(self, dtime)
|
||||
|
||||
who_are_you_looking_at (self, dtime)
|
||||
local newr = vector.zero()
|
||||
local oldp, oldr
|
||||
if self.object.get_bone_override then -- minetest >= 5.9
|
||||
local ov = self.object:get_bone_override(self.head_swivel)
|
||||
oldp, oldr = ov.position.vec, ov.rotation.vec
|
||||
else -- minetest < 5.9
|
||||
oldp, oldr = self.object:get_bone_position(self.head_swivel)
|
||||
oldr = vector.apply(oldr, math.rad) -- old API uses radians
|
||||
end
|
||||
|
||||
local final_rotation = vector.zero()
|
||||
local oldp,oldr = self.object:get_bone_position(self.head_swivel)
|
||||
|
||||
if self._locked_object and (self._locked_object:is_player() or self._locked_object:get_luaentity()) and self._locked_object:get_hp() > 0 then
|
||||
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 self._locked_object:get_luaentity() then
|
||||
_locked_object_eye_height = self._locked_object:get_luaentity().head_eye_height
|
||||
end
|
||||
if self._locked_object:is_player() then
|
||||
_locked_object_eye_height = self._locked_object:get_properties().eye_height
|
||||
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
|
||||
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?
|
||||
|
@ -381,58 +301,63 @@ function mob_class:check_head_swivel(dtime)
|
|||
self_rot = self.object:get_attach():get_rotation()
|
||||
end
|
||||
|
||||
local player_pos = self._locked_object:get_pos()
|
||||
local direction_player = vector.direction(vector.add(self.object:get_pos(), vector.new(0, self.head_eye_height*.7, 0)), vector.add(player_pos, vector.new(0, _locked_object_eye_height, 0)))
|
||||
local mob_yaw = math.deg(-(-(self_rot.y)-(-minetest.dir_to_yaw(direction_player))))+self.head_yaw_offset
|
||||
local mob_pitch = math.deg(-dir_to_pitch(direction_player))*self.head_pitch_multiplier
|
||||
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
|
||||
|
||||
if (mob_yaw < -60 or mob_yaw > 60) and not (self.attack and self.state == "attack" and not self.runaway) then
|
||||
final_rotation = vector.multiply(oldr, 0.9)
|
||||
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
|
||||
final_rotation = vector.new(mob_pitch, mob_yaw, 0)
|
||||
newr = vector.new(mob_pitch, mob_yaw, 0)
|
||||
elseif self.head_yaw == "z" then
|
||||
final_rotation = vector.new(mob_pitch, 0, -mob_yaw)
|
||||
newr = vector.new(mob_pitch, 0, -mob_yaw)
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
if self.head_yaw == "y" then
|
||||
final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, ((mob_yaw-oldr.y)*.3)+oldr.y, 0)
|
||||
newr = vector.new((mob_pitch-oldr.x)*.3+oldr.x, (mob_yaw-oldr.y)*.3+oldr.y, 0)
|
||||
elseif self.head_yaw == "z" then
|
||||
final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, 0, -(((mob_yaw-oldr.y)*.3)+oldr.y)*3)
|
||||
newr = vector.new((mob_pitch-oldr.x)*.3+oldr.x, 0, ((mob_yaw-oldr.y)*.3+oldr.y)*-3)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif not self._locked_object and math.abs(oldr.y) > 3 and math.abs(oldr.x) < 3 then
|
||||
final_rotation = vector.multiply(oldr, 0.9)
|
||||
else
|
||||
--final_rotation = vector.new(0,0,0)
|
||||
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)
|
||||
end
|
||||
|
||||
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horizontal_head_height), final_rotation)
|
||||
-- 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 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 } })
|
||||
else -- minetest < 5.9
|
||||
-- old API uses degrees not radians
|
||||
self.object:set_bone_position(self.head_swivel, newp, vector.apply(newr, math.deg))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function mob_class:set_animation_speed()
|
||||
local v = self.object:get_velocity()
|
||||
if v then
|
||||
local v = self:get_velocity()
|
||||
if v > 0 then
|
||||
if self.frame_speed_multiplier then
|
||||
local v2 = math.abs(v.x)+math.abs(v.z)*.833
|
||||
if not self.animation.walk_speed then
|
||||
self.animation.walk_speed = 25
|
||||
end
|
||||
if math.abs(v.x)+math.abs(v.z) > 0.5 then
|
||||
self.object:set_animation_frame_speed((v2/math.max(1,self.run_velocity))*self.animation.walk_speed*self.frame_speed_multiplier)
|
||||
self.animation.walk_speed = self.animation.walk_speed or 25 -- TODO: move to initialization
|
||||
if v > 0.5 then
|
||||
self.object:set_animation_frame_speed((v/math.max(1,self.run_velocity))*self.animation.walk_speed*self.frame_speed_multiplier)
|
||||
else
|
||||
self.object:set_animation_frame_speed(25)
|
||||
end
|
||||
end
|
||||
--set_speed
|
||||
if validate_vector(self.acc) then
|
||||
self.object:add_velocity(self.acc)
|
||||
end
|
||||
--if validate_vector(self.acc) then
|
||||
-- self.object:add_velocity(self.acc)
|
||||
--end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,6 +6,40 @@ local modname = minetest.get_current_modname()
|
|||
local path = minetest.get_modpath(modname)
|
||||
local S = minetest.get_translator(modname)
|
||||
mcl_mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt"
|
||||
mcl_mobs.see_through_opaque = minetest.settings:get_bool("mobs_see_through_opaque", false)
|
||||
|
||||
-- used by the libaries below.
|
||||
-- get node but use fallback for nil or unknown
|
||||
local function node_ok(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
|
||||
|
||||
local function line_of_sight(origin, target, see_through_opaque, liquids)
|
||||
local raycast = minetest.raycast(origin, target, false, liquids or false)
|
||||
for hitpoint in raycast do
|
||||
if hitpoint.type == "node" then
|
||||
local node = minetest.get_node(minetest.get_pointed_thing_position(hitpoint))
|
||||
if node.name ~= "air" then
|
||||
local nodef = minetest.registered_nodes[node.name]
|
||||
if nodef and nodef.walkable and not (see_through_opaque and not nodef.groups.opaque) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
--TODO type object could block vision, for example chests
|
||||
end
|
||||
return true
|
||||
end
|
||||
mcl_mobs.line_of_sight = line_of_sight
|
||||
|
||||
mcl_mobs.NODE_IGNORE = { name = "ignore", groups = {} } -- fallback for unknown nodes
|
||||
|
||||
--api and helpers
|
||||
-- effects: sounds and particles mostly
|
||||
dofile(path .. "/effects.lua")
|
||||
|
@ -19,10 +53,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 +70,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 +137,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
|
||||
|
@ -131,20 +148,25 @@ function mcl_mobs.register_mob(name, def)
|
|||
end
|
||||
end
|
||||
|
||||
if type(def.fly_in) == "string" then
|
||||
def.fly_in = { def.fly_in }
|
||||
end
|
||||
|
||||
local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25}
|
||||
-- Workaround for <https://github.com/minetest/minetest/issues/5966>:
|
||||
-- Increase upper Y limit to avoid mobs glitching through solid nodes.
|
||||
-- Removed now, as this was supposedly fixed in 5.3.0?
|
||||
-- FIXME: Remove workaround if it's no longer needed.
|
||||
if collisionbox[5] < 0.79 then
|
||||
collisionbox[5] = 0.79
|
||||
end
|
||||
--if collisionbox[5] < 0.79 then
|
||||
-- collisionbox[5] = 0.79
|
||||
--end
|
||||
local final_def = {
|
||||
use_texture_alpha = def.use_texture_alpha,
|
||||
head_swivel = def.head_swivel or nil, -- bool to activate this function
|
||||
head_yaw_offset = def.head_yaw_offset or 0, -- for wonkey model bones
|
||||
head_yaw_offset = math.rad(def.head_yaw_offset or 0), -- for wonkey model bones
|
||||
head_pitch_multiplier = def.head_pitch_multiplier or 1, --for inverted pitch
|
||||
bone_eye_height = def.bone_eye_height or 1.4, -- head bone offset
|
||||
head_eye_height = def.head_eye_height or def.bone_eye_height or 0, -- how hight aproximatly the mobs head is fromm the ground to tell the mob how high to look up at the player
|
||||
head_eye_height = def.head_eye_height or def.bone_eye_height or 0, -- how high aproximately the mobs head is from the ground to tell the mob how high to look up at the player
|
||||
curiosity = def.curiosity or 1, -- how often mob will look at player on idle
|
||||
head_yaw = def.head_yaw or "y", -- axis to rotate head on
|
||||
horizontal_head_height = def.horizontal_head_height or 0,
|
||||
|
@ -163,7 +185,7 @@ function mcl_mobs.register_mob(name, def)
|
|||
spawn_small_alternative = def.spawn_small_alternative,
|
||||
do_custom = def.do_custom,
|
||||
detach_child = def.detach_child,
|
||||
jump_height = def.jump_height or 4, -- was 6
|
||||
jump_height = def.jump_height or 1,
|
||||
rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2
|
||||
lifetimer = def.lifetimer or 57.73,
|
||||
hp_min = scale_difficulty(def.hp_min, 5, 1),
|
||||
|
@ -229,7 +251,6 @@ function mcl_mobs.register_mob(name, def)
|
|||
health = 0,
|
||||
frame_speed_multiplier = 1,
|
||||
reach = def.reach or 3,
|
||||
htimer = 0,
|
||||
texture_list = def.textures,
|
||||
child_texture = def.child_texture,
|
||||
docile_by_day = def.docile_by_day or false,
|
||||
|
@ -383,11 +404,8 @@ end
|
|||
|
||||
-- register arrow for shoot attack
|
||||
function mcl_mobs.register_arrow(name, def)
|
||||
|
||||
if not name or not def then return end -- errorcheck
|
||||
|
||||
minetest.register_entity(name, {
|
||||
|
||||
physical = false,
|
||||
visual = def.visual,
|
||||
visual_size = def.visual_size,
|
||||
|
@ -399,7 +417,6 @@ function mcl_mobs.register_arrow(name, def)
|
|||
hit_object = def.hit_object,
|
||||
homing = def.homing,
|
||||
drop = def.drop or false, -- drops arrow as registered item when true
|
||||
collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows
|
||||
timer = 0,
|
||||
switch = 0,
|
||||
_lifetime = def._lifetime or 7,
|
||||
|
@ -411,31 +428,21 @@ function mcl_mobs.register_arrow(name, def)
|
|||
self._puncher = puncher
|
||||
end,
|
||||
collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0},
|
||||
automatic_face_movement_dir = def.rotate
|
||||
and (def.rotate - (math.pi / 180)) or false,
|
||||
automatic_face_movement_dir = def.rotate and (def.rotate - (math.pi / 180)) or false,
|
||||
|
||||
on_activate = def.on_activate,
|
||||
|
||||
on_step = def.on_step or function(self, dtime)
|
||||
|
||||
self.timer = self.timer + dtime
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
if self.switch == 0
|
||||
or self.timer > self._lifetime
|
||||
or not within_limits(pos, 0) then
|
||||
if self.switch == 0 or self.timer > self._lifetime or not within_limits(pos, 0) then
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove();
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- does arrow have a tail (fireball)
|
||||
if def.tail
|
||||
and def.tail == 1
|
||||
and def.tail_texture then
|
||||
|
||||
if def.tail == 1 and def.tail_texture then
|
||||
minetest.add_particle({
|
||||
pos = pos,
|
||||
velocity = {x = 0, y = 0, z = 0},
|
||||
|
@ -449,24 +456,17 @@ function mcl_mobs.register_arrow(name, def)
|
|||
end
|
||||
|
||||
if self.hit_node then
|
||||
|
||||
local node = node_ok(pos).name
|
||||
|
||||
if minetest.registered_nodes[node].walkable then
|
||||
|
||||
self.hit_node(self, pos, node)
|
||||
|
||||
if self.drop == true then
|
||||
|
||||
pos.y = pos.y + 1
|
||||
|
||||
self.lastpos = (self.lastpos or pos)
|
||||
|
||||
minetest.add_item(self.lastpos, self.object:get_luaentity().name)
|
||||
end
|
||||
|
||||
self.object:remove();
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
@ -483,12 +483,8 @@ function mcl_mobs.register_arrow(name, def)
|
|||
end
|
||||
|
||||
if self.hit_player or self.hit_mob or self.hit_object then
|
||||
|
||||
for _,object in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do
|
||||
|
||||
if self.hit_player
|
||||
and object:is_player() then
|
||||
|
||||
if self.hit_player and object:is_player() then
|
||||
self.hit_player(self, object)
|
||||
self.object:remove();
|
||||
return
|
||||
|
@ -529,30 +525,20 @@ end
|
|||
-- * spawn_egg=1: Spawn egg (generic mob, no metadata)
|
||||
-- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
|
||||
function mcl_mobs.register_egg(mob_id, desc, background_color, overlay_color, addegg, no_creative)
|
||||
|
||||
local grp = {spawn_egg = 1}
|
||||
|
||||
-- do NOT add this egg to creative inventory (e.g. dungeon master)
|
||||
if no_creative == true then
|
||||
grp.not_in_creative_inventory = 1
|
||||
end
|
||||
local grp = { spawn_egg = 1 }
|
||||
if no_creative == true then grp.not_in_creative_inventory = 1 end
|
||||
|
||||
local invimg = "(spawn_egg.png^[multiply:" .. background_color ..")^(spawn_egg_overlay.png^[multiply:" .. overlay_color .. ")"
|
||||
if old_spawn_icons then
|
||||
local mobname = mob_id:gsub("mobs_mc:","")
|
||||
local fn = "mobs_mc_spawn_icon_"..mobname..".png"
|
||||
if mcl_util.file_exists(minetest.get_modpath("mobs_mc").."/textures/"..fn) then
|
||||
invimg = fn
|
||||
end
|
||||
local fn = "mobs_mc_spawn_icon_" .. mob_id:gsub("mobs_mc:","") .. ".png"
|
||||
if mcl_util.file_exists(minetest.get_modpath("mobs_mc").."/textures/"..fn) then invimg = fn end
|
||||
end
|
||||
if addegg == 1 then
|
||||
invimg = "mobs_chicken_egg.png^(" .. invimg ..
|
||||
"^[mask:mobs_chicken_egg_overlay.png)"
|
||||
invimg = "mobs_chicken_egg.png^(" .. invimg .. "^[mask:mobs_chicken_egg_overlay.png)"
|
||||
end
|
||||
|
||||
-- register old stackable mob egg
|
||||
minetest.register_craftitem(mob_id, {
|
||||
|
||||
description = desc,
|
||||
inventory_image = invimg,
|
||||
groups = grp,
|
||||
|
@ -589,9 +575,6 @@ function mcl_mobs.register_egg(mob_id, desc, background_color, overlay_color, ad
|
|||
local dim = mcl_worlds.pos_to_dimension(placer:get_pos())
|
||||
local mob_light_lvl = {mcl_mobs:mob_light_lvl(itemstack:get_name(),dim)}
|
||||
|
||||
--minetest.log("min light: " .. mob_light_lvl[1])
|
||||
--minetest.log("max light: " .. mob_light_lvl[2])
|
||||
|
||||
-- Handle egg conversion
|
||||
local convert_to = (minetest.registered_entities[mob_name] or {})._convert_to
|
||||
if convert_to then mob_name = convert_to end
|
||||
|
@ -603,9 +586,7 @@ function mcl_mobs.register_egg(mob_id, desc, background_color, overlay_color, ad
|
|||
return itemstack
|
||||
end
|
||||
|
||||
if not minetest.registered_entities[mob_name] then
|
||||
return itemstack
|
||||
end
|
||||
if not minetest.registered_entities[mob_name] then return itemstack end
|
||||
|
||||
if minetest.settings:get_bool("only_peaceful_mobs", false)
|
||||
and minetest.registered_entities[mob_name].type == "monster" then
|
||||
|
@ -615,7 +596,7 @@ function mcl_mobs.register_egg(mob_id, desc, background_color, overlay_color, ad
|
|||
|
||||
pos.y = pos.y - 1
|
||||
local mob = mcl_mobs.spawn(pos, mob_name)
|
||||
if not object then
|
||||
if not mob then
|
||||
pos.y = pos.y + 1
|
||||
mob = mcl_mobs.spawn(pos, mob_name)
|
||||
if not mob then return end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
|
||||
local minetest, mcl_mobs = minetest, mcl_mobs
|
||||
local mob_class = mcl_mobs.mob_class
|
||||
--- Item and armor management
|
||||
|
||||
|
|
|
@ -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
|
||||
velo.y = velo.y + math.sqrt(entity.jump_height * 20)
|
||||
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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -72,18 +72,24 @@ local axolotl = {
|
|||
fly = true,
|
||||
fly_in = { "mcl_core:water_source", "mclx_core:river_water_source" },
|
||||
breathes_in_water = true,
|
||||
jump = true,
|
||||
jump = false, -- would get them out of the water too often
|
||||
damage = 2,
|
||||
reach = 2,
|
||||
attack_type = "dogfight",
|
||||
attack_animals = true,
|
||||
specific_attack = {
|
||||
"extra_mobs_cod",
|
||||
"extra_mobs_glow_squid",
|
||||
"extra_mobs_salmon",
|
||||
"extra_mobs_tropical_fish",
|
||||
"mobs_mc_squid"
|
||||
},
|
||||
"mobs_mc:cod",
|
||||
"mobs_mc:glow_squid",
|
||||
"mobs_mc:salmon",
|
||||
"mobs_mc:tropical_fish",
|
||||
"mobs_mc:squid",
|
||||
"mobs_mc:zombie", -- todo: only drowned?
|
||||
"mobs_mc:baby_zombie",
|
||||
"mobs_mc:husk",
|
||||
"mobs_mc:baby_husk",
|
||||
"mobs_mc:guardian_elder",
|
||||
"mobs_mc:guardian",
|
||||
},
|
||||
runaway = true,
|
||||
}
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ mcl_mobs.register_mob("mobs_mc:blaze", {
|
|||
shoot_offset = 1.0,
|
||||
passive = false,
|
||||
jump = true,
|
||||
jump_height = 4,
|
||||
jump_height = 1,
|
||||
fly = true,
|
||||
makes_footstep_sound = false,
|
||||
fear_height = 0,
|
||||
|
|
|
@ -22,7 +22,7 @@ mcl_mobs.register_mob("mobs_mc:chicken", {
|
|||
floats = 1,
|
||||
head_swivel = "head.control",
|
||||
bone_eye_height = 4,
|
||||
head_eye_height = 1.5,
|
||||
head_eye_height = .5,
|
||||
horizontal_head_height = -.3,
|
||||
curiosity = 10,
|
||||
head_yaw="z",
|
||||
|
|
|
@ -82,7 +82,7 @@ local cod = {
|
|||
do_custom = function(self)
|
||||
--[[ this is supposed to make them jump out the water but doesn't appear to work very well
|
||||
self.object:set_bone_position("body", vector.new(0,1,0), vector.new(degrees(dir_to_pitch(self.object:get_velocity())) * -1 + 90,0,0))
|
||||
if minetest.get_item_group(self.standing_in, "water") ~= 0 then
|
||||
if self.standing_in.groups.water then
|
||||
if self.object:get_velocity().y < 5 then
|
||||
self.object:add_velocity({ x = 0 , y = math.random()*.014-.007, z = 0 })
|
||||
end
|
||||
|
|
|
@ -84,7 +84,7 @@ local dolphin = {
|
|||
--[[ this is supposed to make them jump out the water but doesn't appear to work very well
|
||||
do_custom = function(self,dtime)
|
||||
self.object:set_bone_position("body", vector.new(0,1,0), vector.new(degrees(dir_to_pitch(self.object:get_velocity())) * -1 + 90,0,0))
|
||||
if minetest.get_item_group(self.standing_in, "water") ~= 0 then
|
||||
if self.standing_in.groups.water then
|
||||
if self.object:get_velocity().y < 5 then
|
||||
self.object:add_velocity({ x = 0 , y = math.random()*.014-.007, z = 0 })
|
||||
end
|
||||
|
|
|
@ -77,7 +77,7 @@ mcl_mobs.register_mob("mobs_mc:enderdragon", {
|
|||
damage = 10,
|
||||
knock_back = false,
|
||||
jump = true,
|
||||
jump_height = 14,
|
||||
jump_height = 10,
|
||||
fly = true,
|
||||
makes_footstep_sound = false,
|
||||
dogshoot_switch = 1,
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
local S = minetest.get_translator("mobs_mc")
|
||||
|
||||
-- TODO: sounds
|
||||
-- TODO: carry one item, spawn with item
|
||||
-- TODO: add sleeping behavior
|
||||
-- TODO: snow color depending on biome not randomly
|
||||
-- TODO: pouncing - jump to attack behavior
|
||||
-- TODO: use totem of undying when carried
|
||||
|
||||
-- Fox
|
||||
local fox = {
|
||||
description = S("Fox"),
|
||||
type = "animal",
|
||||
spawn_class = "passive",
|
||||
can_despawn = true,
|
||||
hp_min = 10,
|
||||
hp_max = 10,
|
||||
xp_min = 1,
|
||||
xp_max = 3,
|
||||
passive = false,
|
||||
group_attack = false,
|
||||
spawn_in_group = 4,
|
||||
collisionbox = { -0.3, 0, -0.3, 0.3, 0.7, 0.3 },
|
||||
visual = "mesh",
|
||||
mesh = "mobs_mc_fox.b3d",
|
||||
textures = {
|
||||
{ "mobs_mc_fox.png", "mobs_mc_fox_sleep.png" },
|
||||
{ "mobs_mc_snow_fox.png", "mobs_mc_snow_fox_sleep.png" }
|
||||
},
|
||||
makes_footstep_sound = true,
|
||||
head_swivel = "Bone.001",
|
||||
bone_eye_height = 0.5,
|
||||
head_eye_height = 0.1,
|
||||
horizontal_head_height = 0,
|
||||
curiosity = 5,
|
||||
head_yaw = "z",
|
||||
sounds = { }, -- FIXME
|
||||
pathfinding = 1,
|
||||
floats = 1,
|
||||
view_range = 16,
|
||||
walk_chance = 50,
|
||||
walk_velocity = 2,
|
||||
run_velocity = 3,
|
||||
damage = 2,
|
||||
reach = 1,
|
||||
attack_type = "dogfight",
|
||||
fear_height = 5,
|
||||
-- drops = { }, -- TODO: only what they are carrying
|
||||
follow = { "mcl_farming:sweet_berry" }, -- TODO: and glow berries, taming
|
||||
animation = {
|
||||
stand_start = 1, stand_end = 20, stand_speed = 20,
|
||||
walk_start = 120, walk_end = 160, walk_speed = 80,
|
||||
run_start = 160, run_end = 199, run_speed = 80,
|
||||
punch_start = 80, punch_end = 105, punch_speed = 80,
|
||||
sit_start = 30 , sit_end = 50,
|
||||
sleep_start = 55, sleep_end = 75,
|
||||
--wiggle_start = 170, wiggle_end = 230,
|
||||
--die_start = 0, die_end = 0, die_speed = 0,--die_loop = 0,
|
||||
},
|
||||
jump = true,
|
||||
jump_height = 2,
|
||||
attacks_monsters = true,
|
||||
attack_animals = true,
|
||||
specific_attack = {
|
||||
"mobs_mc:chicken", "mobs_mc:rabbit",
|
||||
"mobs_mc:cod", "mobs_mc:salmon", "mobs_mc:tropical_fish"
|
||||
-- TODO: baby turtles, monsters?
|
||||
},
|
||||
runaway_from = {
|
||||
-- they are too cute for this: "player",
|
||||
"mobs_mc:wolf",
|
||||
-- TODO: and polar bear
|
||||
},
|
||||
}
|
||||
|
||||
mcl_mobs.register_mob("mobs_mc:fox", fox)
|
||||
-- Spawn
|
||||
mcl_mobs:spawn_specific(
|
||||
"mobs_mc:fox",
|
||||
"overworld",
|
||||
"ground",
|
||||
{
|
||||
"Taiga",
|
||||
"Taiga_beach",
|
||||
"MegaTaiga",
|
||||
"MegaSpruceTaiga",
|
||||
"ColdTaiga",
|
||||
"ColdTaiga_beach",
|
||||
},
|
||||
0,
|
||||
minetest.LIGHT_MAX+1,
|
||||
30,
|
||||
80,
|
||||
7,
|
||||
mobs_mc.water_level+3,
|
||||
mcl_vars.mg_overworld_max)
|
||||
|
||||
|
||||
mcl_mobs.register_egg("mobs_mc:fox", "Fox", "#ba9f8b", "#9f5219", 0)
|
|
@ -52,6 +52,7 @@ mcl_mobs.register_mob("mobs_mc:ghast", {
|
|||
fall_damage = 0,
|
||||
view_range = 64,
|
||||
attack_type = "dogshoot",
|
||||
see_through_opaque = false,
|
||||
arrow = "mobs_mc:fireball",
|
||||
shoot_interval = 5,
|
||||
shoot_offset = -0.5,
|
||||
|
@ -59,7 +60,7 @@ mcl_mobs.register_mob("mobs_mc:ghast", {
|
|||
dogshoot_count_max =1,
|
||||
passive = false,
|
||||
jump = true,
|
||||
jump_height = 4,
|
||||
jump_height = 1,
|
||||
floats=1,
|
||||
fly = true,
|
||||
makes_footstep_sound = false,
|
||||
|
|
|
@ -158,7 +158,7 @@ local horse = {
|
|||
floats = 1,
|
||||
makes_footstep_sound = true,
|
||||
jump = true,
|
||||
jump_height = 5.75, -- can clear 2.5 blocks
|
||||
jump_height = 2.5, -- can clear 2.5 blocks
|
||||
drops = {
|
||||
{name = "mcl_mobitems:leather",
|
||||
chance = 1,
|
||||
|
@ -560,7 +560,7 @@ donkey.collisionbox = {
|
|||
horse.collisionbox[6] * d,
|
||||
}
|
||||
donkey.jump = true
|
||||
donkey.jump_height = 3.75 -- can clear 1 block height
|
||||
donkey.jump_height = 1 -- can clear 1 block height
|
||||
|
||||
|
||||
mcl_mobs.register_mob("mobs_mc:donkey", donkey)
|
||||
|
|
|
@ -149,6 +149,7 @@ dofile(path .. "/salmon.lua")
|
|||
dofile(path .. "/tropical_fish.lua")
|
||||
dofile(path .. "/dolphin.lua")
|
||||
|
||||
dofile(path .. "/fox.lua") -- Mesh and animation by https://codeberg.org/pixelzone texture https://github.com/NovaWostra/Pixel-Perfection-Chorus-Eddit
|
||||
|
||||
dofile(path .. "/glow_squid.lua")
|
||||
|
||||
|
|
Binary file not shown.
|
@ -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")
|
||||
|
|
|
@ -134,7 +134,7 @@ local slime_big = {
|
|||
walk_velocity = 1.9,
|
||||
run_velocity = 1.9,
|
||||
walk_chance = 0,
|
||||
jump_height = 5.2,
|
||||
jump_height = 1,
|
||||
fear_height = 0,
|
||||
spawn_small_alternative = "mobs_mc:slime_small",
|
||||
on_die = spawn_children_on_die("mobs_mc:slime_small", 1.0, 1.5),
|
||||
|
@ -156,7 +156,6 @@ slime_small.damage = 3
|
|||
slime_small.reach = 2.25
|
||||
slime_small.walk_velocity = 1.8
|
||||
slime_small.run_velocity = 1.8
|
||||
slime_small.jump_height = 4.3
|
||||
slime_small.spawn_small_alternative = "mobs_mc:slime_tiny"
|
||||
slime_small.on_die = spawn_children_on_die("mobs_mc:slime_tiny", 0.6, 1.0)
|
||||
mcl_mobs.register_mob("mobs_mc:slime_small", slime_small)
|
||||
|
@ -181,7 +180,6 @@ slime_tiny.drops = {
|
|||
}
|
||||
slime_tiny.walk_velocity = 1.7
|
||||
slime_tiny.run_velocity = 1.7
|
||||
slime_tiny.jump_height = 3
|
||||
slime_tiny.spawn_small_alternative = nil
|
||||
slime_tiny.on_die = nil
|
||||
|
||||
|
@ -363,7 +361,7 @@ local magma_cube_big = {
|
|||
attack_type = "dogfight",
|
||||
passive = false,
|
||||
jump = true,
|
||||
jump_height = 8,
|
||||
jump_height = 4,
|
||||
walk_chance = 0,
|
||||
fear_height = 0,
|
||||
spawn_small_alternative = "mobs_mc:magma_cube_small",
|
||||
|
@ -386,7 +384,7 @@ magma_cube_small.damage = 3
|
|||
magma_cube_small.reach = 2.1
|
||||
magma_cube_small.walk_velocity = .8
|
||||
magma_cube_small.run_velocity = 2.0
|
||||
magma_cube_small.jump_height = 6
|
||||
magma_cube_small.jump_height = 2
|
||||
magma_cube_small.damage = 4
|
||||
magma_cube_small.reach = 2.75
|
||||
magma_cube_small.armor = 66
|
||||
|
@ -407,7 +405,7 @@ magma_cube_tiny.collisionbox = {-0.2505, -0.01, -0.2505, 0.2505, 0.50, 0.2505, r
|
|||
magma_cube_tiny.visual_size = {x=3.125, y=3.125}
|
||||
magma_cube_tiny.walk_velocity = 1.02
|
||||
magma_cube_tiny.run_velocity = 1.02
|
||||
magma_cube_tiny.jump_height = 4
|
||||
magma_cube_tiny.jump_height = 1
|
||||
magma_cube_tiny.damage = 3
|
||||
magma_cube_tiny.reach = 2
|
||||
magma_cube_tiny.armor = 50
|
||||
|
|
|
@ -86,7 +86,7 @@ local spider = {
|
|||
walk_velocity = 1.3,
|
||||
run_velocity = 2.4,
|
||||
jump = true,
|
||||
jump_height = 4,
|
||||
jump_height = 1,
|
||||
view_range = 16,
|
||||
floats = 1,
|
||||
drops = {
|
||||
|
|
|
@ -2,17 +2,35 @@
|
|||
|
||||
local S = minetest.get_translator("mobs_mc")
|
||||
|
||||
--###################
|
||||
--################### STALKER
|
||||
--###################
|
||||
-- foliage and grass palettes, loaded from mcl_maps
|
||||
local palettes = {}
|
||||
|
||||
local function load_json_file(name)
|
||||
local file = assert(io.open(name, "r"))
|
||||
local data = minetest.parse_json(file:read("*all"))
|
||||
file:close()
|
||||
return data
|
||||
end
|
||||
local mapmodpath = minetest.get_modpath("mcl_maps")
|
||||
if mapmodpath then
|
||||
for k,v in pairs(load_json_file(mapmodpath .. "/palettes_grass.json")) do
|
||||
palettes[k] = v
|
||||
end
|
||||
for k,v in pairs(load_json_file(mapmodpath .. "/palettes_foliage.json")) do
|
||||
palettes[k] = v
|
||||
end
|
||||
for k,v in pairs(load_json_file(mapmodpath .. "/palettes_water.json")) do
|
||||
palettes[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_texture(self)
|
||||
local on_name = self.standing_on
|
||||
local function get_texture(self, prev)
|
||||
local standing_on = self.standing_on
|
||||
local texture
|
||||
local texture_suff = ""
|
||||
if on_name and on_name ~= "air" then
|
||||
local tiles = minetest.registered_nodes[on_name].tiles
|
||||
if standing_on and (standing_on.walkable or standing_on.groups.liquid) then
|
||||
local tiles = standing_on.tiles
|
||||
if tiles then
|
||||
local tile = tiles[1]
|
||||
local color
|
||||
|
@ -25,22 +43,49 @@ local function get_texture(self)
|
|||
texture = tile
|
||||
end
|
||||
if not color then
|
||||
color = minetest.colorspec_to_colorstring(minetest.registered_nodes[on_name].color)
|
||||
color = minetest.colorspec_to_colorstring(standing_on.color)
|
||||
end
|
||||
-- handle param2
|
||||
if standing_on.palette and self.standing_on_node then
|
||||
local param2
|
||||
if standing_on.paramtype2 == "color" then
|
||||
param2 = self.standing_on_node.param2
|
||||
elseif standing_on.paramtype2 == "colorfacedir" then
|
||||
param2 = math.floor(self.standing_on_node.param2 / 8)
|
||||
elseif standing_on.paramtype2 == "colorwallmounted" then
|
||||
param2 = math.floor(self.standing_on_node.param2 / 32)
|
||||
elseif standing_on.paramtype2 == "color4dir" then
|
||||
param2 = math.floor(self.standing_on_node.param2 / 64)
|
||||
elseif standing_on.paramtype2 == "colordegrotate" then
|
||||
param2 = math.floor(self.standing_on_node.param2 / 8)
|
||||
end
|
||||
local palette = palettes[standing_on.palette]
|
||||
local oldcol = color
|
||||
if param2 and palette then
|
||||
local c = palette[param2 + 1]
|
||||
if c then color = minetest.rgba(c[1], c[2], c[3], c[4]) end
|
||||
end
|
||||
end
|
||||
if color then
|
||||
texture_suff = "^[multiply:" .. color .. "^[hsl:0:0:20"
|
||||
texture_suff = "^[multiply:" .. color .. "^[contrast:20:10" --"^[hsl:0:0:20"
|
||||
end
|
||||
end
|
||||
end
|
||||
if not texture or texture == "" then
|
||||
-- try to keep last texture when, e.g., falling
|
||||
if prev and (not (not self.attack)) == (string.find(prev, "vl_mobs_starker_overlay_angry.png") ~= nil) then
|
||||
return prev
|
||||
end
|
||||
texture = "vl_stalker_default.png"
|
||||
end
|
||||
texture = texture:gsub("([\\^:\\[])","\\%1") -- escape texture modifiers
|
||||
texture = "([combine:16x24:0,0=(" .. texture .. "):0,16=(" .. texture ..")".. texture_suff
|
||||
if self.attack then
|
||||
texture = texture .. ")^vl_mobs_stalker_overlay_angry.png"
|
||||
if texture_suff then texture = texture .. texture_suff end
|
||||
else
|
||||
texture = texture .. ")^vl_mobs_stalker_overlay.png"
|
||||
texture = texture:gsub("([\\^:\\[])", "\\%1") -- escape texture modifiers
|
||||
texture = "(vl_stalker_default.png^[combine:16x24:0,0=(" .. texture .. "):0,16=(" .. texture .. ")" .. texture_suff .. ")"
|
||||
end
|
||||
if self.attack then
|
||||
texture = texture .. "^vl_mobs_stalker_overlay_angry.png"
|
||||
else
|
||||
texture = texture .. "^vl_mobs_stalker_overlay.png"
|
||||
end
|
||||
return texture
|
||||
end
|
||||
|
@ -98,6 +143,7 @@ mcl_mobs.register_mob("mobs_mc:stalker", {
|
|||
explosion_damage_radius = 3.5,
|
||||
explosiontimer_reset_radius = 3,
|
||||
reach = 3,
|
||||
see_through_opaque = false,
|
||||
explosion_timer = 1.5,
|
||||
allow_fuse_reset = true,
|
||||
stop_to_explode = true,
|
||||
|
@ -132,7 +178,7 @@ mcl_mobs.register_mob("mobs_mc:stalker", {
|
|||
self:boom(mcl_util.get_object_center(self.object), self.explosion_strength)
|
||||
end
|
||||
end
|
||||
local new_texture = get_texture(self)
|
||||
local new_texture = get_texture(self, self._stalker_texture)
|
||||
if self._stalker_texture ~= new_texture then
|
||||
self.object:set_properties({textures={new_texture, "mobs_mc_empty.png"}})
|
||||
self._stalker_texture = new_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
|
||||
|
|
|
@ -13,14 +13,6 @@ local anti_troll = minetest.settings:get_bool("wither_anti_troll_measures", fals
|
|||
local WITHER_INIT_BOOM = 7
|
||||
local WITHER_MELEE_COOLDOWN = 3
|
||||
|
||||
local function atan(x)
|
||||
if not x or x ~= x then
|
||||
return 0
|
||||
else
|
||||
return math.atan(x)
|
||||
end
|
||||
end
|
||||
|
||||
--###################
|
||||
--################### WITHER
|
||||
--###################
|
||||
|
@ -96,7 +88,7 @@ mcl_mobs.register_mob("mobs_mc:wither", {
|
|||
distance = 60,
|
||||
},
|
||||
jump = true,
|
||||
jump_height = 10,
|
||||
jump_height = 5,
|
||||
fly = true,
|
||||
makes_footstep_sound = false,
|
||||
dogshoot_switch = 1, -- unused
|
||||
|
@ -125,6 +117,7 @@ mcl_mobs.register_mob("mobs_mc:wither", {
|
|||
},
|
||||
harmed_by_heal = true,
|
||||
is_boss = true,
|
||||
see_through_opaque = false,
|
||||
extra_hostile = true,
|
||||
attack_exception = function(p)
|
||||
local ent = p:get_luaentity()
|
||||
|
@ -260,7 +253,7 @@ mcl_mobs.register_mob("mobs_mc:wither", {
|
|||
z = p.z - s.z
|
||||
}
|
||||
|
||||
local yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
|
||||
local yaw = (math.atan2(vec.z, vec.x) +math.pi/ 2) - self.rotate
|
||||
if p.x > s.x then yaw = yaw +math.pi end
|
||||
yaw = self:set_yaw( yaw, 0, dtime)
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ local zombie = {
|
|||
fear_height = 4,
|
||||
pathfinding = 1,
|
||||
jump = true,
|
||||
jump_height = 4,
|
||||
jump_height = 1,
|
||||
group_attack = { "mobs_mc:zombie", "mobs_mc:baby_zombie", "mobs_mc:husk", "mobs_mc:baby_husk" },
|
||||
drops = drops_zombie,
|
||||
animation = {
|
||||
|
|
|
@ -157,6 +157,7 @@ function mcl_weather.rain.clear()
|
|||
mcl_weather.rain.remove_sound(player)
|
||||
mcl_weather.rain.remove_player(player)
|
||||
mcl_weather.remove_spawners_player(player)
|
||||
player:set_clouds({color="#FFF0EF"})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local NIGHT_VISION_RATIO = 0.45
|
||||
local DEBUG = false
|
||||
|
||||
-- Settings
|
||||
local minimum_update_interval = { 250e3 }
|
||||
|
@ -190,8 +191,8 @@ end
|
|||
|
||||
function skycolor_utils.convert_to_rgb(minval, maxval, current_val, colors)
|
||||
-- Clamp current_val to valid range
|
||||
current_val = math.min(minval, current_val)
|
||||
current_val = math.max(maxval, current_val)
|
||||
current_val = math.max(minval, current_val)
|
||||
current_val = math.min(maxval, current_val)
|
||||
|
||||
-- Rescale current_val from a number between minval and maxval to a number between 1 and #colors
|
||||
local scaled_value = (current_val - minval) / (maxval - minval) * (#colors - 1) + 1.0
|
||||
|
@ -199,7 +200,7 @@ function skycolor_utils.convert_to_rgb(minval, maxval, current_val, colors)
|
|||
-- Get the first color's values
|
||||
local index1 = math.floor(scaled_value)
|
||||
local color1 = colors[index1]
|
||||
local frac1 = scaled_value - index1
|
||||
local frac1 = 1.0 - (scaled_value - index1)
|
||||
|
||||
-- Get the second color's values
|
||||
local index2 = math.min(index1 + 1, #colors) -- clamp to maximum color index (will occur if index1 == #colors)
|
||||
|
@ -207,11 +208,32 @@ function skycolor_utils.convert_to_rgb(minval, maxval, current_val, colors)
|
|||
local color2 = colors[index2]
|
||||
|
||||
-- Interpolate between color1 and color2
|
||||
return {
|
||||
local res = {
|
||||
r = math.floor(frac1 * color1.r + frac2 * color2.r),
|
||||
g = math.floor(frac1 * color1.g + frac2 * color2.g),
|
||||
b = math.floor(frac1 * color1.b + frac2 * color2.b),
|
||||
}
|
||||
|
||||
if DEBUG then
|
||||
minetest.log(dump({
|
||||
minval = minval,
|
||||
maxval = maxval,
|
||||
current_val = current_val,
|
||||
colors = colors,
|
||||
res = res,
|
||||
scaled_value = scaled_value,
|
||||
|
||||
frac1 = frac1,
|
||||
index1 = index1,
|
||||
color1 = color1,
|
||||
|
||||
frac2 = frac2,
|
||||
index2 = index2,
|
||||
color2 = color2,
|
||||
}))
|
||||
end
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
-- Simple getter. Either returns user given players list or get all connected players if none provided
|
||||
|
|
|
@ -40,18 +40,21 @@ function dimension_handlers.overworld(player, sky_data)
|
|||
end
|
||||
|
||||
-- Use overworld defaults
|
||||
local day_color = mcl_weather.skycolor.get_sky_layer_color(0.15)
|
||||
local day_color = mcl_weather.skycolor.get_sky_layer_color(0.5)
|
||||
local dawn_color = mcl_weather.skycolor.get_sky_layer_color(0.27)
|
||||
local night_color = mcl_weather.skycolor.get_sky_layer_color(0.1)
|
||||
sky_data.sky = {
|
||||
type = "regular",
|
||||
sky_color = {
|
||||
day_sky = day_color,
|
||||
day_horizon = day_color,
|
||||
dawn_sky = dawn_color,
|
||||
dawn_horizon = dawn_color,
|
||||
night_sky = night_color,
|
||||
night_horizon = night_color,
|
||||
day_sky = day_color or "#7BA4FF",
|
||||
day_horizon = day_color or "#C0D8FF",
|
||||
dawn_sky = dawn_color or "7BA4FF",
|
||||
dawn_horizon = dawn_color or "#C0D8FF",
|
||||
night_sky = night_color or "000000",
|
||||
night_horizon = night_color or "4A6790",
|
||||
fog_sun_tint = "#ff5f33",
|
||||
fog_moon_tint = nil,
|
||||
fog_tint_type = "custom",
|
||||
},
|
||||
clouds = true,
|
||||
}
|
||||
|
@ -75,18 +78,15 @@ function dimension_handlers.overworld(player, sky_data)
|
|||
local day_color = mcl_weather.skycolor.get_sky_layer_color(0.5)
|
||||
local dawn_color = mcl_weather.skycolor.get_sky_layer_color(0.75)
|
||||
local night_color = mcl_weather.skycolor.get_sky_layer_color(0)
|
||||
sky_data.sky = {
|
||||
type = "regular",
|
||||
sky_color = {
|
||||
day_sky = day_color,
|
||||
day_horizon = day_color,
|
||||
dawn_sky = dawn_color,
|
||||
dawn_horizon = dawn_color,
|
||||
night_sky = night_color,
|
||||
night_horizon = night_color,
|
||||
},
|
||||
clouds = true,
|
||||
}
|
||||
table.update(sky_data.sky.sky_color,{
|
||||
day_sky = day_color or "#7BA4FF",
|
||||
day_horizon = day_color or "#C0D8FF",
|
||||
dawn_sky = dawn_color or "7BA4FF",
|
||||
dawn_horizon = dawn_color or "#C0D8FF",
|
||||
night_sky = night_color or "000000",
|
||||
night_horizon = night_color or "4A6790",
|
||||
fog_tint_type = "default",
|
||||
})
|
||||
sky_data.sun = {visible = false, sunrise_visible = false}
|
||||
sky_data.moon = {visible = false}
|
||||
sky_data.stars = {visible = false}
|
||||
|
@ -164,7 +164,8 @@ function dimension_handlers.nether(player, sky_data)
|
|||
end
|
||||
|
||||
function dimension_handlers.void(player, sky_data)
|
||||
sky_data.sky = { type = "plain",
|
||||
sky_data.sky = {
|
||||
type = "plain",
|
||||
base_color = "#000000",
|
||||
clouds = false,
|
||||
}
|
||||
|
|
|
@ -75,13 +75,15 @@ function mcl_weather.has_snow(pos)
|
|||
end
|
||||
|
||||
function mcl_weather.snow.set_sky_box()
|
||||
mcl_weather.skycolor.add_layer(
|
||||
"weather-pack-snow-sky",
|
||||
{{r=0, g=0, b=0},
|
||||
{r=85, g=86, b=86},
|
||||
{r=135, g=135, b=135},
|
||||
{r=85, g=86, b=86},
|
||||
{r=0, g=0, b=0}})
|
||||
if mcl_weather.skycolor.current_layer_name() ~= "weather-pack-snow-sky" then
|
||||
mcl_weather.skycolor.add_layer(
|
||||
"weather-pack-snow-sky",
|
||||
{{r=0, g=0, b=0},
|
||||
{r=85, g=86, b=86},
|
||||
{r=135, g=135, b=135},
|
||||
{r=85, g=86, b=86},
|
||||
{r=0, g=0, b=0}})
|
||||
end
|
||||
mcl_weather.skycolor.active = true
|
||||
for _, player in pairs(get_connected_players()) do
|
||||
player:set_clouds({color="#ADADADE8"})
|
||||
|
|
|
@ -23,13 +23,15 @@ minetest.register_globalstep(function(dtime)
|
|||
mcl_weather.rain.make_weather()
|
||||
|
||||
if mcl_weather.thunder.init_done == false then
|
||||
mcl_weather.skycolor.add_layer("weather-pack-thunder-sky", {
|
||||
{r=0, g=0, b=0},
|
||||
{r=40, g=40, b=40},
|
||||
{r=85, g=86, b=86},
|
||||
{r=40, g=40, b=40},
|
||||
{r=0, g=0, b=0},
|
||||
})
|
||||
if mcl_weather.skycolor.current_layer_name() ~= "weather-pack-thunder-sky" then
|
||||
mcl_weather.skycolor.add_layer("weather-pack-thunder-sky", {
|
||||
{r=0, g=0, b=0},
|
||||
{r=40, g=40, b=40},
|
||||
{r=85, g=86, b=86},
|
||||
{r=40, g=40, b=40},
|
||||
{r=0, g=0, b=0},
|
||||
})
|
||||
end
|
||||
mcl_weather.skycolor.active = true
|
||||
for _, player in pairs(get_connected_players()) do
|
||||
player:set_clouds({color="#3D3D3FE8"})
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
--- Copyright (C) 2022 - 2023, Michieal. See License.txt
|
||||
|
||||
-- CONSTS
|
||||
local DOUBLE_DROP_CHANCE = 8
|
||||
-- Used everywhere. Often this is just the name, but it makes sense to me as BAMBOO, because that's how I think of it...
|
||||
-- "BAMBOO" goes here.
|
||||
local BAMBOO = "mcl_bamboo:bamboo"
|
||||
local BAMBOO_ENDCAP_NAME = "mcl_bamboo:bamboo_endcap"
|
||||
local BAMBOO_PLANK = BAMBOO .. "_plank"
|
||||
|
@ -16,7 +13,7 @@ local BAMBOO_PLANK = BAMBOO .. "_plank"
|
|||
local modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator(modname)
|
||||
local node_sound = mcl_sounds.node_sound_wood_defaults()
|
||||
local pr = PseudoRandom((os.time() + 15766) * 12) -- switched from math.random() to PseudoRandom because the random wasn't very random.
|
||||
local pr = PseudoRandom((os.time() + 15766) * 12)
|
||||
|
||||
local on_rotate
|
||||
if minetest.get_modpath("screwdriver") then
|
||||
|
@ -31,33 +28,7 @@ local bamboo_def = {
|
|||
paramtype = "light",
|
||||
groups = {handy = 1, axey = 1, choppy = 1, dig_by_piston = 1, plant = 1, non_mycelium_plant = 1, flammable = 3},
|
||||
sounds = node_sound,
|
||||
|
||||
drop = {
|
||||
max_items = 1,
|
||||
-- From the API:
|
||||
-- max_items: Maximum number of item lists to drop.
|
||||
-- The entries in 'items' are processed in order. For each:
|
||||
-- Item filtering is applied, chance of drop is applied, if both are
|
||||
-- successful the entire item list is dropped.
|
||||
-- Entry processing continues until the number of dropped item lists
|
||||
-- equals 'max_items'.
|
||||
-- Therefore, entries should progress from low to high drop chance.
|
||||
items = {
|
||||
-- Examples:
|
||||
{
|
||||
-- 1 in DOUBLE_DROP_CHANCE chance of dropping.
|
||||
-- Default rarity is '1'.
|
||||
rarity = DOUBLE_DROP_CHANCE,
|
||||
items = {BAMBOO .. " 2"},
|
||||
},
|
||||
{
|
||||
-- 1 in 1 chance of dropping. (Note: this means that it will drop 100% of the time.)
|
||||
-- Default rarity is '1'.
|
||||
rarity = 1,
|
||||
items = {BAMBOO},
|
||||
},
|
||||
},
|
||||
},
|
||||
drop = BAMBOO,
|
||||
|
||||
inventory_image = "mcl_bamboo_bamboo_shoot.png",
|
||||
wield_image = "mcl_bamboo_bamboo_shoot.png",
|
||||
|
@ -86,7 +57,6 @@ local bamboo_def = {
|
|||
on_rotate = on_rotate,
|
||||
|
||||
on_place = function(itemstack, placer, pointed_thing)
|
||||
|
||||
if not pointed_thing then
|
||||
return itemstack
|
||||
end
|
||||
|
@ -241,9 +211,6 @@ local bamboo_def = {
|
|||
if node_above and ((bamboo_node and bamboo_node > 0) or node_above.name == BAMBOO_ENDCAP_NAME) then
|
||||
minetest.remove_node(new_pos)
|
||||
minetest.sound_play(node_sound.dug, sound_params, true)
|
||||
if pr:next(1, DOUBLE_DROP_CHANCE) == 1 then
|
||||
minetest.add_item(new_pos, istack)
|
||||
end
|
||||
minetest.add_item(new_pos, istack)
|
||||
end
|
||||
end,
|
||||
|
|
|
@ -9,8 +9,6 @@ local SIDE_SCAFFOLDING = false
|
|||
local SIDE_SCAFFOLD_NAME = "mcl_bamboo:scaffolding_horizontal"
|
||||
-- ---------------------------------------------------------------------------
|
||||
local SCAFFOLDING_NAME = "mcl_bamboo:scaffolding"
|
||||
-- Used everywhere. Often this is just the name, but it makes sense to me as BAMBOO, because that's how I think of it...
|
||||
-- "BAMBOO" goes here.
|
||||
local BAMBOO = "mcl_bamboo:bamboo"
|
||||
local BAMBOO_PLANK = BAMBOO .. "_plank"
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
-- LOCALS
|
||||
local modname = minetest.get_current_modname()
|
||||
-- Used everywhere. Often this is just the name, but it makes sense to me as BAMBOO, because that's how I think of it...
|
||||
-- "BAMBOO" goes here.
|
||||
local BAMBOO = "mcl_bamboo:bamboo"
|
||||
|
||||
mcl_bamboo = {}
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
--- These are all of the fuel recipes and all of the crafting recipes, consolidated into one place.
|
||||
--- Copyright (C) 2022 - 2023, Michieal. See License.txt
|
||||
|
||||
-- Used everywhere. Often this is just the name, but it makes sense to me as BAMBOO, because that's how I think of it...
|
||||
-- "BAMBOO" goes here.
|
||||
local BAMBOO = "mcl_bamboo:bamboo"
|
||||
local BAMBOO_PLANK = BAMBOO .. "_plank"
|
||||
-- Craftings
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 649 B |
Binary file not shown.
After Width: | Height: | Size: 649 B |
Binary file not shown.
After Width: | Height: | Size: 602 B |
Binary file not shown.
After Width: | Height: | Size: 600 B |
Loading…
Reference in New Issue