diff --git a/mods/ENTITIES/mcl_mobs/api.lua b/mods/ENTITIES/mcl_mobs/api.lua index dd3c84819..e7988f557 100644 --- a/mods/ENTITIES/mcl_mobs/api.lua +++ b/mods/ENTITIES/mcl_mobs/api.lua @@ -1,29 +1,24 @@ local mob_class = mcl_mobs.mob_class local mob_class_meta = {__index = mcl_mobs.mob_class} local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs --- API for Mobs Redo: VoxeLibre Edition local PATHFINDING = "gowp" local CRASH_WARN_FREQUENCY = 60 local LIFETIMER_DISTANCE = 47 +local MAPGEN_LIMIT = mcl_vars.mapgen_limit +local MAPGEN_MOB_LIMIT = MAPGEN_LIMIT - 90 +-- 30927 seems to be the edge of the world, so could be closer, but this is safer --- Localize local S = minetest.get_translator("mcl_mobs") -local DEVELOPMENT = minetest.settings:get_bool("mcl_development",false) - -- Invisibility mod check mcl_mobs.invis = {} local remove_far = true local mobs_debug = minetest.settings:get_bool("mobs_debug", false) -- Shows helpful debug info above each mob -local spawn_logging = minetest.settings:get_bool("mcl_logging_mobs_spawn", false) - -local MAPGEN_LIMIT = mcl_vars.mapgen_limit -local MAPGEN_MOB_LIMIT = MAPGEN_LIMIT - 90 --- 30927 seems to be the edge of the world, so could be closer, but this is safer - +local spawn_logging = minetest.settings:get_bool("mcl_logging_mobs_spawn", true) +local DEVELOPMENT = minetest.settings:get_bool("mcl_development", false) -- Peaceful mode message so players will know there are no monsters if minetest.settings:get_bool("only_peaceful_mobs", false) then @@ -36,10 +31,7 @@ end function mob_class:update_tag() --update nametag and/or the debug box local tag if mobs_debug then - local name = self.name - if self.nametag and self.nametag ~= "" then - name = self.nametag - end + local name = self.nametag ~= "" and self.nametag or self.name tag = "name = '"..tostring(name).."'\n".. "state = '"..tostring(self.state).."'\n".. "order = '"..tostring(self.order).."'\n".. @@ -56,9 +48,7 @@ function mob_class:update_tag() --update nametag and/or the debug box else tag = self.nametag end - self.object:set_properties({ - nametag = tag, - }) + self.object:set_properties({ nametag = tag }) end function mob_class:jock_to(mob, reletive_pos, rot) @@ -74,19 +64,15 @@ function mob_class:jock_to(mob, reletive_pos, rot) end function mob_class:get_staticdata() - for _,p in pairs(minetest.get_connected_players()) do self:remove_particlespawners(p:get_player_name()) end -- remove mob when out of range unless tamed - if remove_far - and self:despawn_allowed() - and self.lifetimer <= 20 then + if remove_far and self:despawn_allowed() and self.lifetimer <= 20 then if spawn_logging then minetest.log("action", "[mcl_mobs] Mob "..tostring(self.name).." despawns at "..minetest.pos_to_string(vector.round(self.object:get_pos())) .. " - out of range") end - return "remove"-- nil end @@ -95,17 +81,9 @@ function mob_class:get_staticdata() self.state = "stand" local tmp = {} - for tag, stat in pairs(self) do - local t = type(stat) - - if t ~= "function" - and t ~= "nil" - and t ~= "userdata" - and tag ~= "_cmi_components" then - tmp[tag] = self[tag] - end + if t ~= "function" and t ~= "nil" and t ~= "userdata" and tag ~= "_cmi_components" then tmp[tag] = self[tag] end end tmp._mcl_potions = self._mcl_potions @@ -120,10 +98,7 @@ function mob_class:get_staticdata() end local function valid_texture(self, def_textures) - if not self.base_texture then - return false - end - + if not self.base_texture then return false end if self.texture_selected then if #def_textures < self.texture_selected then self.texture_selected = nil @@ -148,32 +123,18 @@ function mob_class:mob_activate(staticdata, def, dtime) end local tmp = minetest.deserialize(staticdata) - if tmp then -- Patch incorrectly converted mobs - if tmp.base_mesh ~= minetest.registered_entities[self.name].mesh then - mcl_mobs.strip_staticdata(tmp) - end - - for _,stat in pairs(tmp) do - self[_] = stat - end + if tmp.base_mesh ~= minetest.registered_entities[self.name].mesh then mcl_mobs.strip_staticdata(tmp) end + for _, stat in pairs(tmp) do self[_] = stat end end --If textures in definition change, reload textures if not valid_texture(self, def.textures) then - -- compatiblity with old simple mobs textures - if type(def.textures[1]) == "string" then - def.textures = {def.textures} - end - - if not self.texture_selected then - local c = 1 - if #def.textures > c then c = #def.textures end - self.texture_selected = math.random(c) - end + if type(def.textures[1]) == "string" then def.textures = {def.textures} end + self.texture_selected = self.texture_selected or math.random(#def.textures) self.base_texture = def.textures[self.texture_selected] self.base_mesh = def.mesh self.base_size = self.visual_size @@ -181,9 +142,7 @@ function mob_class:mob_activate(staticdata, def, dtime) self.base_selbox = self.selectionbox end - if not self.base_selbox then - self.base_selbox = self.selectionbox or self.base_colbox - end + self.base_selbox = self.base_selbox or self.selectionbox or self.base_colbox local textures = self.base_texture local mesh = self.base_mesh @@ -191,26 +150,11 @@ function mob_class:mob_activate(staticdata, def, dtime) local colbox = self.base_colbox local selbox = self.base_selbox - if self.gotten == true - and def.gotten_texture then - textures = def.gotten_texture - end - - if self.gotten == true - and def.gotten_mesh then - mesh = def.gotten_mesh - end - - if self.child == true then - - vis_size = { - x = self.base_size.x * .5, - y = self.base_size.y * .5, - } - - if def.child_texture then - textures = def.child_texture[1] - end + if self.gotten and def.gotten_texture then textures = def.gotten_texture end + if self.gotten and def.gotten_mesh then mesh = def.gotten_mesh end + if self.child then + vis_size = { x = self.base_size.x * .5, y = self.base_size.y * .5 } + if def.child_texture then textures = def.child_texture[1] end colbox = { self.base_colbox[1] * .5, @@ -230,16 +174,12 @@ function mob_class:mob_activate(staticdata, def, dtime) } end - if self.health == 0 then - self.health = math.random (self.hp_min, self.hp_max) - end - if self.breath == nil then - self.breath = self.breath_max - end + if self.health == 0 then self.health = math.random(self.hp_min, self.hp_max) end + if self.breath == nil then self.breath = self.breath_max end self.path = {} self.path.way = {} -- path to follow, table of positions - self.path.lastpos = {x = 0, y = 0, z = 0} + self.path.lastpos = vector.zero() self.path.stuck = false self.path.following = false -- currently following path? self.path.stuck_timer = 0 -- if stuck for too long search for path @@ -276,42 +216,22 @@ function mob_class:mob_activate(staticdata, def, dtime) self.blinktimer = 0 self.blinkstatus = false - if not self.nametag then - self.nametag = def.nametag - end - if not self.custom_visual_size then - self.visual_size = nil - self.base_size = self.visual_size - if self.child then - self.visual_size = { - x = self.visual_size.x * 0.5, - y = self.visual_size.y * 0.5, - } - end - end + self.nametag = self.nametag or def.nametag self.object:set_properties(self) - self:set_yaw( (math.random(0, 360) - 180) / 180 * math.pi, 6) + self:set_yaw(math.random() * math.pi * 2, 6) self:update_tag() self._current_animation = nil - self:set_animation( "stand") + self:set_animation("stand") - - if self.riden_by_jock then --- Keep this function before self.on_spawn() is run. + if self.riden_by_jock then --- Keep this function before self:on_spawn() self.object:remove() return end + if self.on_spawn and not self.on_spawn_run and self:on_spawn() then self.on_spawn_run = true end - if self.on_spawn and not self.on_spawn_run then - if self.on_spawn(self) then - self.on_spawn_run = true - end - end - - if not self.wears_armor and self.armor_list then - self.armor_list = nil - end + if not self.wears_armor and self.armor_list then self.armor_list = nil end if not self._run_armor_init and self.wears_armor then self.armor_list={helmet="",chestplate="",boots="",leggings=""} @@ -319,15 +239,10 @@ function mob_class:mob_activate(staticdata, def, dtime) self._run_armor_init = true end - if not self._mcl_potions then - self._mcl_potions = {} - end + if not self._mcl_potions then self._mcl_potions = {} end mcl_potions._load_entity_effects(self) - - if def.after_activate then - def.after_activate(self, staticdata, def, dtime) - end + if def.after_activate then def.after_activate(self, staticdata, def, dtime) end end -- execute current state (stand, walk, run, attacks) @@ -346,9 +261,7 @@ function mob_class:do_states(dtime, player_in_active_range) if self.state == PATHFINDING then self:check_gowp(dtime) elseif self.state == "attack" then - if self:do_states_attack(dtime) then - return true - end + if self:do_states_attack(dtime) then return true end else if mcl_util.check_dtime_timer(self, dtime, "onstep_dostates", 1) then if self.state == "stand" then @@ -364,34 +277,28 @@ end function mob_class:outside_limits() local pos = self.object:get_pos() - if pos then - local posx = math.abs(pos.x) - local posy = math.abs(pos.y) - local posz = math.abs(pos.z) - if posx > MAPGEN_MOB_LIMIT or posy > MAPGEN_MOB_LIMIT or posz > MAPGEN_MOB_LIMIT then - --minetest.log("action", "Getting close to limits of worldgen: " .. minetest.pos_to_string(pos)) - if posx > MAPGEN_LIMIT or posy > MAPGEN_LIMIT or posz > MAPGEN_LIMIT then - minetest.log("action", "Warning mob past limits of worldgen: " .. minetest.pos_to_string(pos)) - else - if self.state ~= "stand" then - minetest.log("action", "Warning mob close to limits of worldgen: " .. minetest.pos_to_string(pos)) - self.state = "stand" - self:set_animation("stand") - self.object:set_acceleration(vector.zero()) - self.object:set_velocity(vector.zero()) - end + if not pos then return end + local posx, posy, posz = math.abs(pos.x), math.abs(pos.y), math.abs(pos.z) + if posx > MAPGEN_MOB_LIMIT or posy > MAPGEN_MOB_LIMIT or posz > MAPGEN_MOB_LIMIT then + --minetest.log("action", "Getting close to limits of worldgen: " .. minetest.pos_to_string(pos)) + if posx > MAPGEN_LIMIT or posy > MAPGEN_LIMIT or posz > MAPGEN_LIMIT then + minetest.log("action", "Warning mob past limits of worldgen: " .. minetest.pos_to_string(pos)) + else + if self.state ~= "stand" then + minetest.log("action", "Warning mob close to limits of worldgen: " .. minetest.pos_to_string(pos)) + self.state = "stand" + self:set_animation("stand") + self.object:set_acceleration(vector.zero()) + self.object:set_velocity(vector.zero()) end - return true end + return true end end - - local function on_step_work(self, dtime, moveresult) local pos = self.object:get_pos() if not pos then return end - if self:check_despawn(pos, dtime) then return true end if self:outside_limits() then return end @@ -403,29 +310,20 @@ local function on_step_work(self, dtime, moveresult) end if self:falling(pos, moveresult) then return end - if self:step_damage (dtime, pos) then return end + if self:step_damage(dtime, pos) then return end if self.state == "die" then return end -- End: Death/damage processing local player_in_active_range = self:player_in_active_range() self:check_suspend(player_in_active_range) - self:check_water_flow() - - if not self._jumping_cliff then - self._can_jump_cliff = self:can_jump_cliff() - else - self._can_jump_cliff = false - end - + self._can_jump_cliff = not self._jumping_cliff and self:can_jump_cliff() self:flop() - self:check_smooth_rotation(dtime) if player_in_active_range then self:set_animation_speed() -- set animation speed relative to velocity - self:check_head_swivel(dtime) if mcl_util.check_dtime_timer(self, dtime, "onstep_engage", 0.2) then @@ -442,91 +340,68 @@ local function on_step_work(self, dtime, moveresult) end if mcl_util.check_dtime_timer(self, dtime, "onstep_occassional", 1) then - if player_in_active_range then self:check_item_pickup() self:set_armor_texture() self:step_opinion_sound(dtime) end - self:check_breeding() end self:check_aggro(dtime) - self:check_particlespawners(dtime) - if self.do_custom and self.do_custom(self, dtime) == false then return end - if self:do_states(dtime, player_in_active_range) then return end - if mobs_debug then self:update_tag() end - - if not self.object:get_luaentity() then - return false - end + if not self.object:get_luaentity() then return false end end local last_crash_warn_time = 0 -local function log_error (stack_trace, info, info2) +local function log_error(stack_trace, info, info2) minetest.log("action", "--- Bug report start (please provide a few lines before this also for context) ---") minetest.log("action", "Error: " .. stack_trace) minetest.log("action", "Bug info: " .. info) - if info2 then - minetest.log("action", "Bug info additional: " .. info2) - end + if info2 then minetest.log("action", "Bug info additional: " .. info2) end minetest.log("action", "--- Bug report end ---") end local function warn_user_error () local current_time = os.time() local time_since_warning = current_time - last_crash_warn_time - --minetest.log("previous_crash_time: " .. current_time) --minetest.log("last_crash_time: " .. last_crash_warn_time) --minetest.log("time_since_warning: " .. time_since_warning) - if time_since_warning > CRASH_WARN_FREQUENCY then last_crash_warn_time = current_time minetest.log("A game crashing bug was prevented. Please provide debug.log information to VoxeLibre dev team for investigation. (Search for: --- Bug report start)") end end -local on_step_error_handler = function () - warn_user_error () +local on_step_error_handler = function() + warn_user_error() local info = debug.getinfo(1, "SnlufL") log_error(tostring(debug.traceback()), dump(info)) end - - -- main mob function function mob_class:on_step(dtime, moveresult) - if not DEVELOPMENT then - -- Removed as bundled Lua (5.1 doesn't support xpcall) - --local status, retVal = xpcall(on_step_work, on_step_error_handler, self, dtime) - local status, retVal = pcall(on_step_work, self, dtime, moveresult) - if status then - return retVal - else - warn_user_error () - local pos = self.object:get_pos() - if pos then - local node = minetest.get_node(pos) - if node and node.name == "ignore" then - minetest.log("warning", "Pos is ignored: " .. dump(pos)) - end - end - log_error (dump(retVal), dump(pos), dump(self)) - end - else - return on_step_work (self, dtime, moveresult) + -- allow crash in development mode + if DEVELOPMENT then return on_step_work(self, dtime, moveresult) end + -- Removed as bundled Lua (5.1 doesn't support xpcall) + --local status, retVal = xpcall(on_step_work, on_step_error_handler, self, dtime) + local status, retVal = pcall(on_step_work, self, dtime, moveresult) + if status then return retVal end + warn_user_error() + local pos = self.object:get_pos() + if pos then + local node = minetest.get_node(pos) + if node and node.name == "ignore" then minetest.log("warning", "Pos is ignored: " .. dump(pos)) end end + log_error(dump(retVal), dump(pos), dump(self)) end local timer = 0 - local function update_lifetimer(dtime) timer = timer + dtime if timer < 1 then return end @@ -547,7 +422,6 @@ minetest.register_globalstep(function(dtime) update_lifetimer(dtime) end) - minetest.register_chatcommand("clearmobs", { privs = { maphack = true }, params = "[all|monster|passive| [|nametagged|tamed]]", @@ -560,11 +434,7 @@ minetest.register_chatcommand("clearmobs", { S("Default usage. Clearing hostile mobs. For more options please type: /help clearmobs")) end local mob, unsafe = param:match("^([%w]+)[ ]?([%w%d]*)$") - - local all = false - local nametagged = false - local tamed = false - + local all, nametagged, tamed = false, false, false local mob_name, mob_type, range -- Param 1 resolve @@ -578,12 +448,7 @@ minetest.register_chatcommand("clearmobs", { end --minetest.log ("mob: [" .. mob .. "]") else - --minetest.log("No valid first param") - if default then - --minetest.log("Use default") - mob_type = "monster" - end - --return + if default then mob_type = "monster" end end -- Param 2 resolve @@ -600,7 +465,6 @@ minetest.register_chatcommand("clearmobs", { end local p = minetest.get_player_by_name(player) - for _,o in pairs(minetest.luaentities) do if o and o.is_mob then local mob_match = false @@ -609,7 +473,6 @@ minetest.register_chatcommand("clearmobs", { --minetest.log("Match - All mobs specified") mob_match = true elseif mob_type then - --minetest.log("Match - o.type: ".. tostring(o.type)) --minetest.log("mob_type: ".. tostring(mob_type)) if mob_type == "monster" and o.type == mob_type then @@ -621,7 +484,6 @@ minetest.register_chatcommand("clearmobs", { else --minetest.log("No match for type.") end - elseif mob_name and (o.name == mob_name or string.find(o.name, mob_name)) then --minetest.log("Match - mob_name = ".. tostring(o.name)) mob_match = true @@ -632,35 +494,16 @@ minetest.register_chatcommand("clearmobs", { end if mob_match then - local in_range = true - if (not range or range <= 0 ) then - in_range = true - else - if ( vector.distance(p:get_pos(),o.object:get_pos()) <= range ) then - in_range = true - else - --minetest.log("Out of range") - in_range = false - end - end - - --minetest.log("o.nametag: ".. tostring(o.nametag)) - + local in_range = (not range or range <= 0) or vector.distance(p:get_pos(), o.object:get_pos()) <= range if nametagged then - if o.nametag then - --minetest.log("Namedtagged and it has a name tag. Kill it") - o.object:remove() - end + if o.nametag then o.object:remove() end elseif tamed then - if o.tamed then - --minetest.log("Tamed. Kill it") - o.object:remove() - end + if o.tamed then o.object:remove() end elseif in_range and (not o.nametag or o.nametag == "") and not o.tamed then - --minetest.log("No nametag or tamed. Kill it") o.object:remove() end end end end -end}) + end +}) diff --git a/mods/ENTITIES/mcl_mobs/breeding.lua b/mods/ENTITIES/mcl_mobs/breeding.lua index 426353b5a..73d956b9b 100644 --- a/mods/ENTITIES/mcl_mobs/breeding.lua +++ b/mods/ENTITIES/mcl_mobs/breeding.lua @@ -63,7 +63,7 @@ function mob_class:feed_tame(clicker, feed_count, breed, tame, notake) -- make children grow quicker - if not consume_food and self.child == true then + if not consume_food and self.child then consume_food = true -- deduct 10% of the time to adulthood self.hornytimer = self.hornytimer + ((CHILD_GROW_TIME - self.hornytimer) * 0.1) @@ -158,7 +158,7 @@ function mob_class:check_breeding() --mcl_log("In breed function") -- child takes a long time before growing into adult - if self.child == true then + if self.child then -- When a child, hornytimer is used to count age until adulthood self.hornytimer = self.hornytimer + 1 diff --git a/mods/ENTITIES/mcl_mobs/combat.lua b/mods/ENTITIES/mcl_mobs/combat.lua index 608209ee2..4d92360c2 100644 --- a/mods/ENTITIES/mcl_mobs/combat.lua +++ b/mods/ENTITIES/mcl_mobs/combat.lua @@ -11,39 +11,30 @@ local stuck_path_timeout = 10 -- how long will mob follow path before giving up local enable_pathfinding = true local TIME_TO_FORGET_TARGET = 15 - -local atann = math.atan -local function atan(x) - if not x or x ~= x then - return 0 - else - return atann(x) - end -end - +local PI = math.pi +local HALFPI = PI * 0.5 +local random = math.random +local min = math.min +local floor = math.floor +local ceil = math.ceil +local abs = math.abs +local cos = math.cos +local sin = math.sin +local atan2 = math.atan2 +local vector_offset = vector.offset +local vector_new = vector.new +local vector_copy = vector.copy +local vector_distance = vector.distance -- check if daytime and also if mob is docile during daylight hours function mob_class:day_docile() - if self.docile_by_day == false then - return false - elseif self.docile_by_day == true - and self.time_of_day > 0.2 - and self.time_of_day < 0.8 then - return true - end + return self.docile_by_day == true and self.time_of_day > 0.2 and self.time_of_day < 0.8 end -- get this mob to attack the object function mob_class:do_attack(object) - - if self.state == "attack" or self.state == "die" then - return - end - - - if object:is_player() and not minetest.settings:get_bool("enable_damage") then - return - end + if self.state == "attack" or self.state == "die" then return end + if object:is_player() and not minetest.settings:get_bool("enable_damage") then return end self.attack = object self.state = "attack" @@ -55,21 +46,18 @@ function mob_class:do_attack(object) end -- blast damage to entities nearby -local function entity_physics(pos,radius) - +local function entity_physics(pos, radius) radius = radius * 2 local objs = minetest.get_objects_inside_radius(pos, radius) local obj_pos, dist - for n = 1, #objs do - obj_pos = objs[n]:get_pos() - dist = vector.distance(pos, obj_pos) + dist = vector_distance(pos, obj_pos) if dist < 1 then dist = 1 end - local damage = math.floor((4 / dist) * radius) + local damage = floor((4 / dist) * radius) local ent = objs[n]:get_luaentity() -- punches work on entities AND players @@ -87,75 +75,57 @@ local height_switcher = false -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3 function mob_class:smart_mobs(s, p, dist, dtime) - local s1 = self.path.lastpos - local target_pos = self.attack:get_pos() -- is it becoming stuck? - if math.abs(s1.x - s.x) + math.abs(s1.z - s.z) < .5 then + if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then self.path.stuck_timer = self.path.stuck_timer + dtime else self.path.stuck_timer = 0 end - self.path.lastpos = {x = s.x, y = s.y, z = s.z} + self.path.lastpos = vector_copy(s) local use_pathfind = false - local has_lineofsight = minetest.line_of_sight( - {x = s.x, y = (s.y) + .5, z = s.z}, - {x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2) + local has_lineofsight = minetest.line_of_sight(vector_offset(s, 0, .5, 0), vector_offset(target_pos, 0, 1.5, 0), .2) -- im stuck, search for path if not has_lineofsight then - if los_switcher == true then use_pathfind = true los_switcher = false end -- cannot see target! else if los_switcher == false then - los_switcher = true use_pathfind = false - minetest.after(1, function(self) - if not self.object:get_luaentity() then - return - end + if not self.object:get_luaentity() then return end if has_lineofsight then self.path.following = false end end, self) end -- can see target! end if (self.path.stuck_timer > stuck_timeout and not self.path.following) then - use_pathfind = true self.path.stuck_timer = 0 - minetest.after(1, function(self) - if not self.object:get_luaentity() then - return - end + if not self.object:get_luaentity() then return end if has_lineofsight then self.path.following = false end end, self) end if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then - use_pathfind = true self.path.stuck_timer = 0 - minetest.after(1, function(self) - if not self.object:get_luaentity() then - return - end + if not self.object:get_luaentity() then return end if has_lineofsight then self.path.following = false end end, self) end - if math.abs(vector.subtract(s,target_pos).y) > self.stepheight then - + if abs(s.y - target_pos.y) > self.stepheight then if height_switcher then use_pathfind = true height_switcher = false @@ -174,28 +144,21 @@ function mob_class:smart_mobs(s, p, dist, dtime) -- round position to center of node to avoid stuck in walls -- also adjust height for player models! - s.x = math.floor(s.x + 0.5) - s.z = math.floor(s.z + 0.5) + s.x, s.z = floor(s.x + 0.5), floor(s.z + 0.5) - local ssight, sground = minetest.line_of_sight(s, { - x = s.x, y = s.y - 4, z = s.z}, 1) + local ssight, sground = minetest.line_of_sight(s, vector_offset(s, 0, -4, 0), 1) -- determine node above ground - if not ssight then - s.y = sground.y + 1 - end + if not ssight then s.y = sground.y + 1 end local p1 = self.attack:get_pos() - - p1.x = math.floor(p1.x + 0.5) - p1.y = math.floor(p1.y + 0.5) - p1.z = math.floor(p1.z + 0.5) + p1 = vector_new(floor(p1.x + 0.5), floor(p1.y + 0.5), floor(p1.z + 0.5)) local dropheight = 12 if self.fear_height ~= 0 then dropheight = self.fear_height end local jumpheight = 0 if self.jump and self.jump_height >= 4 then - jumpheight = math.min(math.ceil(self.jump_height / 4), 4) + jumpheight = min(ceil(self.jump_height * 0.25), 4) elseif self.stepheight > 0.5 then jumpheight = 1 end @@ -206,34 +169,27 @@ function mob_class:smart_mobs(s, p, dist, dtime) -- no path found, try something else if not self.path.way then - self.path.following = false -- lets make way by digging/building if not accessible if self.pathfinding == 2 and mobs_griefing then - -- is player higher than mob? if s.y < p1.y then - -- build upwards if not minetest.is_protected(s, "") then - local ndef1 = minetest.registered_nodes[self.standing_in] - if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then - minetest.set_node(s, {name = mcl_mobs.fallback_node}) end end - local sheight = math.ceil(self.collisionbox[5]) + 1 + local sheight = ceil(self.collisionbox[5]) + 1 -- assume mob is 2 blocks high so it digs above its head s.y = s.y + sheight -- remove one block above to make room to jump if not minetest.is_protected(s, "") then - local node1 = node_ok(s, "air").name local ndef1 = minetest.registered_nodes[node1] @@ -243,32 +199,21 @@ function mob_class:smart_mobs(s, p, dist, dtime) and not ndef1.groups.level and not ndef1.groups.unbreakable and not ndef1.groups.liquid then - minetest.set_node(s, {name = "air"}) minetest.add_item(s, ItemStack(node1)) - end end s.y = s.y - sheight - self.object:set_pos({x = s.x, y = s.y + 2, z = s.z}) - + self.object:set_pos(vector_offset(s, 0, 2, 0)) else -- dig 2 blocks to make door toward player direction - - local yaw1 = self.object:get_yaw() + math.pi / 2 - local p1 = { - x = s.x + math.cos(yaw1), - y = s.y, - z = s.z + math.sin(yaw1) - } + local yaw1 = self.object:get_yaw() + HALFPI + local p1 = vector_offset(s, cos(yaw1), 0, sin(yaw1)) if not minetest.is_protected(p1, "") then - local node1 = node_ok(p1, "air").name local ndef1 = minetest.registered_nodes[node1] - - if node1 ~= "air" - and node1 ~= "ignore" + if node1 ~= "air" and node1 ~= "ignore" and ndef1 and not ndef1.groups.level and not ndef1.groups.unbreakable @@ -282,8 +227,7 @@ function mob_class:smart_mobs(s, p, dist, dtime) node1 = node_ok(p1, "air").name ndef1 = minetest.registered_nodes[node1] - if node1 ~= "air" - and node1 ~= "ignore" + if node1 ~= "air" and node1 ~= "ignore" and ndef1 and not ndef1.groups.level and not ndef1.groups.unbreakable @@ -317,28 +261,19 @@ end -- specific attacks local specific_attack = function(list, what) - -- no list so attack default (player, animals etc.) - if list == nil then - return true - end + if list == nil then return true end -- found entity on list to attack? for no = 1, #list do - - if list[no] == what then - return true - end + if list[no] == what then return true end end - return false end -- find someone to attack function mob_class:monster_attack() - if not damage_enabled or self.passive ~= false or self.state == "attack" or self:day_docile() then - return - end + if not damage_enabled or self.passive ~= false or self.state == "attack" or self:day_docile() then return end local s = self.object:get_pos() local p, sp, dist @@ -392,7 +327,7 @@ function mob_class:monster_attack() p = player:get_pos() sp = s - dist = vector.distance(p, s) + dist = vector_distance(p, s) -- aim higher to make looking up hills more realistic p.y = p.y + 1 @@ -414,7 +349,7 @@ function mob_class:monster_attack() end end if not min_player and #blacklist_attack > 0 then - min_player=blacklist_attack[math.random(#blacklist_attack)] + min_player=blacklist_attack[random(#blacklist_attack)] end -- attack player if min_player then @@ -425,7 +360,6 @@ end -- npc, find closest monster to attack function mob_class:npc_attack() - if self.type ~= "npc" or not self.attacks_monsters or self.state == "attack" then @@ -444,7 +378,7 @@ function mob_class:npc_attack() p = obj.object:get_pos() sp = s - local dist = vector.distance(p, s) + local dist = vector_distance(p, s) -- aim higher to make looking up hills more realistic p.y = p.y + 1 @@ -466,19 +400,13 @@ end -- dogshoot attack switch and counter function function mob_class:dogswitch(dtime) - -- switch mode not activated - if not self.dogshoot_switch - or not dtime then - return 0 - end + if not self.dogshoot_switch or not dtime then return 0 end self.dogshoot_count = self.dogshoot_count + dtime - if (self.dogshoot_switch == 1 - and self.dogshoot_count > self.dogshoot_count_max) - or (self.dogshoot_switch == 2 - and self.dogshoot_count > self.dogshoot_count2_max) then + if (self.dogshoot_switch == 1 and self.dogshoot_count > self.dogshoot_count_max) + or (self.dogshoot_switch == 2 and self.dogshoot_count > self.dogshoot_count2_max) then self.dogshoot_count = 0 @@ -525,13 +453,9 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) if is_player then -- is mob out of reach? - if vector.distance(mob_pos, player_pos) > 3 then - return - end + if vector_distance(mob_pos, player_pos) > 3 then return end -- is mob protected? - if self.protected and minetest.is_protected(mob_pos, hitter:get_player_name()) then - return - end + if self.protected and minetest.is_protected(mob_pos, hitter:get_player_name()) then return end mcl_potions.update_haste_and_fatigue(hitter) end @@ -540,13 +464,10 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) local time_diff = time_now - self.invul_timestamp -- check for invulnerability time in microseconds (0.5 second) - if time_diff <= 500000 and time_diff >= 0 then - return - end + if time_diff <= 500000 and time_diff >= 0 then return end -- custom punch function if self.do_punch then - -- when false skip going any further if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then return @@ -562,15 +483,11 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) local time_now = minetest.get_us_time() if is_player then - if minetest.is_creative_enabled(hitter:get_player_name()) then - self.health = 0 - end - + if minetest.is_creative_enabled(hitter:get_player_name()) then self.health = 0 end -- set/update 'drop xp' timestamp if hitted by player self.xp_timestamp = time_now end - -- punch interval local weapon = hitter:get_wielded_item() local punch_interval = 1.4 @@ -591,18 +508,10 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) end - for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do - + for group,_ in pairs((tool_capabilities.damage_groups or {}) ) do tmp = tflp / (tool_capabilities.full_punch_interval or 1.4) - - if tmp < 0 then - tmp = 0.0 - elseif tmp > 1 then - tmp = 1.0 - end - - damage = damage + (tool_capabilities.damage_groups[group] or 0) - * tmp * ((armor[group] or 0) / 100.0) + tmp = tmp < 0 and 0 or (tmp > 1 and 1 or tmp) + damage = damage + (tool_capabilities.damage_groups[group] or 0) * tmp * ((armor[group] or 0) / 100.0) end -- strength and weakness effects @@ -621,9 +530,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) -- check for tool immunity or special damage for n = 1, #self.immune_to do - if self.immune_to[n][1] == weapon:get_name() then - damage = self.immune_to[n][2] or 0 break end @@ -631,7 +538,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) -- healing if damage <= -1 then - self.health = self.health - math.floor(damage) + self.health = self.health - floor(damage) return end @@ -651,7 +558,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) local weapon = hitter:get_wielded_item(player) local def = weapon:get_definition() if def.tool_capabilities and def.tool_capabilities.punch_attack_uses then - local wear = math.floor(65535/tool_capabilities.punch_attack_uses) + local wear = floor(65535/tool_capabilities.punch_attack_uses) weapon:add_wear(wear) tt.reload_itemstack_description(weapon) -- update tooltip hitter:set_wielded_item(weapon) @@ -662,14 +569,12 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) local die = false - if damage >= 0 then -- only play hit sound and show blood effects if damage is 1 or over; lower to 0.1 to ensure armor works appropriately. if damage >= 0.1 then -- weapon sounds if weapon:get_definition().sounds ~= nil then - - local s = math.random(0, #weapon:get_definition().sounds) + local s = random(0, #weapon:get_definition().sounds) minetest.sound_play(weapon:get_definition().sounds[s], { object = self.object, --hitter, @@ -696,27 +601,20 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) end end -- knock back effect (only on full punch) - if self.knock_back - and tflp >= punch_interval then + if self.knock_back and tflp >= punch_interval then -- direction error check - dir = dir or {x = 0, y = 0, z = 0} + dir = dir or vector_zero() local v = self.object:get_velocity() if not v then return end - local r = 1.4 - math.min(punch_interval, 1.4) - local kb = r * (math.abs(v.x)+math.abs(v.z)) + local r = 1.4 - min(punch_interval, 1.4) + local kb = r * (abs(v.x)+abs(v.z)) local up = 2.625 - if die==true then - kb=kb*1.25 - end + if die then kb = kb * 1.25 end -- if already in air then dont go up anymore when hit - if math.abs(v.y) > 0.1 - or self.fly then - up = 0 - end - + if abs(v.y) > 0.1 or self.fly then up = 0 end -- check if tool already has specific knockback value if tool_capabilities.damage_groups["knockback"] then @@ -725,21 +623,17 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) kb = kb * 1.25 end - - local luaentity - if hitter then - luaentity = hitter:get_luaentity() - end + local luaentity = hitter and hitter:get_luaentity() if hitter and is_player then local wielditem = hitter:get_wielded_item() kb = kb + 9 * mcl_enchanting.get_enchantment(wielditem, "knockback") -- add player velocity to mob knockback local hv = hitter:get_velocity() local dir_dot = (hv.x * dir.x) + (hv.z * dir.z) - local player_mag = math.sqrt((hv.x * hv.x) + (hv.z * hv.z)) - local mob_mag = math.sqrt((v.x * v.x) + (v.z * v.z)) + local player_mag = ((hv.x * hv.x) + (hv.z * hv.z))^0.5 + local mob_mag = ((v.x * v.x) + (v.z * v.z))^0.5 if dir_dot > 0 and mob_mag <= player_mag * 0.625 then - kb = kb + ((math.abs(hv.x) + math.abs(hv.z)) * r) + kb = kb + (abs(hv.x) + abs(hv.z)) * r end elseif luaentity and luaentity._knockback and die == false then kb = kb + luaentity._knockback @@ -747,12 +641,12 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) kb = kb + luaentity._knockback * 0.25 end self._kb_turn = true - self._turn_to=self.object:get_yaw()-1.57 + self:turn_by(HALFPI, .1) -- knockback turn self.frame_speed_multiplier=2.3 if self.animation.run_end then - self:set_animation( "run") + self:set_animation("run") elseif self.animation.walk_end then - self:set_animation( "walk") + self:set_animation("walk") end minetest.after(0.2, function() if self and self.object then @@ -760,11 +654,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) self._kb_turn = false end end) - self.object:add_velocity({ - x = dir.x * kb, - y = up*2, - z = dir.z * kb - }) + self.object:add_velocity(vector_new(dir.x * kb, up*2, dir.z * kb )) self.pause_timer = 0.25 end @@ -772,12 +662,15 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) -- if skittish then run away if hitter and is_player and hitter:get_pos() and not die and self.runaway == true and self.state ~= "flop" then - - local yaw = self:set_yaw( minetest.dir_to_yaw(vector.direction(hitter:get_pos(), self.object:get_pos()))) + local hp, sp = hitter:get_pos(), self.object:get_pos() + self:turn_in_direction(sp.x - hp.x, sp.z - hp.z, 1) minetest.after(0.2,function() - if self and self.object and self.object:get_pos() and hitter and is_player and hitter:get_pos() then - yaw = self:set_yaw( minetest.dir_to_yaw(vector.direction(hitter:get_pos(), self.object:get_pos()))) - self:set_velocity( self.run_velocity) + if self and self.object and hitter and is_player then + local hp, sp = hitter:get_pos(), self.object:get_pos() + if hp and sp then + self:turn_in_direction(sp.x - hp.x, sp.z - hp.z, 1) + self:set_velocity(self.run_velocity) + end end end) self.state = "runaway" @@ -808,7 +701,6 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) local obj = nil for n = 1, #objs do - obj = objs[n]:get_luaentity() if obj then @@ -840,11 +732,7 @@ end function mob_class:check_aggro(dtime) if not self._aggro or not self.attack then return end - - if not self._check_aggro_timer then - self._check_aggro_timer = 0 - end - + if not self._check_aggro_timer then self._check_aggro_timer = 0 end if self._check_aggro_timer > 5 then self._check_aggro_timer = 0 @@ -852,7 +740,7 @@ function mob_class:check_aggro(dtime) -- TODO consider removing this in favour of what is done in do_states_attack -- Attack is dropped in do_states_attack if out of range, so won't even trigger here -- I do not think this code does anything. Are mobs still loaded in at 128? - if not self.attack:get_pos() or vector.distance(self.attack:get_pos(),self.object:get_pos()) > 128 then + if not self.attack:get_pos() or vector_distance(self.attack:get_pos(),self.object:get_pos()) > 128 then self._aggro = nil self.attack = nil self.state = "stand" @@ -878,17 +766,14 @@ local function clear_aggro(self) self.path.way = nil end -function mob_class:do_states_attack (dtime) +function mob_class:do_states_attack(dtime) self.timer = self.timer + dtime - if self.timer > 100 then - self.timer = 1 - end + if self.timer > 100 then self.timer = 1 end local s = self.object:get_pos() if not s then return end local p = self.attack:get_pos() or s - local yaw = self.object:get_yaw() or 0 -- stop attacking if player invisible or out of range @@ -920,20 +805,15 @@ function mob_class:do_states_attack (dtime) end -- calculate distance from mob and enemy - local dist = vector.distance(p, s) + local dist = vector_distance(p, s) if self.attack_type == "explode" then - if target_line_of_sight then - local vec = { x = p.x - s.x, z = p.z - s.z } - yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate - if p.x > s.x then yaw = yaw +math.pi end - yaw = self:set_yaw( yaw, 0, dtime) + self:turn_in_direction(p.x - s.x, p.z - s.z, 1) end local node_break_radius = self.explosion_radius or 1 - local entity_damage_radius = self.explosion_damage_radius - or (node_break_radius * 2) + local entity_damage_radius = self.explosion_damage_radius or (node_break_radius * 2) -- start timer when in reach and line of sight if not self.v_start and dist <= self.reach and target_line_of_sight then @@ -960,9 +840,9 @@ function mob_class:do_states_attack (dtime) end if self.animation and self.animation.run_start then - self:set_animation( "run") + self:set_animation("run") else - self:set_animation( "walk") + self:set_animation("walk") end if self.v_start then @@ -1005,92 +885,50 @@ function mob_class:do_states_attack (dtime) or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2) and (dist >= self.avoid_distance or not self.shooter_avoid_enemy) or (self.attack_type == "dogshoot" and dist <= self.reach and self:dogswitch() == 0) then - if self.fly - and dist > self.reach then - - local p1 = s - local me_y = math.floor(p1.y) - local p2 = p - local p_y = math.floor(p2.y + 1) + if self.fly and dist > self.reach then + local p1, p2 = s, p + local me_y, p_y = floor(p1.y), floor(p2.y + 1) local v = self.object:get_velocity() if self:flight_check( s) then - if me_y < p_y then - - self.object:set_velocity({ - x = v.x, - y = 1 * self.walk_velocity, - z = v.z - }) - + self.object:set_velocity(vector_new(v.x, 1 * self.walk_velocity, v.z)) elseif me_y > p_y then - - self.object:set_velocity({ - x = v.x, - y = -1 * self.walk_velocity, - z = v.z - }) + self.object:set_velocity(vector_new(v.x, -1 * self.walk_velocity, v.z)) end else if me_y < p_y then - - self.object:set_velocity({ - x = v.x, - y = 0.01, - z = v.z - }) - + self.object:set_velocity(vector_new(v.x, 0.01, v.z)) elseif me_y > p_y then - - self.object:set_velocity({ - x = v.x, - y = -0.01, - z = v.z - }) + self.object:set_velocity(vector_new(v.x, -0.01, v.z)) end end - end -- rnd: new movement direction - if self.path.following - and self.path.way - and self.attack_type ~= "dogshoot" then - + if self.path.following and self.path.way and self.attack_type ~= "dogshoot" then -- no paths longer than 50 - if #self.path.way > 50 - or dist < self.reach then + if #self.path.way > 50 or dist < self.reach then self.path.following = false return end local p1 = self.path.way[1] - if not p1 then self.path.following = false return end - if math.abs(p1.x-s.x) + math.abs(p1.z - s.z) < 0.6 then + if abs(p1.x - s.x) + abs(p1.z - s.z) < 0.6 then -- reached waypoint, remove it from queue table.remove(self.path.way, 1) end -- set new temporary target - p = {x = p1.x, y = p1.y, z = p1.z} + p = vector_copy(p1) end - local vec = { - x = p.x - s.x, - z = p.z - s.z - } - - yaw = (atan(vec.z / vec.x) + math.pi / 2) - self.rotate - - if p.x > s.x then yaw = yaw + math.pi end - - yaw = self:set_yaw( yaw, 0, dtime) + self:turn_in_direction(p.x - s.x, p.z - s.z, 10) -- move towards enemy if beyond mob reach if dist > self.reach then @@ -1100,10 +938,9 @@ function mob_class:do_states_attack (dtime) end if self:is_at_cliff_or_danger() then - self:set_velocity( 0) - self:set_animation( "stand") - local yaw = self.object:get_yaw() or 0 - yaw = self:set_yaw( yaw + 0.78, 8) + self:set_velocity(0) + self:set_animation("stand") + --self:turn_by(PI * (random() - 0.5), 10) else if self.path.stuck then self:set_velocity(self.walk_velocity) @@ -1129,19 +966,13 @@ function mob_class:do_states_attack (dtime) self.timer = 0 if not self.custom_attack then - if self.double_melee_attack and math.random(1, 2) == 1 then + if self.double_melee_attack and random(1, 2) == 1 then self:set_animation("punch2") else self:set_animation("punch") end - local p2 = p - local s2 = s - - p2.y = p2.y + .5 - s2.y = s2.y + .5 - - if self:line_of_sight( p2, s2) == true then + if self:line_of_sight(vector_offset(p, 0, .5, 0), vector_offset(s, 0, .5, 0)) == true then self:mob_sound("attack") -- punch player (or what player is attached to) @@ -1167,59 +998,31 @@ function mob_class:do_states_attack (dtime) elseif self.attack_type == "shoot" or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1) or (self.attack_type == "dogshoot" and (dist > self.reach or dist < self.avoid_distance and self.shooter_avoid_enemy) and self:dogswitch() == 0) then - - p.y = p.y - .5 - s.y = s.y + .5 - - local dist = vector.distance(p, s) - local vec = { - x = p.x - s.x, - y = p.y - s.y, - z = p.z - s.z - } - - yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate - - if p.x > s.x then yaw = yaw +math.pi end - - yaw = self:set_yaw( yaw, 0, dtime) - - local stay_away_from_player = vector.zero() - - --strafe back and fourth - - --stay away from player so as to shoot them - if dist < self.avoid_distance and self.shooter_avoid_enemy then - self:set_animation( "shoot") - stay_away_from_player=vector.multiply(vector.direction(p, s), 0.33) - end + local vec = vector_new(p.x - s.x, p.y - s.y - 1, p.z - s.z) + local dist = (vec.x*vec.x + vec.y*vec.y + vec.z*vec.z)^0.5 + self:turn_in_direction(vec.x, vec.z, 10) if self.strafes then - if not self.strafe_direction then - self.strafe_direction = 1.57 - end - if math.random(40) == 1 then - self.strafe_direction = self.strafe_direction*-1 - end + if not self.strafe_direction then self.strafe_direction = HALFPI end + if random(40) == 1 then self.strafe_direction = self.strafe_direction * -1 end - local dir = vector.rotate_around_axis(vector.direction(s, p), vector.new(0,1,0), self.strafe_direction) - local dir2 = vector.multiply(dir, 0.3 * self.walk_velocity) - - if dir2 and stay_away_from_player then - self.acc = vector.add(dir2, stay_away_from_player) + local dir = -atan2(p.x - s.x, p.z - s.z) + self.acc = vector_new(-sin(dir + self.strafe_direction) * 0.8, 0, cos(dir + self.strafe_direction) * 0.8) + --stay away from player so as to shoot them + if self.avoid_distance and dist < self.avoid_distance and self.shooter_avoid_enemy then + local f = 0.3 * (self.avoid_distance - dist) / self.avoid_distance + self.acc.x, self.acc.z = self.acc.x - sin(dir) * f, self.acc.z + cos(dir) * f end else - self:set_velocity( 0) + self:set_velocity(0) + self:set_animation("stand") end local p = self.object:get_pos() - p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2 - - if self.shoot_interval - and self.timer > self.shoot_interval - and not minetest.raycast(vector.add(p, vector.new(0,self.shoot_offset,0)), vector.add(self.attack:get_pos(), vector.new(0,1.5,0)), false, false):next() - and math.random(1, 100) <= 60 then + p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) * 0.5 + if self.shoot_interval and self.timer > self.shoot_interval and random(1, 100) <= 60 + and not minetest.raycast(vector_offset(p, 0, self.shoot_offset, 0), vector_offset(self.attack:get_pos(), 0, 1.5, 0), false, false):next() then self.timer = 0 self:set_animation( "shoot") @@ -1228,7 +1031,6 @@ function mob_class:do_states_attack (dtime) -- Shoot arrow if minetest.registered_entities[self.arrow] then - local arrow, ent local v = 1 if not self.shoot_arrow then @@ -1238,9 +1040,7 @@ function mob_class:do_states_attack (dtime) end) arrow = minetest.add_entity(p, self.arrow) ent = arrow:get_luaentity() - if ent.velocity then - v = ent.velocity - end + v = ent.velocity or v ent.switch = 1 ent.owner_id = tostring(self.object) -- add unique owner id to arrow @@ -1252,12 +1052,9 @@ function mob_class:do_states_attack (dtime) end end - local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5 -- offset makes shoot aim accurate vec.y = vec.y + self.shoot_offset - vec.x = vec.x * (v / amount) - vec.y = vec.y * (v / amount) - vec.z = vec.z * (v / amount) + vec.x, vec.y, vec.z = vec.x * (v / dist), vec.y * (v / dist), vec.z * (v / dist) if self.shoot_arrow then vec = vector.normalize(vec) self:shoot_arrow(p, vec) @@ -1266,13 +1063,9 @@ function mob_class:do_states_attack (dtime) end end end - elseif self.attack_type == "custom" and self.attack_state then self.attack_state(self, dtime) end - if self.on_attack then - self.on_attack(self, dtime) - end - + if self.on_attack then self.on_attack(self, dtime) end end diff --git a/mods/ENTITIES/mcl_mobs/effects.lua b/mods/ENTITIES/mcl_mobs/effects.lua index cf48551f1..9205c80db 100644 --- a/mods/ENTITIES/mcl_mobs/effects.lua +++ b/mods/ENTITIES/mcl_mobs/effects.lua @@ -250,9 +250,7 @@ end -- set defined animation function mob_class:set_animation(anim, fixed_frame) - if not self.animation or not anim then - return - end + if not self.animation or not anim then return end if self.jockey and self.object:get_attach() then anim = "jockey" @@ -260,11 +258,7 @@ function mob_class:set_animation(anim, fixed_frame) self.jockey = nil end - if self.state == "die" and anim ~= "die" and anim ~= "stand" then - return - end - - + if self.state == "die" and anim ~= "die" and anim ~= "stand" then return end if self.fly and self:flight_check() and anim == "walk" then anim = "fly" end @@ -279,12 +273,7 @@ function mob_class:set_animation(anim, fixed_frame) self._current_animation = anim local a_start = self.animation[anim .. "_start"] - local a_end - if fixed_frame then - a_end = a_start - else - a_end = self.animation[anim .. "_end"] - end + local a_end = fixed_frame and a_start or self.animation[anim .. "_end"] if a_start and a_end then self.object:set_animation({ x = a_start, @@ -294,11 +283,6 @@ function mob_class:set_animation(anim, fixed_frame) end end --- above function exported for mount.lua -function mcl_mobs:set_animation(self, anim) - self:set_animation(anim) -end - local function who_are_you_looking_at (self, dtime) if self.order == "sleep" then self._locked_object = nil diff --git a/mods/ENTITIES/mcl_mobs/init.lua b/mods/ENTITIES/mcl_mobs/init.lua index 1fcbb944a..3ac42ff9b 100644 --- a/mods/ENTITIES/mcl_mobs/init.lua +++ b/mods/ENTITIES/mcl_mobs/init.lua @@ -6,6 +6,19 @@ local modname = minetest.get_current_modname() local path = minetest.get_modpath(modname) local S = minetest.get_translator(modname) mcl_mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt" + +-- used by the libaries below. +-- get node but use fallback for nil or unknown +local node_ok = function(pos, fallback) + fallback = fallback or mcl_mobs.fallback_node + local node = minetest.get_node_or_nil(pos) + if node and minetest.registered_nodes[node.name] then + return node + end + return minetest.registered_nodes[fallback] +end +mcl_mobs.node_ok = node_ok + --api and helpers -- effects: sounds and particles mostly dofile(path .. "/effects.lua") @@ -19,10 +32,9 @@ dofile(path .. "/items.lua") dofile(path .. "/pathfinding.lua") -- combat: attack logic dofile(path .. "/combat.lua") --- the enity functions themselves +-- the entity functions themselves dofile(path .. "/api.lua") - --utility functions dofile(path .. "/breeding.lua") dofile(path .. "/spawning.lua") @@ -37,16 +49,6 @@ local old_spawn_icons = minetest.settings:get_bool("mcl_old_spawn_icons",false) local extended_pet_control = minetest.settings:get_bool("mcl_extended_pet_control",true) local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0 --- get node but use fallback for nil or unknown -local node_ok = function(pos, fallback) - fallback = fallback or mcl_mobs.fallback_node - local node = minetest.get_node_or_nil(pos) - if node and minetest.registered_nodes[node.name] then - return node - end - return minetest.registered_nodes[fallback] -end - --#### REGISTER FUNCS -- Code to execute before custom on_rightclick handling @@ -114,14 +116,8 @@ function mcl_mobs.register_mob(name, def) mcl_mobs.spawning_mobs[name] = true mcl_mobs.registered_mobs[name] = def - local can_despawn - if def.can_despawn ~= nil then - can_despawn = def.can_despawn - elseif def.spawn_class == "passive" then - can_despawn = false - else - can_despawn = true - end + local can_despawn = def.can_despawn + if def.can_despawn == nil then can_despawn = def.spawn_class ~= "passive" end local function scale_difficulty(value, default, min, special) if (not value) or (value == default) or (value == special) then diff --git a/mods/ENTITIES/mcl_mobs/mod.conf b/mods/ENTITIES/mcl_mobs/mod.conf index 927c1c905..8e5c8a261 100644 --- a/mods/ENTITIES/mcl_mobs/mod.conf +++ b/mods/ENTITIES/mcl_mobs/mod.conf @@ -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 diff --git a/mods/ENTITIES/mcl_mobs/mount.lua b/mods/ENTITIES/mcl_mobs/mount.lua index e6b6de78f..fdc499440 100644 --- a/mods/ENTITIES/mcl_mobs/mount.lua +++ b/mods/ENTITIES/mcl_mobs/mount.lua @@ -1,110 +1,41 @@ local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs local mob_class = mcl_mobs.mob_class --- lib_mount by Blert2112 (edited by TenPlus1) +-- based on lib_mount by Blert2112 (edited by TenPlus1) local enable_crash = false local crash_threshold = 6.5 -- ignored if enable_crash=false +local GRAVITY = -9.8 ------------------------------------------------------------------------------- - --- --- Helper functions --- - -local node_ok = function(pos, fallback) - - fallback = fallback or mcl_mobs.fallback_node - - local node = minetest.get_node_or_nil(pos) - - if node and minetest.registered_nodes[node.name] then - return node - end - - return {name = fallback} -end - +local node_ok = mcl_mobs.node_ok +local sign = math.sign -- minetest extension local function node_is(pos) - local node = node_ok(pos) - - if node.name == "air" then - return "air" - end - - if minetest.get_item_group(node.name, "lava") ~= 0 then - return "lava" - end - - if minetest.get_item_group(node.name, "liquid") ~= 0 then - return "liquid" - end - - if minetest.registered_nodes[node.name].walkable == true then - return "walkable" - end - + if node.name == "air" then return "air" end + local ndef = minetest.registered_nodes[node.name] + if not ndef then return "other" end -- unknown/ignore + if ndef.groups.lava then return "lava" end + if ndef.groups.liquid then return "liquid" end + if ndef.walkable then return "walkable" end return "other" end -local function get_sign(i) - - i = i or 0 - - if i == 0 then - return 0 - else - return i / math.abs(i) - end -end - - -local function get_velocity(v, yaw, y) - - local x = -math.sin(yaw) * v - local z = math.cos(yaw) * v - - return {x = x, y = y, z = z} -end - - -local function get_v(v) - return math.sqrt(v.x * v.x + v.z * v.z) -end - - local function force_detach(player) - local attached_to = player:get_attach() - - if not attached_to then - return - end + if not attached_to then return end local entity = attached_to:get_luaentity() - - if entity.driver - and entity.driver == player then - - entity.driver = nil - end + if entity.driver and entity.driver == player then entity.driver = nil end player:set_detach() mcl_player.player_attached[player:get_player_name()] = false - player:set_eye_offset({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0}) + player:set_eye_offset(vector.zero(), vector.zero()) mcl_player.player_set_animation(player, "stand" , 30) player:set_properties({visual_size = {x = 1, y = 1} }) - end -------------------------------------------------------------------------------- - - -minetest.register_on_leaveplayer(function(player) - force_detach(player) -end) +minetest.register_on_leaveplayer(force_detach) minetest.register_on_shutdown(function() local players = minetest.get_connected_players() @@ -118,39 +49,24 @@ minetest.register_on_dieplayer(function(player) return true end) -------------------------------------------------------------------------------- - function mcl_mobs.attach(entity, player) - - local attach_at, eye_offset - - entity.player_rotation = entity.player_rotation or {x = 0, y = 0, z = 0} - entity.driver_attach_at = entity.driver_attach_at or {x = 0, y = 0, z = 0} - entity.driver_eye_offset = entity.driver_eye_offset or {x = 0, y = 0, z = 0} + entity.player_rotation = entity.player_rotation or vector.zero() + entity.driver_attach_at = entity.driver_attach_at or vector.zero() + entity.driver_eye_offset = entity.driver_eye_offset or vector.zero() entity.driver_scale = entity.driver_scale or {x = 1, y = 1} - local rot_view = 0 - - if entity.player_rotation.y == 90 then - rot_view = math.pi/2 - end - - attach_at = entity.driver_attach_at - eye_offset = entity.driver_eye_offset + local rot_view = entity.player_rotation.y == 90 and math.pi/2 or 0 + local attach_at = entity.driver_attach_at + local eye_offset = entity.driver_eye_offset entity.driver = player force_detach(player) player:set_attach(entity.object, "", attach_at, entity.player_rotation) mcl_player.player_attached[player:get_player_name()] = true - player:set_eye_offset(eye_offset, {x = 0, y = 0, z = 0}) + player:set_eye_offset(eye_offset, vector.zero()) - player:set_properties({ - visual_size = { - x = entity.driver_scale.x, - y = entity.driver_scale.y - } - }) + player:set_properties({ visual_size = entity.driver_scale }) minetest.after(0.2, function(name) local player = minetest.get_player_by_name(name) @@ -164,162 +80,88 @@ end function mcl_mobs.detach(player, offset) - force_detach(player) - mcl_player.player_set_animation(player, "stand" , 30) - - --local pos = player:get_pos() - - --pos = {x = pos.x + offset.x, y = pos.y + 0.2 + offset.y, z = pos.z + offset.z} - - player:add_velocity(vector.new(math.random(-6,6),math.random(5,8),math.random(-6,6))) --throw the rider off - - --[[ - minetest.after(0.1, function(name, pos) - local player = minetest.get_player_by_name(name) - if player then - player:set_pos(pos) - end - end, player:get_player_name(), pos) - ]]-- + player:add_velocity(vector.new(math.random()*12-6,math.random()*3+5,math.random()*12-6)) --throw the rider off end function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime) - - local rot_view = 0 - - if entity.player_rotation.y == 90 then - rot_view = math.pi/2 - end - - local acce_y = 0 local velo = entity.object:get_velocity() - - entity.v = get_v(velo) * get_sign(entity.v) + local v = math.sqrt(velo.x * velo.x + velo.y * velo.y) + local acce_y = GRAVITY -- process controls if entity.driver then - local ctrl = entity.driver:get_player_control() - - -- move forwards - if ctrl.up then - - entity.v = entity.v + entity.accel / 10 * entity.run_velocity / 2.6 - - -- move backwards - elseif ctrl.down then - - if entity.max_speed_reverse == 0 and entity.v == 0 then - return - end - - entity.v = entity.v - entity.accel / 10 + if ctrl.up then -- forward + v = v + entity.accel * 0.1 * entity.run_velocity * 0.385 + elseif ctrl.down then -- backwards + if entity.max_speed_reverse == 0 and v == 0 then return end + v = v - entity.accel * 0.1 * entity.run_velocity * 0.385 end - -- fix mob rotation - entity.object:set_yaw(entity.driver:get_look_horizontal() - entity.rotate) + entity:set_yaw(entity.driver:get_look_horizontal() - entity.rotate, 2) if can_fly then - + -- FIXME: use acce_y instead? -- fly up if ctrl.jump then - velo.y = velo.y + 1 - if velo.y > entity.accel then velo.y = entity.accel end - - elseif velo.y > 0 then + velo.y = math.min(velo.y + 1, entity.accel) + elseif velo.y > 0.1 then velo.y = velo.y - 0.1 - if velo.y < 0 then velo.y = 0 end + elseif velo.y > 0 then + velo.y = 0 end -- fly down if ctrl.sneak then - velo.y = velo.y - 1 - if velo.y < -entity.accel then velo.y = -entity.accel end - - elseif velo.y < 0 then + velo.y = math.max(velo.y - 1, -entity.accel) + elseif velo.y < -0.1 then velo.y = velo.y + 0.1 - if velo.y > 0 then velo.y = 0 end + elseif velo.y < 0 then + velo.y = 0 end - else - -- jump if ctrl.jump then - if velo.y == 0 then velo.y = velo.y + entity.jump_height - acce_y = acce_y + (acce_y * 3) + 1 + acce_y = acce_y + 1 end end - end end - -- Stop! - local s = get_sign(entity.v) - - entity.v = entity.v - 0.02 * s - - if s ~= get_sign(entity.v) then - - entity.object:set_velocity({x = 0, y = 0, z = 0}) - entity.v = 0 - return + if math.abs(v) < 0.02 then -- stop + entity.object:set_velocity(vector.zero()) + v = 0 + else + v = v - 0.02 * sign(v) -- slow down end -- if not moving then set animation and return - if entity.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then - - if stand_anim then - mcl_mobs:set_animation(entity, stand_anim) - end - + if v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then + entity:set_animation(stand_anim) return - end - - -- set moving animation - if moving_anim then - mcl_mobs:set_animation(entity, moving_anim) + else + entity:set_animation(moving_anim) end -- enforce speed limit forward and reverse - local max_spd = entity.max_speed_reverse - - if get_sign(entity.v) >= 0 then - max_spd = entity.max_speed_forward - end - - if math.abs(entity.v) > max_spd then - entity.v = entity.v - get_sign(entity.v) - end + v = math.max(-entity.max_speed_reverse, math.min(v, entity.max_speed_forward)) -- Set position, velocity and acceleration local p = entity.object:get_pos() - local new_velo - local new_acce = {x = 0, y = -9.8, z = 0} - p.y = p.y - 0.5 local ni = node_is(p) - local v = entity.v - if ni == "air" then - - if can_fly == true then - new_acce.y = 0 - end - + if can_fly then acce_y = acce_y - GRAVITY end elseif ni == "liquid" or ni == "lava" then - if ni == "lava" and entity.lava_damage ~= 0 then - entity.lava_counter = (entity.lava_counter or 0) + dtime - if entity.lava_counter > 1 then - minetest.sound_play("default_punch", { object = entity.object, max_hear_distance = 5 @@ -336,18 +178,15 @@ function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime) if entity.terrain_type == 2 or entity.terrain_type == 3 then - - new_acce.y = 0 + acce_y = 0 p.y = p.y + 1 - if node_is(p) == "liquid" then - if velo.y >= 5 then velo.y = 5 elseif velo.y < 0 then - new_acce.y = 20 + acce_y = 20 else - new_acce.y = 5 + acce_y = 5 end else if math.abs(velo.y) < 1 then @@ -362,75 +201,51 @@ function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime) end end - new_velo = get_velocity(v, entity.object:get_yaw() - rot_view, velo.y) - new_acce.y = new_acce.y + acce_y + local rot_view = entity.player_rotation.y == 90 and math.pi/2 or 0 + local new_yaw = entity.object:get_yaw() - rot_view + local new_velo = vector.new(-math.sin(new_yaw) * v, velo.y, math.cos(new_yaw) * v) entity.object:set_velocity(new_velo) - entity.object:set_acceleration(new_acce) + entity.object:set_acceleration(vector.new(0, acce_y, 0)) - -- CRASH! if enable_crash then - - local intensity = entity.v2 - v - - if intensity >= crash_threshold then - + if v >= crash_threshold then entity.object:punch(entity.object, 1.0, { full_punch_interval = 1.0, - damage_groups = {fleshy = intensity} + damage_groups = {fleshy = v} }, nil) - end end - - entity.v2 = v end -- directional flying routine by D00Med (edited by TenPlus1) - function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim) - local ctrl = entity.driver:get_player_control() local velo = entity.object:get_velocity() local dir = entity.driver:get_look_dir() - local yaw = entity.driver:get_look_horizontal() + 1.57 -- offset fix between old and new commands + local yaw = entity.driver:get_look_horizontal() if ctrl.up then - entity.object:set_velocity({ - x = dir.x * speed, - y = dir.y * speed + 2, - z = dir.z * speed - }) - + entity.object:set_velocity(vector.new(dir.x * speed, dir.y * speed + 2, dir.z * speed)) elseif ctrl.down then - entity.object:set_velocity({ - x = -dir.x * speed, - y = dir.y * speed + 2, - z = -dir.z * speed - }) - + entity.object:set_velocity(vector.new(-dir.x * speed, dir.y * speed + 2, -dir.z * speed)) elseif not ctrl.down or ctrl.up or ctrl.jump then - entity.object:set_velocity({x = 0, y = -2, z = 0}) + entity.object:set_velocity(vector.new(0, -2, 0)) end - entity.object:set_yaw(yaw + math.pi + math.pi / 2 - entity.rotate) + entity:set_yaw(yaw - entity.rotate, 2) -- firing arrows if ctrl.LMB and ctrl.sneak and shoots then - local pos = entity.object:get_pos() - local obj = minetest.add_entity({ - x = pos.x + 0 + dir.x * 2.5, - y = pos.y + 1.5 + dir.y, - z = pos.z + 0 + dir.z * 2.5}, arrow) - + local obj = minetest.add_entity(vector.offset(pos, dir.x * 2.5, 1.5 + dir.y, dir.z * 2.5), arrow) local ent = obj:get_luaentity() if ent then ent.switch = 1 -- for mob specific arrows ent.owner_id = tostring(entity.object) -- so arrows dont hurt entity you are riding - local vec = {x = dir.x * 6, y = dir.y * 6, z = dir.z * 6} + local vec = vector.new(dir.x * 6, dir.y * 6, dir.z * 6) local yaw = entity.driver:get_look_horizontal() - obj:set_yaw(yaw + math.pi / 2) + obj:set_yaw(yaw) obj:set_velocity(vec) else obj:remove() @@ -439,11 +254,9 @@ function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_an -- change animation if stopped if velo.x == 0 and velo.y == 0 and velo.z == 0 then - - mcl_mobs:set_animation(entity, stand_anim) + entity:set_animation(stand_anim) else - -- moving animation - mcl_mobs:set_animation(entity, moving_anim) + entity:set_animation(moving_anim) end end @@ -452,12 +265,7 @@ mcl_mobs.mob_class.fly = mcl_mobs.fly mcl_mobs.mob_class.attach = mcl_mobs.attach function mob_class:on_detach_child(child) - if self.detach_child then - if self.detach_child(self, child) then - return - end - end - if self.driver == child then - self.driver = nil - end + if self.detach_child and self.detach_child(self, child) then return end + if self.driver == child then self.driver = nil end end + diff --git a/mods/ENTITIES/mcl_mobs/movement.lua b/mods/ENTITIES/mcl_mobs/movement.lua index de002b8df..c62602d3b 100644 --- a/mods/ENTITIES/mcl_mobs/movement.lua +++ b/mods/ENTITIES/mcl_mobs/movement.lua @@ -11,77 +11,61 @@ local PATHFINDING = "gowp" local node_snow = "mcl_core:snow" local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false +local logging = minetest.settings:get_bool("mcl_logging_mobs_movement", true) -local atann = math.atan -local function atan(x) - if not x or x ~= x then - return 0 - else - return atann(x) - end -end +local random = math.random +local sin = math.sin +local cos = math.cos +local abs = math.abs +local floor = math.floor +local PI = math.pi +local TWOPI = 2 * math.pi +local HALFPI = 0.5 * math.pi +local QUARTERPI = 0.25 * math.pi + +local vector_new = vector.new +local vector_zero = vector.zero +local vector_copy = vector.copy +local vector_offset = vector.offset +local vector_distance = vector.distance local registered_fallback_node = minetest.registered_nodes[mcl_mobs.fallback_node] +-- Stop movement and stand +function mob_class:stand() + self:set_velocity(0) + self.state = "stand" + self:set_animation("stand") +end + -- get node but use fallback for nil or unknown local node_ok = function(pos, fallback) local node = minetest.get_node_or_nil(pos) - if node and minetest.registered_nodes[node.name] then - return node - end - if fallback then - return minetest.registered_nodes[fallback] - else - return registered_fallback_node - end + if node and minetest.registered_nodes[node.name] then return node end + return fallback and minetest.registered_nodes[fallback] or registered_fallback_node end -- Returns true is node can deal damage to self function mob_class:is_node_dangerous(nodename) - local nn = nodename - if self.lava_damage > 0 then - if minetest.get_item_group(nn, "lava") ~= 0 then - return true - end - end - if self.fire_damage > 0 then - if minetest.get_item_group(nn, "fire") ~= 0 then - return true - end - end - if minetest.registered_nodes[nn] and minetest.registered_nodes[nn].damage_per_second and minetest.registered_nodes[nn].damage_per_second > 0 then - return true - end - return false + local ndef = minetest.registered_nodes[nodename] + return ndef and ( + (self.lava_damage > 0 and ndef.groups.lava) + or (self.fire_damage > 0 and ndef.groups.fire) + or ((ndef.damage_per_second or 0) > 0)) end - -- Returns true if node is a water hazard function mob_class:is_node_waterhazard(nodename) - local nn = nodename - if self.water_damage > 0 then - if minetest.get_item_group(nn, "water") ~= 0 then - return true - end - end - if minetest.registered_nodes[nn] and minetest.registered_nodes[nn].drowning and minetest.registered_nodes[nn].drowning > 0 then - if self.breath_max ~= -1 then - -- check if the mob is water-breathing _and_ the block is water; only return true if neither is the case - -- this will prevent water-breathing mobs to classify water or e.g. sand below them as dangerous - if not self.breathes_in_water and minetest.get_item_group(nn, "water") ~= 0 then - return true - end - end - end - return false + local ndef = minetest.registered_nodes[nodename] + return ndef and ndef.groups.water and ( + (self.water_damage > 0) + or (not self.breathes_in_water and self.breath_max ~= -1 and (ndef.drowning or 0) > 0)) end -local function raycast_line_of_sight (origin, target) +local function raycast_line_of_sight(origin, target) local raycast = minetest.raycast(origin, target, false, true) - local los_blocked = false - for hitpoint in raycast do if hitpoint.type == "node" then --TODO type object could block vision, for example chests @@ -101,25 +85,23 @@ end function mob_class:target_visible(origin) if not origin then return end - if not self.attack then return end local target_pos = self.attack:get_pos() - if not target_pos then return end - local origin_eye_pos = vector.offset(origin, 0, self.head_eye_height, 0) + local origin_eye_pos = vector_offset(origin, 0, self.head_eye_height, 0) --minetest.log("origin: " .. dump(origin)) --minetest.log("origin_eye_pos: " .. dump(origin_eye_pos)) local targ_head_height, targ_feet_height + local cbox = self.collisionbox if self.attack:is_player() then - local cbox = self.object:get_properties().collisionbox - targ_head_height = vector.offset(target_pos, 0, cbox[5], 0) + targ_head_height = vector_offset(target_pos, 0, cbox[5], 0) targ_feet_height = target_pos -- Cbox would put feet under ground which interferes with ray else - targ_head_height = vector.offset(target_pos, 0, self.collisionbox[5], 0) - targ_feet_height = vector.offset(target_pos, 0, self.collisionbox[2], 0) + targ_head_height = vector_offset(target_pos, 0, cbox[5], 0) + targ_feet_height = vector_offset(target_pos, 0, cbox[2], 0) end --minetest.log("start targ_head_height: " .. dump(targ_head_height)) @@ -139,7 +121,6 @@ end -- check line of sight (BrunoMine) function mob_class:line_of_sight(pos1, pos2, stepsize) - stepsize = stepsize or 1 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize) @@ -150,8 +131,7 @@ function mob_class:line_of_sight(pos1, pos2, stepsize) end -- New pos1 to be analyzed - local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z} - + local npos1 = vector_copy(pos1) local r, pos = minetest.line_of_sight(npos1, pos2, stepsize) -- Checks the return @@ -161,23 +141,19 @@ function mob_class:line_of_sight(pos1, pos2, stepsize) local nn = minetest.get_node(pos).name -- Target Distance (td) to travel - local td = vector.distance(pos1, pos2) + local td = vector_distance(pos1, pos2) -- Actual Distance (ad) traveled local ad = 0 -- It continues to advance in the line of sight in search of a real -- obstruction which counts as 'normal' nodebox. - while minetest.registered_nodes[nn] - and minetest.registered_nodes[nn].walkable == false do - + while minetest.registered_nodes[nn] and minetest.registered_nodes[nn].walkable == false do -- Check if you can still move forward - if td < ad + stepsize then - return true -- Reached the target - end + if td < ad + stepsize then return true end -- Reached the target -- Moves the analyzed pos - local d = vector.distance(pos1, pos2) + local d = vector_distance(pos1, pos2) npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y @@ -200,7 +176,6 @@ function mob_class:line_of_sight(pos1, pos2, stepsize) -- New Nodename found nn = minetest.get_node(pos).name - end return false @@ -209,94 +184,83 @@ end function mob_class:can_jump_cliff() local yaw = self.object:get_yaw() local pos = self.object:get_pos() - local v = self.object:get_velocity() - - local v2 = math.abs(v.x)+math.abs(v.z)*.833 - local jump_c_multiplier = 1 - if v2/self.walk_velocity/2>1 then - jump_c_multiplier = v2/self.walk_velocity/2 - end + local cbox = self.collisionbox -- where is front - local dir_x = -math.sin(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+0.6 - local dir_z = math.cos(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+0.6 + local dir_x = -sin(yaw) * (cbox[4] + 0.5) + local dir_z = cos(yaw) * (cbox[4] + 0.5) --is there nothing under the block in front? if so jump the gap. - local nodLow = node_ok({ - x = pos.x + dir_x-0.6, - y = pos.y - 0.5, - z = pos.z + dir_z-0.6 - }, "air") + local node_low = node_ok(vector_offset(pos, dir_x * 0.6, -0.5, dir_z * 0.6), "air") + -- next is solid, no need to jump + if minetest.registered_nodes[node_low.name] and minetest.registered_nodes[node_low.name].walkable == true then + self._jumping_cliff = false + return false + end - local nodFar = node_ok({ - x = pos.x + dir_x*2, - y = pos.y - 0.5, - z = pos.z + dir_z*2 - }, "air") - - local nodFar2 = node_ok({ - x = pos.x + dir_x*2.5, - y = pos.y - 0.5, - z = pos.z + dir_z*2.5 - }, "air") - - - if minetest.registered_nodes[nodLow.name] - and minetest.registered_nodes[nodLow.name].walkable ~= true - - - and (minetest.registered_nodes[nodFar.name] - and minetest.registered_nodes[nodFar.name].walkable == true - - or minetest.registered_nodes[nodFar2.name] - and minetest.registered_nodes[nodFar2.name].walkable == true) + local node_far = node_ok(vector_offset(pos, dir_x * 1.6, -0.5, dir_z * 1.6), "air") + local node_far2 = node_ok(vector_offset(pos, dir_x * 2.5, -0.5, dir_z * 2.5), "air") + -- TODO: also check there is air above these nodes? + -- some place to land on + if (minetest.registered_nodes[node_far.name] and minetest.registered_nodes[node_far.name].walkable == true) + or (minetest.registered_nodes[node_far2.name] and minetest.registered_nodes[node_far2.name].walkable == true) then - --disable fear heigh while we make our jump + --disable fear height while we make our jump self._jumping_cliff = true - minetest.after(1, function() + --minetest.log("Jumping cliff: " .. self.name .. " nodes " .. node_low.name .. " - " .. node_far.name .. " - " .. node_far2.name) + minetest.after(.1, function() if self and self.object then self._jumping_cliff = false end end) return true else + self._jumping_cliff = false return false end end -- is mob facing a cliff or danger function mob_class:is_at_cliff_or_danger() + --minetest.log(self.name.. " "..tostring(self.fear_height).." "..tostring(self._jumping_cliff).." "..tostring(self._can_jump_cliff).." "..tostring(self.fly)) if self.fear_height == 0 or self._jumping_cliff or self._can_jump_cliff or not self.object:get_luaentity() then -- 0 for no falling protection! return false end - + if self.fly then -- also avoids checking fish + return false + end local yaw = self.object:get_yaw() - local dir_x = -math.sin(yaw) * (self.collisionbox[4] + 0.5) - local dir_z = math.cos(yaw) * (self.collisionbox[4] + 0.5) + local cbox = self.collisionbox + local dir_x = -sin(yaw) * (cbox[4] + 0.5) + local dir_z = cos(yaw) * (cbox[4] + 0.5) local pos = self.object:get_pos() - local ypos = pos.y + self.collisionbox[2] -- just above floor + local ypos = pos.y + cbox[2] + 0.1 -- just above floor local free_fall, blocker = minetest.line_of_sight( - vector.new(pos.x + dir_x, ypos, pos.z + dir_z), - vector.new(pos.x + dir_x, ypos - self.fear_height, pos.z + dir_z)) + vector_new(pos.x + dir_x, ypos, pos.z + dir_z), + vector_new(pos.x + dir_x, floor(ypos - self.fear_height), pos.z + dir_z)) if free_fall then - return true - else - local bnode = minetest.get_node(blocker) - local danger = self:is_node_dangerous(bnode.name) - if danger then - return true - else - local def = minetest.registered_nodes[bnode.name] - if def and def.walkable then - return false - end - end + return "free fall" + end + local height = ypos + 0.4 - blocker.y + local chance = (self.jump_height or 4) * 0.25 / (height * height) + if height >= self.fear_height and random() < chance then + if logging then + minetest.log("action", "[mcl_mobs] "..self.name.." avoiding drop of "..height) --.." chance "..chance) + end + return "drop of "..tostring(height) + end + local bnode = minetest.get_node(blocker) + -- minetest.log("At cliff: " .. self.name .. " below " .. bnode.name .. " height "..height) + if self:is_node_dangerous(self.standing_in) or self:is_node_waterhazard(self.standing_in) then + return false -- allow to get out of the immediate danger + end + if self:is_node_dangerous(bnode.name) or self:is_node_waterhazard(bnode.name) then + return bnode.name end - return false end @@ -305,7 +269,10 @@ end function mob_class:is_at_water_danger() if self.water_damage == 0 and self.breath_max == -1 then --minetest.log("Do not need a water check for: " .. self.name) - return + return false + end + if self.fly then -- also avoids checking fish + return false end local in_water_danger = self:is_node_waterhazard(self.standing_in) or self:is_node_waterhazard(self.standing_on) @@ -318,33 +285,30 @@ function mob_class:is_at_water_danger() local pos = self.object:get_pos() if not yaw or not pos then - return + return false end - local dir_x = -math.sin(yaw) * (self.collisionbox[4] + 0.5) - local dir_z = math.cos(yaw) * (self.collisionbox[4] + 0.5) + local cbox = self.collisionbox + local dir_x = -sin(yaw) * (cbox[4] + 0.5) + local dir_z = cos(yaw) * (cbox[4] + 0.5) - local ypos = pos.y + self.collisionbox[2] -- just above floor + local ypos = pos.y + cbox[2] + 0.1 -- just above floor local los, blocker = minetest.line_of_sight( - vector.new(pos.x + dir_x, ypos, pos.z + dir_z), - vector.new(pos.x + dir_x, ypos - 3, pos.z + dir_z)) + vector_new(pos.x + dir_x, ypos, pos.z + dir_z), + vector_new(pos.x + dir_x, ypos - 3, pos.z + dir_z)) if not los then local bnode = minetest.get_node(blocker) local waterdanger = self:is_node_waterhazard(bnode.name) - - if waterdanger and not in_water_danger then - return true + if waterdanger then + return bnode.name end end - return false end function mob_class:env_danger_movement_checks(player_in_active_range) - local yaw = 0 - if not player_in_active_range then return end if self.state == PATHFINDING @@ -355,208 +319,149 @@ function mob_class:env_danger_movement_checks(player_in_active_range) end if self:is_at_water_danger() then - --minetest.log("At water danger for mob, stop?: " .. self.name) - if math.random(1, 10) <= 7 then - if self.state ~= "stand" then - self:set_velocity(0) - self.state = "stand" - self:set_animation( "stand") - end - yaw = yaw + math.random() - 0.5 - yaw = self:set_yaw( yaw, 8) + if logging then + minetest.log("action", "[mcl_mobs] "..self.name.." at water danger, stop and rotate?") + end + if random() <= 0.9 then + if self.state ~= "stand" then self:stand() end + self:turn_by(PI * (random() - 0.5), 10) return end end - - --[[if self:is_at_cliff_or_danger(can_jump_cliff) then - if self.state ~= "stand" then - self:set_velocity(0) - self.state = "stand" - self:set_animation( "stand") + if not self._can_jump_cliff and self:is_at_cliff_or_danger() then + if logging then + minetest.log("action", "[mcl_mobs] "..self.name.." at cliff danger, rotate") end - local yaw = self.object:get_yaw() or 0 - yaw = self:set_yaw( yaw + 0.78, 8) - end--]] + if random() <= 0.99 then + if self.state ~= "stand" then self:stand() end + self:turn_by(PI * (random() - 0.5), 10) + end + end end -- jump if facing a solid node (not fences or gates) function mob_class:do_jump() - if not self.jump - or self.jump_height == 0 - or self.fly - or self.order == "stand" then - return false - end - + if not self.jump or self.jump_height == 0 or self.fly or self.order == "stand" then return false end self.facing_fence = false + self._jumping_cliff = false -- something stopping us while moving? - if self.state ~= "stand" - and self:get_velocity() > 0.5 - and self.object:get_velocity().y ~= 0 then - return false - end + if self.state ~= "stand" and self:get_velocity() > 0.5 and self.object:get_velocity().y ~= 0 then return false end local pos = self.object:get_pos() + local v = self.object:get_velocity() + local cbox = self.collisionbox + + local in_water = minetest.get_item_group(node_ok(pos).name, "water") > 0 + -- what is mob standing on? + pos.y = pos.y + cbox[2] + + local node_below = node_ok(vector_offset(pos, 0, -0.2, 0)) + local nbdef = minetest.registered_nodes[node_below.name] + if nbdef and nbdef.walkable == false and not in_water then return false end + local yaw = self.object:get_yaw() - -- what is mob standing on? - pos.y = pos.y + self.collisionbox[2] - 0.2 - - local nod = node_ok(pos) - - if minetest.registered_nodes[nod.name].walkable == false then - return false - end - - local v = self.object:get_velocity() - local v2 = math.abs(v.x)+math.abs(v.z)*.833 - local jump_c_multiplier = 1 - if v2/self.walk_velocity/2>1 then - jump_c_multiplier = v2/self.walk_velocity/2 - end - - local yaw_dir = minetest.yaw_to_dir(self.object:get_yaw()) - -- where is front - local dir_x = -math.sin(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+yaw_dir.x - local dir_z = math.cos(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+yaw_dir.z + local dir_x = -sin(yaw) * (cbox[4] + 0.5) + v.x * 0.25 + local dir_z = cos(yaw) * (cbox[4] + 0.5) + v.z * 0.25 -- what is in front of mob? - nod = node_ok({ - x = pos.x + dir_x, - y = pos.y + 0.5, - z = pos.z + dir_z - }) + local nod = node_ok(vector_offset(pos, dir_x, 0.5, dir_z)) + local ndef = minetest.registered_nodes[nod.name] + + -- thin blocks that do not need to be jumped + if nod.name == node_snow or (ndef and ndef.groups.carpet or 0) > 0 then return false end -- this is used to detect if there's a block on top of the block in front of the mob. -- If there is, there is no point in jumping as we won't manage. - local nodTop = node_ok({ - x = pos.x + dir_x, - y = pos.y + 1.5, - z = pos.z + dir_z - }, "air") + local node_top = node_ok(vector_offset(pos, dir_x, 1.5, dir_z), "air") + -- TODO: also check above the mob itself? + -- we don't attempt to jump if there's a stack of blocks blocking, unless attacking + local ntdef = minetest.registered_nodes[node_top.name] + if ntdef and ntdef.walkable == true and not (self.attack and self.state == "attack") then return false end - -- we don't attempt to jump if there's a stack of blocks blocking - if minetest.registered_nodes[nodTop.name].walkable == true and not (self.attack and self.state == "attack") then + if self.walk_chance ~= 0 and not (ndef and ndef.walkable) and not self._can_jump_cliff then return false end + + if (ndef.groups.fence or 0) ~= 0 or (ndef.groups.fence_gate or 0) ~= 0 or (ndef.groups.wall or 0) ~= 0 then + self.facing_fence = true return false end - -- thin blocks that do not need to be jumped - if nod.name == node_snow then - return false + v.y = self.jump_height + 0.3 + if in_water then + v.x, v.y, v.z = v.x * 1.2, v.y * 1.5, v.z * 1.2 + elseif self._can_jump_cliff then + v.x, v.y, v.z = v.x * 2.5, v.y * 1.1, v.z * 2.5 end - local ndef = minetest.registered_nodes[nod.name] - if self.walk_chance == 0 or ndef and ndef.walkable or self._can_jump_cliff then + self:set_animation("jump") -- only when defined + self.object:set_velocity(v) - if minetest.get_item_group(nod.name, "fence") == 0 - and minetest.get_item_group(nod.name, "fence_gate") == 0 - and minetest.get_item_group(nod.name, "wall") == 0 then + -- when in air move forward + local forward = function(self, v) + if not self.object or not self.object:get_luaentity() or self.state == "die" then return end + self.object:set_acceleration(vector_new(v.x * 2, DEFAULT_FALL_SPEED, v.z * 2)) + end + -- trying multiple time helps mobs jump + minetest.after(0.1, forward, self, v) + minetest.after(0.2, forward, self, v) + minetest.after(0.3, forward, self, v) - local v = self.object:get_velocity() - - v.y = self.jump_height + 0.1 * 3 - - if self._can_jump_cliff then - v=vector.multiply(v, vector.new(2.8,1,2.8)) - end - - self:set_animation( "jump") -- only when defined - - self.object:set_velocity(v) - - -- when in air move forward - minetest.after(0.3, function(self, v) - if (not self.object) or (not self.object:get_luaentity()) or (self.state == "die") then - return - end - self.object:set_acceleration({ - x = v.x * 2, - y = DEFAULT_FALL_SPEED, - z = v.z * 2, - }) - end, self, v) - - if self.jump_sound_cooloff <= 0 then - self:mob_sound("jump") - self.jump_sound_cooloff = 0.5 - end - else - self.facing_fence = true - end - - -- if we jumped against a block/wall 4 times then turn - if self.object:get_velocity().x ~= 0 - and self.object:get_velocity().z ~= 0 then - - self.jump_count = (self.jump_count or 0) + 1 - - if self.jump_count == 4 then - - local yaw = self.object:get_yaw() or 0 - - yaw = self:set_yaw( yaw + 1.35, 8) - - self.jump_count = 0 - end - end - - return true + if self.jump_sound_cooloff <= 0 then + self:mob_sound("jump") + self.jump_sound_cooloff = 0.5 end - return false + -- if we jumped against a block/wall 4 times then turn + if (v.x * v.x + v.z * v.z) < 0.1 then + self._jump_count = (self._jump_count or 0) + 1 + if self._jump_count == 4 then + self:turn_by(TWOPI * (random() - 0.5), 8) + self._jump_count = 0 + return false + end + else + self._jump_count = 0 + end + return true end -- should mob follow what I'm holding ? function mob_class:follow_holding(clicker) if self.nofollow then return false end - - if mcl_mobs.invis[clicker:get_player_name()] then - return false - end + if mcl_mobs.invis[clicker:get_player_name()] then return false end local item = clicker:get_wielded_item() local t = type(self.follow) - -- single item - if t == "string" - and item:get_name() == self.follow then + if t == "string" and item:get_name() == self.follow then return true - -- multiple items elseif t == "table" then - for no = 1, #self.follow do - - if self.follow[no] == item:get_name() then - return true - end + if self.follow[no] == item:get_name() then return true end end end - return false end -- find and replace what mob is looking for (grass, wheat etc.) function mob_class:replace_node(pos) - - if not self.replace_rate or not self.replace_what - or self.child == true + or self.child or self.object:get_velocity().y ~= 0 - or math.random(1, self.replace_rate) > 1 then + or random(1, self.replace_rate) > 1 then return end local what, with, y_offset if type(self.replace_what[1]) == "table" then - - local num = math.random(#self.replace_what) + local num = random(#self.replace_what) what = self.replace_what[num][1] or "" with = self.replace_what[num][2] or "" @@ -571,7 +476,6 @@ function mob_class:replace_node(pos) local node = minetest.get_node(pos) if node.name == what then - local oldnode = {name = what, param2 = node.param2} local newnode = {name = with, param2 = node.param2} local on_replace_return = false @@ -581,7 +485,6 @@ function mob_class:replace_node(pos) if on_replace_return ~= false then - if mobs_griefing then minetest.after(self.replace_delay, function() if self and self.object and self.object:get_velocity() and self.health > 0 then @@ -595,32 +498,20 @@ end -- specific runaway local specific_runaway = function(list, what) - if type(list) ~= "table" then - list = {} - end - -- no list so do not run - if list == nil then - return false - end - + if list == nil then return false end + if type(list) ~= "table" then list = {} 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 runaway from function mob_class:check_runaway_from() - if not self.runaway_from and self.state ~= "flop" then - return - end + if not self.runaway_from and self.state ~= "flop" then return end local s = self.object:get_pos() local p, sp, dist @@ -630,9 +521,7 @@ function mob_class:check_runaway_from() local objs = minetest.get_objects_inside_radius(s, self.view_range) for n = 1, #objs do - if objs[n]:is_player() then - if mcl_mobs.invis[ objs[n]:get_player_name() ] or self.owner == objs[n]:get_player_name() or (not self:object_in_range(objs[n])) then @@ -644,7 +533,6 @@ function mob_class:check_runaway_from() end else obj = objs[n]:get_luaentity() - if obj then player = obj.object type = obj.type @@ -655,20 +543,13 @@ function mob_class:check_runaway_from() -- find specific mob to runaway from if name ~= "" and name ~= self.name and specific_runaway(self.runaway_from, name) then - p = player:get_pos() sp = s - - -- aim higher to make looking up hills more realistic - p.y = p.y + 1 - sp.y = sp.y + 1 - - dist = vector.distance(p, s) - - + dist = vector_distance(p, s) -- choose closest player/mpb to runaway from if dist < min_dist - and self:line_of_sight(sp, p, 2) == true then + and self:line_of_sight(vector_offset(sp, 0, 1, 0), vector_offset(p, 0, 1, 0), 2) == true then + -- aim higher to make looking up hills more realistic min_dist = dist min_player = player end @@ -676,21 +557,8 @@ function mob_class:check_runaway_from() end if min_player then - local lp = player:get_pos() - local vec = { - x = lp.x - s.x, - y = lp.y - s.y, - z = lp.z - s.z - } - - local yaw = (atan(vec.z / vec.x) + 3 *math.pi/ 2) - self.rotate - - if lp.x > s.x then - yaw = yaw + math.pi - end - - yaw = self:set_yaw( yaw, 4) + self:turn_in_direction(s.x - lp.x, s.z - lp.z, 4) -- away from player self.state = "runaway" self.runaway_timer = 3 self.following = nil @@ -717,7 +585,6 @@ function mob_class:check_follow() if self.type == "npc" and self.order == "follow" and self.state ~= "attack" and self.order ~= "sit" and self.owner ~= "" then - if self.following and self.owner and self.owner ~= self.following:get_player_name() then self.following = nil end @@ -733,40 +600,25 @@ function mob_class:check_follow() -- follow that thing if self.following then local s = self.object:get_pos() - - local p - if self.following:is_player() then - p = self.following:get_pos() - elseif self.following.object then - p = self.following.object:get_pos() - end + local p = self.following:is_player() and self.following:get_pos() + or self.following.object and self.following.object:get_pos() if p then - local dist = vector.distance(p, s) - if (not self:object_in_range(self.following)) then self.following = nil else - local vec = { - x = p.x - s.x, - z = p.z - s.z - } - - local yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate - if p.x > s.x then yaw = yaw +math.pi end - self:set_yaw( yaw, 2.35) + self:turn_in_direction(p.x - s.x, p.z - s.z, 2.35) -- anyone but standing npc's can move along + local dist = vector_distance(p, s) if dist > 3 and self.order ~= "stand" then - self:set_velocity(self.follow_velocity) + self:set_velocity(self.follow_velocity) if self.walk_chance ~= 0 then - self:set_animation( "run") - else - self:set_animation( "stand") + self:set_animation("run") end else self:set_velocity(0) - self:set_animation( "stand") + self:set_animation("stand") end return end @@ -781,19 +633,15 @@ function mob_class:flop() if self:flight_check(s) == false then self.state = "flop" - self.object:set_acceleration({x = 0, y = DEFAULT_FALL_SPEED, z = 0}) + self.object:set_acceleration(vector_new(0, DEFAULT_FALL_SPEED, 0)) local p = self.object:get_pos() - local sdef = minetest.registered_nodes[node_ok(vector.add(p, vector.new(0,self.collisionbox[2]-0.2,0))).name] + local sdef = minetest.registered_nodes[node_ok(vector_offset(p, 0, self.collisionbox[2] - 0.2, 0)).name] -- Flop on ground if sdef and sdef.walkable then if self.object:get_velocity().y < 0.1 then self:mob_sound("flop") - self.object:set_velocity({ - x = (math.random()-0.5) * 2 * FLOP_HOR_SPEED, - y = FLOP_HEIGHT, - z = (math.random()-0.5) * 2 * FLOP_HOR_SPEED, - }) + self.object:set_velocity(vector_new((random() * 2 - 1) * FLOP_HOR_SPEED, FLOP_HEIGHT, (random() * 2 - 1) * FLOP_HOR_SPEED)) end end @@ -801,28 +649,22 @@ function mob_class:flop() return elseif self.state == "flop" then self.state = "stand" - self.object:set_acceleration(vector.zero()) + self.object:set_acceleration(vector_zero()) self:set_velocity(0) end end end -function mob_class:go_to_pos(b) +function mob_class:go_to_pos(b, speed) if not self then return end - local s=self.object:get_pos() - if not b then - --self.state = "stand" - return end - if vector.distance(b,s) < 1 then - --self:set_velocity(0) - return true - end - local v = { x = b.x - s.x, z = b.z - s.z } - local yaw = (atann(v.z / v.x) +math.pi/ 2) - self.rotate - if b.x > s.x then yaw = yaw +math.pi end - self.object:set_yaw(yaw) - self:set_velocity(self.follow_velocity) - self:set_animation("walk") + if not b then return end + local s = self.object:get_pos() + if vector_distance(b,s) < .4 then return true end + if b.y > s.y + 0.2 then self:do_jump() end + self:turn_in_direction(b.x - s.x, b.z - s.z, 2) + speed = speed or self.walk_velocity + self:set_velocity(speed) + self:set_animation(speed <= self.walk_velocity and "walk" or "run") end local check_herd_timer = 0 @@ -837,170 +679,122 @@ function mob_class:check_herd(dtime) check_herd_timer = 0 for _,o in pairs(minetest.get_objects_inside_radius(pos,self.view_range)) do local l = o:get_luaentity() - local p,y if l and l.is_mob and l.name == self.name then if self.horny and l.horny then - p = l.object:get_pos() + self:go_to_pos(l.object:get_pos()) else - y = o:get_yaw() - end - if p then - self:go_to_pos(p) - elseif y then - self:set_yaw(y) + self:set_yaw(o:get_yaw(), 8) end end end end function mob_class:teleport(target) - if self.do_teleport then - if self.do_teleport(self, target) == false then - return - end + if self.do_teleport then return self.do_teleport(self, target) end +end + +function mob_class:animate_walk_or_fly() + if self:flight_check() and self.animation and self.animation.fly_start and self.animation.fly_end then + self:set_animation("fly") + else + self:set_animation("walk") end end function mob_class:do_states_walk() local yaw = self.object:get_yaw() or 0 - local s = self.object:get_pos() - local lp = nil - -- is there something I need to avoid? - if (self.water_damage > 0 - and self.lava_damage > 0) - or self.breath_max ~= -1 then - lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"}) - elseif self.water_damage > 0 then - lp = minetest.find_node_near(s, 1, {"group:water"}) - elseif self.lava_damage > 0 then - lp = minetest.find_node_near(s, 1, {"group:lava"}) - elseif self.fire_damage > 0 then - lp = minetest.find_node_near(s, 1, {"group:fire"}) - end - - local is_in_danger = false - if lp then - -- If mob in or on dangerous block, look for land - if (self:is_node_dangerous(self.standing_in) or - self:is_node_dangerous(self.standing_on)) or (self:is_node_waterhazard(self.standing_in) or self:is_node_waterhazard(self.standing_on)) and (not self.fly) then - is_in_danger = true - - -- If mob in or on dangerous block, look for land - if is_in_danger then - -- Better way to find shore - copied from upstream - lp = minetest.find_nodes_in_area_under_air( - {x = s.x - 5, y = s.y - 0.5, z = s.z - 5}, - {x = s.x + 5, y = s.y + 1, z = s.z + 5}, - {"group:solid"}) - - lp = #lp > 0 and lp[math.random(#lp)] - - -- did we find land? - if lp then - - local vec = { - x = lp.x - s.x, - z = lp.z - s.z - } - - yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate - - - if lp.x > s.x then yaw = yaw +math.pi end - - -- look towards land and move in that direction - yaw = self:set_yaw( yaw, 6) - self:set_velocity(self.walk_velocity) - - end - end - - -- A danger is near but mob is not inside - else - - -- Randomly turn - if math.random(1, 100) <= 30 then - yaw = yaw + math.random() - 0.5 - yaw = self:set_yaw( yaw, 8) - end + -- If mob in or on dangerous block, look for land + if self:is_node_dangerous(self.standing_in) or self:is_node_waterhazard(self.standing_in) + or not self.fly and (self:is_node_dangerous(self.standing_on) or self:is_node_waterhazard(self.standing_on)) then + -- Better way to find shore - copied from upstream + local lp = minetest.find_nodes_in_area_under_air(vector_offset(s, -5, -0.5, -5), vector_offset(s, 5, 1, 5), {"group:solid"}) + if #lp == 0 then + local lp = minetest.find_nodes_in_area_under_air(vector_offset(s, -10, -0.5, -10), vector_offset(s, 10, 1, 10), {"group:solid"}) end - - yaw = self:set_yaw( yaw, 8) - - -- otherwise randomly turn - elseif math.random(1, 100) <= 30 then - yaw = yaw + math.random() - 0.5 - yaw = self:set_yaw( yaw, 8) - end - - -- stand for great fall or danger or fence in front - local cliff_or_danger = false - if is_in_danger then - cliff_or_danger = self:is_at_cliff_or_danger() - end - if self.facing_fence == true - or cliff_or_danger - or math.random(1, 100) <= 30 then - - self:set_velocity(0) - self.state = "stand" - self:set_animation( "stand") - local yaw = self.object:get_yaw() or 0 - yaw = self:set_yaw( yaw + 0.78, 8) - else - - self:set_velocity(self.walk_velocity) - - if self:flight_check() - and self.animation - and self.animation.fly_start - and self.animation.fly_end then - self:set_animation( "fly") - else - self:set_animation( "walk") + -- TODO: use node with smallest change in yaw instead of random? + lp = #lp > 0 and lp[random(#lp)] + -- did we find land? + if lp then + if logging then + minetest.log("action", "[mcl_mobs] "..self.name.." heading to land ".. tostring(minetest.get_node(lp).name or nil)) + end + -- look towards land and move in that direction + self:turn_in_direction(lp.x - s.x, lp.z - s.z, 8) + self:set_velocity(self.walk_velocity) + self:animate_walk_or_fly() + return end end + -- stop at fences or randomly + -- fences break villager pathfinding! if self.facing_fence == true or random() <= 0.3 then + if random() <= 0.3 then + self:stand() + return + end + -- facing wall? then turn + local facing_wall = false + local cbox = self.collisionbox + local dir_x = -sin(yaw - QUARTERPI) * (cbox[4] + 0.5) + local dir_z = cos(yaw - QUARTERPI) * (cbox[4] + 0.5) + local nodface = node_ok(vector_offset(s, dir_x, cbox[5] - cbox[2], dir_z)) + if minetest.registered_nodes[nodface.name] and minetest.registered_nodes[nodface.name].walkable == true then + dir_x = -sin(yaw + QUARTERPI) * (cbox[4] + 0.5) + dir_z = cos(yaw + QUARTERPI) * (cbox[4] + 0.5) + nodface = node_ok(vector_offset(s, dir_x, cbox[5] - cbox[2], dir_z)) + if minetest.registered_nodes[nodface.name] and minetest.registered_nodes[nodface.name].walkable == true then + facing_wall = true + end + end + if facing_wall then + if logging then + minetest.log("action", "[mcl_mobs] "..self.name.." facing a wall, turning.") + end + self:turn_by(TWOPI * (random() - 0.5), 6) + -- otherwise randomly turn + elseif random() <= 0.3 then + local home = self._home or self._bed + if home and random() < 0.1 then + self:turn_in_direction(home.x - s.x, home.z - s.z, 8) + else + self:turn_by(QUARTERPI * (random() - 0.5), 10) + end + end + self:set_velocity(self.walk_velocity) + self:animate_walk_or_fly() end function mob_class:do_states_stand(player_in_active_range) - local yaw = self.object:get_yaw() or 0 - - if math.random(1, 4) == 1 then - + if random() < 0.25 then local s = self.object:get_pos() - local objs = minetest.get_objects_inside_radius(s, 3) local lp - for n = 1, #objs do - if objs[n]:is_player() then - lp = objs[n]:get_pos() - break + if player_in_active_range and self.look_at_players then + local objs = minetest.get_objects_inside_radius(s, 3) + for n = 1, #objs do + if objs[n]:is_player() then + lp = objs[n]:get_pos() + break + end end end - -- look at any players nearby, otherwise turn randomly - if lp and self.look_at_players then - - local vec = { - x = lp.x - s.x, - z = lp.z - s.z - } - - yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate - - if lp.x > s.x then yaw = yaw +math.pi end + if lp then + self:turn_in_direction(lp.x - s.x, lp.z - s.z, 10) else - yaw = yaw + math.random() - 0.5 + local home = self._home or self._bed + if home and random() < 0.3 then + self:turn_in_direction(home.x - s.x, home.z - s.z, 8) + else + self:turn_by(HALFPI * (random() - 0.5), 10) + end end - - yaw = self:set_yaw( yaw, 8) end if self.order == "sit" then - self:set_animation( "sit") + self:set_animation("sit") self:set_velocity(0) else - self:set_animation( "stand") + self:set_animation("stand") self:set_velocity(0) end @@ -1011,86 +805,30 @@ function mob_class:do_states_stand(player_in_active_range) if player_in_active_range then if self.walk_chance ~= 0 and self.facing_fence ~= true - and math.random(1, 100) <= self.walk_chance - and self:is_at_cliff_or_danger() == false then - - self:set_velocity(self.walk_velocity) - self.state = "walk" - self:set_animation( "walk") + and random(1, 100) <= self.walk_chance then + if self:is_at_cliff_or_danger() then + self:turn_by(PI * (random() - 0.5), 10) + else + self:set_velocity(self.walk_velocity) + self.state = "walk" + self:set_animation( "walk") + end end end end end function mob_class:do_states_runaway() - local yaw = self.object:get_yaw() or 0 - self.runaway_timer = self.runaway_timer + 1 - -- stop after 5 seconds or when at cliff if self.runaway_timer > 5 or self:is_at_cliff_or_danger() then self.runaway_timer = 0 - self:set_velocity(0) - self.state = "stand" - self:set_animation( "stand") - local yaw = self.object:get_yaw() or 0 - yaw = self:set_yaw( yaw + 0.78, 8) + self:stand() + self:turn_by(PI * (random() + 0.5), 8) else self:set_velocity( self.run_velocity) self:set_animation( "run") end end - - - - - -function mob_class:check_smooth_rotation(dtime) - -- smooth rotation by ThomasMonroe314 - if self._turn_to then - self:set_yaw( self._turn_to, .1) - end - - if self.delay and self.delay > 0 then - - local yaw = self.object:get_yaw() or 0 - - if self.delay == 1 then - yaw = self.target_yaw - else - local dif = math.abs(yaw - self.target_yaw) - - if yaw > self.target_yaw then - - if dif > math.pi then - dif = 2 * math.pi - dif -- need to add - yaw = yaw + dif / self.delay - else - yaw = yaw - dif / self.delay -- need to subtract - end - - elseif yaw < self.target_yaw then - - if dif >math.pi then - dif = 2 * math.pi - dif - yaw = yaw - dif / self.delay -- need to subtract - else - yaw = yaw + dif / self.delay -- need to add - end - end - - if yaw > (math.pi * 2) then yaw = yaw - (math.pi * 2) end - if yaw < 0 then yaw = yaw + (math.pi * 2) end - end - - self.delay = self.delay - 1 - if self.shaking then - yaw = yaw + (math.random() * 2 - 1) * 5 * dtime - end - self.object:set_yaw(yaw) - --self:update_roll() - end - -- end rotation -end diff --git a/mods/ENTITIES/mcl_mobs/pathfinding.lua b/mods/ENTITIES/mcl_mobs/pathfinding.lua index ce706b190..3dc01d073 100644 --- a/mods/ENTITIES/mcl_mobs/pathfinding.lua +++ b/mods/ENTITIES/mcl_mobs/pathfinding.lua @@ -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 diff --git a/mods/ENTITIES/mcl_mobs/physics.lua b/mods/ENTITIES/mcl_mobs/physics.lua index ea470d722..727c6f98e 100644 --- a/mods/ENTITIES/mcl_mobs/physics.lua +++ b/mods/ENTITIES/mcl_mobs/physics.lua @@ -6,6 +6,18 @@ local ENTITY_CRAMMING_MAX = 24 local CRAMMING_DAMAGE = 3 local DEATH_DELAY = 0.5 local DEFAULT_FALL_SPEED = -9.81*1.5 +local PI = math.pi +local HALFPI = 0.5 * PI +local TWOPI = 2 * PI -- aka tau, but not very common +local random = math.random +local min = math.min +local max = math.max +local floor = math.floor +local abs = math.abs +local atan2 = math.atan2 +local sin = math.sin +local cos = math.cos +local node_ok = mcl_mobs.node_ok local PATHFINDING = "gowp" local mobs_debug = minetest.settings:get_bool("mobs_debug", false) @@ -13,20 +25,6 @@ local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false local mob_active_range = tonumber(minetest.settings:get("mcl_mob_active_range")) or 48 local show_health = false --- get node but use fallback for nil or unknown -local node_ok = function(pos, fallback) - - fallback = fallback or mcl_mobs.fallback_node - - local node = minetest.get_node_or_nil(pos) - - if node and minetest.registered_nodes[node.name] then - return node - end - - return minetest.registered_nodes[fallback] -end - -- check if within physical map limits (-30911 to 30927) local function within_limits(pos, radius) local wmin, wmax = -30912, 30928 @@ -56,9 +54,7 @@ end -- Return true if object is in view_range function mob_class:object_in_range(object) - if not object then - return false - end + if not object then return false end local factor -- Apply view range reduction for special player armor if object:is_player() then @@ -110,24 +106,21 @@ function mob_class:item_drop(cooked, looting_level) local num = 0 local do_common_looting = (looting_level > 0 and looting_type == "common") - if math.random() < chance then - num = math.random(dropdef.min or 1, dropdef.max or 1) + if random() < chance then + num = random(dropdef.min or 1, dropdef.max or 1) elseif not dropdef.looting_ignore_chance then do_common_looting = false end if do_common_looting then - num = num + math.floor(math.random(0, looting_level) + 0.5) + num = num + floor(random(0, looting_level) + 0.5) end if num > 0 then item = dropdef.name if cooked then - - local output = minetest.get_craft_result({ - method = "cooking", width = 1, items = {item}}) - + local output = minetest.get_craft_result({method = "cooking", width = 1, items = {item}}) if output and output.item and not output.item:is_empty() then item = output.item:get_name() end @@ -135,17 +128,12 @@ function mob_class:item_drop(cooked, looting_level) for x = 1, num do obj = minetest.add_item(pos, ItemStack(item .. " " .. 1)) - end - if obj and obj:get_luaentity() then - - obj:set_velocity({ - x = math.random(-10, 10) / 9, - y = 6, - z = math.random(-10, 10) / 9, - }) - elseif obj then - obj:remove() -- item does not exist + if obj and obj:get_luaentity() then + obj:set_velocity(vector.new((random() - 0.5) * 1.5, 6, (random() - 0.5) * 1.5)) + elseif obj then + obj:remove() -- item does not exist + end end end end @@ -156,32 +144,29 @@ end -- collision function borrowed amended from jordan4ibanez open_ai mod function mob_class:collision() local pos = self.object:get_pos() - if not pos then return {0,0} end + if not pos then return 0,0 end local vel = self.object:get_velocity() - local x = 0 - local z = 0 + local x, z = 0, 0 local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5 for _,object in pairs(minetest.get_objects_inside_radius(pos, width)) do - local ent = object:get_luaentity() if object:is_player() or (ent and ent.is_mob and object ~= self.object) then - if object:is_player() and mcl_burning.is_burning(self.object) then mcl_burning.set_on_fire(object, 4) end local pos2 = object:get_pos() - local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z} - local force = (width + 0.5) - vector.distance( - {x = pos.x, y = 0, z = pos.z}, - {x = pos2.x, y = 0, z = pos2.z}) - - x = x + (vec.x * force) - z = z + (vec.z * force) + local vx, vz = pos.x - pos2.x, pos.z - pos2.z + local force = width - (vx*vx+vz*vz)^0.5 + if force > 0 then + force = force * force * (object:is_player() and 2 or 1) -- players push more + -- minetest.log("mob push force "..force.." "..tostring(self.name).." by "..tostring(ent and ent.name or "player")) + x = x + vx * force + z = z + vz * force + end end end - - return({x,z}) + return x, z end function mob_class:check_death_and_slow_mob() @@ -192,196 +177,113 @@ function mob_class:check_death_and_slow_mob() local v = self.object:get_velocity() if v then --diffuse object velocity - self.object:set_velocity({x = v.x*d, y = v.y, z = v.z*d}) + self.object:set_velocity(vector.new(v.x*d, v.y, v.z*d)) end return dying end -- move mob in facing direction function mob_class:set_velocity(v) - if not v then return end - - local c_x, c_y = 0, 0 - + local c_x, c_z = 0, 0 -- can mob be pushed, if so calculate direction if self.pushable then - c_x, c_y = unpack(self:collision()) + c_x, c_z = self:collision() end - - -- halt mob if it has been ordered to stay - if self.order == "stand" or self.order == "sit" then - self.acc = vector.zero() - return - end - - local yaw = (self.object:get_yaw() or 0) + self.rotate - local vv = self.object:get_velocity() - - if vv and yaw then - self.acc = vector.new(((math.sin(yaw) * -v) + c_x) * .4, 0, ((math.cos(yaw) * v) + c_y) * .4) + if v > 0 then + local yaw = (self.object:get_yaw() or 0) + self.rotate + local x = ((-sin(yaw) * v) + c_x) * .4 + local z = (( cos(yaw) * v) + c_z) * .4 + if not self.acc then + self.acc = vector.new(x, 0, z) + else + self.acc.x, self.acc.y, self.acc.z = x, 0, z + end + else -- allow standing mobs to be pushed + if not self.acc then + self.acc = vector.new(c_x * .2, 0, c_z * .2) + else + self.acc.x, self.acc.y, self.acc.z = c_x * .2, 0, c_z * .2 + end end end --- calculate mob velocity +-- calculate mob velocity (2d) function mob_class:get_velocity() local v = self.object:get_velocity() - if v then - return (v.x * v.x + v.z * v.z) ^ 0.5 - end - - return 0 + if not v then return 0 end + return (v.x*v.x + v.z*v.z)^0.5 end function mob_class:update_roll() local is_Fleckenstein = self.nametag == "Fleckenstein" - local was_Fleckenstein = false + if not is_Fleckenstein and not self.is_Fleckenstein then return end local rot = self.object:get_rotation() - rot.z = is_Fleckenstein and pi or 0 + rot.z = is_Fleckenstein and PI or 0 self.object:set_rotation(rot) - local cbox = table.copy(self.collisionbox) - local acbox = self.object:get_properties().collisionbox - - if math.abs(cbox[2] - acbox[2]) > 0.1 then - was_Fleckenstein = true - end - - if is_Fleckenstein ~= was_Fleckenstein then + if is_Fleckenstein ~= self.is_Fleckenstein then local pos = self.object:get_pos() - pos.y = pos.y + (acbox[2] + acbox[5]) + local cbox = is_Fleckenstein and table.copy(self.collisionbox) or self.object:get_properties().collisionbox + pos.y = pos.y + (cbox[2] + cbox[5]) + cbox[2], cbox[5] = -cbox[5], -cbox[2] + -- This leads to child mobs having the wrong collisionbox + -- and seeing as it seems to be nothing but an easter egg + -- i've put it inside the if. Which just makes it be upside + -- down lol. + self.object:set_properties({collisionbox = cbox}) self.object:set_pos(pos) end - - if is_Fleckenstein then - cbox[2], cbox[5] = -cbox[5], -cbox[2] - self.object:set_properties({collisionbox = cbox}) - -- This leads to child mobs having the wrong collisionbox - -- and seeing as it seems to be nothing but an easter egg - -- i've put it inside the if. Which just makes it be upside - -- down lol. - end - + self.is_Fleckenstein = is_Fleckenstein end -local function shortest_term_of_yaw_rotation(self, rot_origin, rot_target, nums) - - if not rot_origin or not rot_target then - return - end - - rot_origin = math.deg(rot_origin) - rot_target = math.deg(rot_target) - - if rot_origin < rot_target then - if math.abs(rot_origin-rot_target)<180 then - if nums then - return rot_target-rot_origin - else - return 1 - end - else - if nums then - return -(rot_origin-(rot_target-360)) - else - return -1 - end - end - else - if math.abs(rot_origin-rot_target)<180 then - if nums then - return rot_target-rot_origin - else - return -1 - end - else - if nums then - return (rot_target-(rot_origin-360)) - else - return 1 - end - end - end - +-- Relative turn, primarily for random turning +-- @param dtime deprecated: ignored now, because of smooth rotations +function mob_class:turn_by(angle, delay, dtime) + return self:set_yaw((self.object:get_yaw() or 0) + angle, delay, dtime) +end +-- Turn into a direction (e.g., to the player, or away) +-- @param dtime deprecated: ignored now, because of smooth rotations +function mob_class:turn_in_direction(dx, dz, delay, dtime) + if abs(dx) == 0 and abs(dz) == 0 then return self.object:get_yaw() + self.rotate end + return self:set_yaw(-atan2(dx, dz) - self.rotate, delay, dtime) + self.rotate end - - - -- set and return valid yaw +-- @param dtime deprecated: ignored now, because of smooth rotations function mob_class:set_yaw(yaw, delay, dtime) if self.noyaw then return end - + if self._kb_turn then return yaw end -- knockback in effect if not self.object:get_yaw() or not self.object:get_pos() then return end - - if self.state ~= PATHFINDING then - self._turn_to = yaw - end - - --mcl_log("Yaw is: \t\t" .. tostring(math.deg(yaw))) - --mcl_log("self.object:get_yaw() is: \t" .. tostring(math.deg(self.object:get_yaw()))) - - --clamp our yaw to a 360 range - if math.deg(self.object:get_yaw()) > 360 then - self.object:set_yaw(math.rad(0)) - elseif math.deg(self.object:get_yaw()) < 0 then - self.object:set_yaw(math.rad(360)) - end - - if math.deg(yaw) > 360 then - yaw=math.rad(math.deg(yaw)%360) - elseif math.deg(yaw) < 0 then - yaw=math.rad(((360*5)-math.deg(yaw))%360) - end - - --calculate the shortest way to turn to find our target - local target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), yaw, false) - local target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), yaw, true) - - --turn in the shortest path possible toward our target. if we are attacking, don't dance. - if (math.abs(target_shortest_path) > 50 and not self._kb_turn) and (self.attack and self.attack:get_pos() or self.following and self.following:get_pos()) then - if self.following then - target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.following:get_pos())), true) - target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.following:get_pos())), false) - else - target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.attack:get_pos())), true) - target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.attack:get_pos())), false) - end - end - - local ddtime = 0.05 --set_tick_rate - - if dtime then - ddtime = dtime - end - - if math.abs(target_shortest_path_nums) > 10 then - self.object:set_yaw(self.object:get_yaw()+(target_shortest_path*(3.6*ddtime))) - if validate_vector(self.acc) then - self.acc=vector.rotate_around_axis(self.acc,vector.new(0,1,0), target_shortest_path*(3.6*ddtime)) - end - end - - delay = delay or 0 - - yaw = self.object:get_yaw() - - if delay == 0 then - if self.shaking and dtime then - yaw = yaw + (math.random() * 2 - 1) * 5 * dtime - end - --self:update_roll() - return yaw - end - - self.target_yaw = yaw - self.delay = delay - + self.delay = delay or 0 + self.target_yaw = yaw % TWOPI return self.target_yaw end --- global function to set mob yaw -function mcl_mobs.yaw(self, yaw, delay, dtime) - return mob_class.set_yaw(self, yaw, delay, dtime) +-- improved smooth rotation +function mob_class:check_smooth_rotation(dtime) + if not self.target_yaw then return end + + local delay = self.delay + local initial_yaw = self.object:get_yaw() or 0 + local yaw -- resulting yaw for this tick + if delay and delay > 1 then + local dif = (self.target_yaw - initial_yaw + PI) % TWOPI - PI + yaw = (initial_yaw + dif / delay) % TWOPI + self.delay = delay - 1 + else + yaw = self.target_yaw + end + + if self.shaking then + yaw = yaw + (random() * 2 - 1) / 72 * dtime + end + --[[ needed? if self.acc then + local change = yaw - initial_yaw + local si, co = sin(change), cos(change) + self.acc.x, self.acc.y = co * self.acc.x - si * self.acc.y, si * self.acc.x + co * self.acc.y + end ]]-- + self.object:set_yaw(yaw) + self:update_roll() end -- are we flying in what we are suppose to? (taikedz) @@ -489,7 +391,7 @@ function mob_class:check_for_death(cause, cmi_cause) if ((not self.child) or self.type ~= "animal") and (minetest.get_us_time() - self.xp_timestamp <= math.huge) then local pos = self.object:get_pos() - local xp_amount = math.random(self.xp_min, self.xp_max) + local xp_amount = random(self.xp_min, self.xp_max) if not mcl_sculk.handle_death(pos, xp_amount) then --minetest.log("Xp not thrown") @@ -562,7 +464,7 @@ function mob_class:check_for_death(cause, cmi_cause) elseif self.animation and self.animation.die_start and self.animation.die_end then local frames = self.animation.die_end - self.animation.die_start local speed = self.animation.die_speed or 15 - length = math.max(frames / speed, 0) + DEATH_DELAY + length = max(frames / speed, 0) + DEATH_DELAY self:set_animation( "die") else length = 1 + DEATH_DELAY @@ -662,7 +564,7 @@ function mob_class:do_env_damage() -- what is mob standing in? pos.y = pos.y + y_level + 0.25 -- foot level - local pos2 = {x=pos.x, y=pos.y-1, z=pos.z} + local pos2 = vector.new(pos.x, pos.y-1, pos.z) self.standing_in = node_ok(pos, "air").name self.standing_on = node_ok(pos2, "air").name @@ -671,7 +573,7 @@ function mob_class:do_env_damage() -- don't fall when on ignore, just stand still if self.standing_in == "ignore" then - self.object:set_velocity({x = 0, y = 0, z = 0}) + self.object:set_velocity(vector.zero()) -- wither rose effect elseif self.standing_in == "mcl_flowers:wither_rose" then mcl_potions.give_effect_by_level("withering", self.object, 2, 2) @@ -791,7 +693,7 @@ function mob_class:do_env_damage() end if drowning then - self.breath = math.max(0, self.breath - 1) + self.breath = max(0, self.breath - 1) mcl_mobs.effect(pos, 2, "bubble.png", nil, nil, 1, nil) if self.breath <= 0 then local dmg @@ -808,7 +710,7 @@ function mob_class:do_env_damage() return true end else - self.breath = math.min(self.breath_max, self.breath + 1) + self.breath = min(self.breath_max, self.breath + 1) end end @@ -874,7 +776,7 @@ end function mob_class:damage_mob(reason,damage) if not self.health then return end - damage = math.floor(damage) + damage = floor(damage) if damage > 0 then self.health = self.health - damage @@ -920,57 +822,45 @@ end -- falling and fall damage -- returns true if mob died function mob_class:falling(pos, moveresult) - if self.fly and self.state ~= "die" then - return - end - + if self.fly and self.state ~= "die" then return end if not self.fall_speed then self.fall_speed = DEFAULT_FALL_SPEED end + -- Gravity + local v = self.object:get_velocity() + if v then + if v.y > 0 or (v.y <= 0 and v.y > self.fall_speed) then + -- fall downwards at set speed + if moveresult and moveresult.touching_ground then + -- when touching ground, retain a minimal gravity to keep the touching_ground flag + -- but also to not get upwards acceleration with large dtime when on bouncy ground + self.object:set_acceleration(vector.new(0, self.fall_speed * 0.01, 0)) + else + self.object:set_acceleration(vector.new(0, self.fall_speed, 0)) + end + else + -- stop accelerating once max fall speed hit + self.object:set_acceleration(vector.zero()) + end + end + if mcl_portals ~= nil then if mcl_portals.nether_portal_cooloff(self.object) then return false -- mob has teleported through Nether portal - it's 99% not falling end end - -- floating in water (or falling) - local v = self.object:get_velocity() - if v then - local new_acceleration - - if v.y > 0 then - -- apply gravity when moving up - new_acceleration = vector.new(0, DEFAULT_FALL_SPEED, 0) - elseif v.y <= 0 and v.y > self.fall_speed then - -- fall downwards at set speed - if moveresult and moveresult.touching_ground then - -- when touching ground, retain a minimal gravity to keep the touching_ground flag - -- but also to not get upwards acceleration with large dtime when on bouncy ground - new_acceleration = vector.new(0, self.fall_speed * 0.01, 0) - else - new_acceleration = vector.new(0, self.fall_speed, 0) - end - else - -- stop accelerating once max fall speed hit - new_acceleration =vector.zero() - end - - self.object:set_acceleration(new_acceleration) - end - - local acc = self.object:get_acceleration() - local registered_node = minetest.registered_nodes[node_ok(pos).name] if registered_node.groups.lava then - if acc and self.floats_on_lava == 1 then - self.object:set_acceleration(vector.new(0, -self.fall_speed / (math.max(1, v.y) ^ 2), 0)) + if self.floats_on_lava == 1 then + self.object:set_acceleration(vector.new(0, -self.fall_speed / max(1, v.y^2), 0)) end end -- in water then float up if registered_node.groups.water then - if acc and self.floats == 1 and minetest.registered_nodes[node_ok(vector.offset(pos,0,self.collisionbox[5] -0.25,0)).name].groups.water then - self.object:set_acceleration(vector.new(0, -self.fall_speed / (math.max(1, v.y) ^ 2), 0)) + if self.floats == 1 and minetest.registered_nodes[node_ok(vector.offset(pos,0,self.collisionbox[5] -0.25,0)).name].groups.water then + self.object:set_acceleration(vector.new(0, -self.fall_speed / max(1, v.y^2), 0)) end else -- fall damage onto solid ground @@ -994,13 +884,9 @@ end function mob_class:check_water_flow() -- Add water flowing for mobs from mcl_item_entity - local p, node, nn, def - p = self.object:get_pos() - node = minetest.get_node_or_nil(p) - if node then - nn = node.name - def = minetest.registered_nodes[nn] - end + local p = self.object:get_pos() + local node = minetest.get_node_or_nil(p) + local def = node and minetest.registered_nodes[node.name] -- Move item around on flowing liquids if def and def.liquidtype == "flowing" then @@ -1015,14 +901,12 @@ function mob_class:check_water_flow() local f = 1.39 -- Set new item moving speed into the direciton of the liquid local newv = vector.multiply(vec, f) - self.object:set_acceleration({x = 0, y = 0, z = 0}) - self.object:set_velocity({x = newv.x, y = -0.22, z = newv.z}) + self.object:set_acceleration(vector.zero()) + self.object:set_velocity(vector.new(newv.x, -0.22, newv.z)) self.physical_state = true self._flowing = true - self.object:set_properties({ - physical = true - }) + self.object:set_properties({ physical = true }) return end elseif self._flowing == true then @@ -1036,7 +920,7 @@ function mob_class:check_dying() if ((self.state and self.state=="die") or self:check_for_death()) and not self.animation.die_end then local rot = self.object:get_rotation() if rot then - rot.z = ((math.pi/2-rot.z)*.2)+rot.z + rot.z = ((HALFPI - rot.z) * .2) + rot.z self.object:set_rotation(rot) end return true diff --git a/mods/ENTITIES/mobs_mc/pillager.lua b/mods/ENTITIES/mobs_mc/pillager.lua index b73577323..4e4af92ac 100644 --- a/mods/ENTITIES/mobs_mc/pillager.lua +++ b/mods/ENTITIES/mobs_mc/pillager.lua @@ -43,10 +43,10 @@ pillager = { arrow = "mcl_bows:arrow_entity", attack_type = "dogshoot", -- Alternate punching/shooting attack_npcs = true, - reach = 0, -- Punching max distance - damage = 0, -- Punching damage + reach = 2, -- Punching max distance + damage = 2, -- Punching damage dogshoot_switch = 1, -- Start of shooting - dogshoot_count_max = 5, -- Max time spent shooting (standing) + dogshoot_count_max = 4, -- Max time spent shooting (standing) dogshoot_count2_max = 1, -- Max time spent punching (running) sounds = { random = "mobs_mc_pillager_grunt2", diff --git a/mods/ENTITIES/mobs_mc/shulker.lua b/mods/ENTITIES/mobs_mc/shulker.lua index 78959b717..9280ef982 100644 --- a/mods/ENTITIES/mobs_mc/shulker.lua +++ b/mods/ENTITIES/mobs_mc/shulker.lua @@ -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") diff --git a/mods/ENTITIES/mobs_mc/villager.lua b/mods/ENTITIES/mobs_mc/villager.lua index 96bd8f971..712d5a035 100644 --- a/mods/ENTITIES/mobs_mc/villager.lua +++ b/mods/ENTITIES/mobs_mc/villager.lua @@ -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