diff --git a/mods/ENTITIES/mcl_mobs/api.lua b/mods/ENTITIES/mcl_mobs/api.lua index 4fb89dfa6..a898ed979 100644 --- a/mods/ENTITIES/mcl_mobs/api.lua +++ b/mods/ENTITIES/mcl_mobs/api.lua @@ -1,25 +1,15 @@ +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: MineClone 2 Edition (MRM) - -mcl_mobs = {} - local MAX_MOB_NAME_LENGTH = 30 -local HORNY_TIME = 30 -local HORNY_AGAIN_TIME = 300 -local CHILD_GROW_TIME = 60*20 -local DEATH_DELAY = 0.5 local DEFAULT_FALL_SPEED = -9.81*1.5 -local FLOP_HEIGHT = 6 -local FLOP_HOR_SPEED = 1.5 -local ENTITY_CRAMMING_MAX = 24 -local CRAMMING_DAMAGE = 3 local PATHFINDING = "gowp" -- Localize local S = minetest.get_translator("mcl_mobs") -local mob_active_range = tonumber(minetest.settings:get("mcl_mob_active_range")) or 48 - local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager",false) local function mcl_log (message) if LOGGING_ON then @@ -27,45 +17,14 @@ local function mcl_log (message) end end -local function shortest_term_of_yaw_rotatoin(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 math.abs(rot_target - rot_origin) < 180 then - return rot_target - rot_origin - else - if (rot_target - rot_origin) > 0 then - return 360-(rot_target - rot_origin) - else - return (rot_target - rot_origin)+360 - end - end -end - -- Invisibility mod check mcl_mobs.invis = {} -- localize math functions -local pi = math.pi -local sin = math.sin -local cos = math.cos -local abs = math.abs -local min = math.min -local max = math.max local atann = math.atan -local random = math.random -local floor = math.floor -local ceil = math.ceil -local atan = function(x) +local function atan(x) if not x or x ~= x then return 0 else @@ -73,20 +32,10 @@ local atan = function(x) end end --- Load settings -local damage_enabled = minetest.settings:get_bool("enable_damage") -local disable_blood = minetest.settings:get_bool("mobs_disable_blood") -local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false +local remove_far = true local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false -local player_transfer_distance = tonumber(minetest.settings:get("player_transfer_distance")) or 128 -if player_transfer_distance == 0 then player_transfer_distance = math.huge end -local remove_far = true -local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0 -local show_health = false -local old_spawn_icons = minetest.settings:get_bool("mcl_old_spawn_icons",false) --- Shows helpful debug info above each mob -local mobs_debug = minetest.settings:get_bool("mobs_debug", false) +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",true) -- Peaceful mode message so players will know there are no monsters @@ -97,713 +46,23 @@ if minetest.settings:get_bool("only_peaceful_mobs", false) then end) end -local active_particlespawners = {} - - -local function dir_to_pitch(dir) - --local dir2 = vector.normalize(dir) - local xz = math.abs(dir.x) + math.abs(dir.z) - return -math.atan2(-dir.y, xz) +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 --- pathfinding settings -local enable_pathfinding = true -local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching -local stuck_path_timeout = 10 -- how long will mob follow path before giving up - --- default nodes -local node_ice = "mcl_core:ice" -local node_snowblock = "mcl_core:snowblock" -local node_snow = "mcl_core:snow" -mcl_mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt" - -minetest.register_chatcommand("clearmobs",{ - privs={maphack=true}, - params = "||", - description=S("Removes all spawned mobs except nametagged and tamed ones. all removes all mobs, nametagged only nametagged ones and with the range paramter all mobs in a distance of the current player are removed."), - func=function(n,param) - local p = minetest.get_player_by_name(n) - local num=tonumber(param) - for _,o in pairs(minetest.luaentities) do - if o.is_mob then - if param == "all" or - ( param == "nametagged" and o.nametag ) or - ( param == "" and ( not o.nametag or o.nametag == "" ) and not o.tamed ) or - ( num and num > 0 and vector.distance(p:get_pos(),o.object:get_pos()) <= num ) then - o.object:remove() - end - end - end -end}) - -local function remove_particlespawners(pn,self) - if not active_particlespawners[pn] then return end - if not active_particlespawners[pn][self.object] then return end - for k,v in pairs(active_particlespawners[pn][self.object]) do - minetest.delete_particlespawner(v) - end -end - -local function add_particlespawners(pn,self) - if not active_particlespawners[pn] then active_particlespawners[pn] = {} end - if not active_particlespawners[pn][self.object] then active_particlespawners[pn][self.object] = {} end - for _,ps in pairs(self.particlespawners) do - ps.attached = self.object - ps.playername = pn - table.insert(active_particlespawners[pn][self.object],minetest.add_particlespawner(ps)) - end -end - -local function particlespawner_check(self,dtime) - if not self.particlespawners then return end - --minetest.log(dump(active_particlespawners)) - if self._particle_timer and self._particle_timer >= 1 then - self._particle_timer = 0 - local players = {} - for _,player in pairs(minetest.get_connected_players()) do - local pn = player:get_player_name() - table.insert(players,pn) - if not active_particlespawners[pn] then - active_particlespawners[pn] = {} end - - local dst = vector.distance(player:get_pos(),self.object:get_pos()) - if dst < player_transfer_distance and not active_particlespawners[pn][self.object] then - add_particlespawners(pn,self) - elseif dst >= player_transfer_distance and active_particlespawners[pn][self.object] then - remove_particlespawners(pn,self) - end - end - elseif not self._particle_timer then - self._particle_timer = 0 - end - self._particle_timer = self._particle_timer + dtime -end - -minetest.register_on_leaveplayer(function(player) - local pn = player:get_player_name() - if not active_particlespawners[pn] then return end - for _,m in pairs(active_particlespawners[pn]) do - for k,v in pairs(m) do - minetest.delete_particlespawner(v) - end - end - active_particlespawners[pn] = nil -end) - -----For Water Flowing: -local enable_physics = function(object, luaentity, ignore_check) - if luaentity.physical_state == false or ignore_check == true then - luaentity.physical_state = true - object:set_properties({ - physical = true - }) - object:set_velocity({x=0,y=0,z=0}) - object:set_acceleration({x=0,y=DEFAULT_FALL_SPEED,z=0}) - end -end - -local disable_physics = function(object, luaentity, ignore_check, reset_movement) - if luaentity.physical_state == true or ignore_check == true then - luaentity.physical_state = false - object:set_properties({ - physical = false - }) - if reset_movement ~= false then - object:set_velocity({x=0,y=0,z=0}) - object:set_acceleration({x=0,y=0,z=0}) - end - end -end - -local function player_in_active_range(self) - for _,p in pairs(minetest.get_connected_players()) do - if vector.distance(self.object:get_pos(),p:get_pos()) <= mob_active_range then return true end - -- slightly larger than the mc 32 since mobs spawn on that circle and easily stand still immediately right after spawning. - end -end - - --- play sound -local mob_sound = function(self, soundname, is_opinion, fixed_pitch) - - local soundinfo - if self.sounds_child and self.child then - soundinfo = self.sounds_child - elseif self.sounds then - soundinfo = self.sounds - end - if not soundinfo then - return - end - local sound = soundinfo[soundname] - if sound then - if is_opinion and self.opinion_sound_cooloff > 0 then - return - end - local pitch - if not fixed_pitch then - local base_pitch = soundinfo.base_pitch - if not base_pitch then - base_pitch = 1 - end - if self.child and (not self.sounds_child) then - -- Children have higher pitch - pitch = base_pitch * 1.5 - else - pitch = base_pitch - end - -- randomize the pitch a bit - pitch = pitch + random(-10, 10) * 0.005 - end - minetest.sound_play(sound, { - object = self.object, - gain = 1.0, - max_hear_distance = self.sounds.distance, - pitch = pitch, - }, true) - self.opinion_sound_cooloff = 1 - end -end - --- Return true if object is in view_range -local function object_in_range(self, object) - if not object then - return false - end - local factor - -- Apply view range reduction for special player armor - if object:is_player() then - local factors = mcl_armor.player_view_range_factors[object] - factor = factors and factors[self.name] - end - -- Distance check - local dist - if factor and factor == 0 then - return false - elseif factor then - dist = self.view_range * factor - else - dist = self.view_range - end - - local p1, p2 = self.object:get_pos(), object:get_pos() - return p1 and p2 and (vector.distance(p1, p2) <= dist) -end - --- attack player/mob -local do_attack = function(self, player) - - if self.state == "attack" or self.state == "die" then - return - end - - self.attack = player - self.state = "attack" - - -- TODO: Implement war_cry sound without being annoying - --if random(0, 100) < 90 then - --mob_sound(self, "war_cry", true) - --end -end - - --- collision function borrowed amended from jordan4ibanez open_ai mod -local collision = function(self) - - local pos = self.object:get_pos() - if not pos then return {0,0} end - local vel = self.object:get_velocity() - local x = 0 - local z = 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) - end - end - - return({x,z}) -end - --- move mob in facing direction -local set_velocity = function(self, v) - - local c_x, c_y = 0, 0 - - -- can mob be pushed, if so calculate direction - if self.pushable then - c_x, c_y = unpack(collision(self)) - end - - -- halt mob if it has been ordered to stay - if self.order == "stand" or self.order == "sit" then - self.acc=vector.new(0,0,0) - return - end - - local yaw = (self.object:get_yaw() or 0) + self.rotate - local vv = self.object:get_velocity() - if vv then - self.acc={ - x = ((sin(yaw) * -v) + c_x)*.27, - y = 0, - z = ((cos(yaw) * v) + c_y)*.27, - } - end -end - - - --- calculate mob velocity -local get_velocity = function(self) - - local v = self.object:get_velocity() - if v then - return (v.x * v.x + v.z * v.z) ^ 0.5 - end - - return 0 -end - -local function update_roll(self) - local is_Fleckenstein = self.nametag == "Fleckenstein" - local was_Fleckenstein = false - - local rot = self.object:get_rotation() - 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 abs(cbox[2] - acbox[2]) > 0.1 then - was_Fleckenstein = true - end - - if is_Fleckenstein ~= was_Fleckenstein then - local pos = self.object:get_pos() - pos.y = pos.y + (acbox[2] + acbox[5]) - 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 - -end - --- set and return valid yaw - - -local set_yaw = function(self, yaw, delay, dtime) - if self.noyaw 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(1)) - elseif math.deg(self.object:get_yaw()) < 0 then - self.object:set_yaw(math.rad(359)) - end - - --calculate the shortest way to turn to find our target - local target_shortest_path = shortest_term_of_yaw_rotatoin(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_rotatoin(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.following:get_pos())), true) - else - target_shortest_path = shortest_term_of_yaw_rotatoin(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.attack:get_pos())), true) - end - end - - local ddtime = 0.05 --set_tick_rate - - if dtime then - ddtime = dtime - end - - if math.abs(target_shortest_path) > 280*ddtime then - if target_shortest_path > 0 then - self.object:set_yaw(self.object:get_yaw()+3.6*ddtime) - if self.acc then - self.acc=vector.rotate_around_axis(self.acc,vector.new(0,1,0), 3.6*ddtime) - end - else - self.object:set_yaw(self.object:get_yaw()-3.6*ddtime) - if self.acc then - self.acc=vector.rotate_around_axis(self.acc,vector.new(0,1,0), -3.6*ddtime) - end - end - end - - delay = delay or 0 - - yaw = self.object:get_yaw() - - if delay == 0 then - if self.shaking and dtime then - yaw = yaw + (random() * 2 - 1) * 5 * dtime - end - update_roll(self) - return yaw - end - - self.target_yaw = yaw - self.delay = delay - - return self.target_yaw -end - --- global function to set mob yaw -function mcl_mobs:yaw(self, yaw, delay, dtime) - set_yaw(self, yaw, delay, dtime) -end - -local add_texture_mod = function(self, mod) - local full_mod = "" - local already_added = false - for i=1, #self.texture_mods do - if mod == self.texture_mods[i] then - already_added = true - end - full_mod = full_mod .. self.texture_mods[i] - end - if not already_added then - full_mod = full_mod .. mod - table.insert(self.texture_mods, mod) - end - self.object:set_texture_mod(full_mod) -end -local remove_texture_mod = function(self, mod) - local full_mod = "" - local remove = {} - for i=1, #self.texture_mods do - if self.texture_mods[i] ~= mod then - full_mod = full_mod .. self.texture_mods[i] - else - table.insert(remove, i) - end - end - for i=#remove, 1 do - table.remove(self.texture_mods, remove[i]) - end - self.object:set_texture_mod(full_mod) -end - --- are we flying in what we are suppose to? (taikedz) -local flight_check = function(self) - - local nod = self.standing_in - local def = minetest.registered_nodes[nod] - - if not def then return false end -- nil check - - local fly_in - if type(self.fly_in) == "string" then - fly_in = { self.fly_in } - elseif type(self.fly_in) == "table" then - fly_in = self.fly_in - else - return false - end - - for _,checknode in pairs(fly_in) do - if nod == checknode or nod == "ignore" then - return true - end - end - - return false -end - --- set defined animation -local set_animation = function(self, anim, fixed_frame) - if not self.animation or not anim then - return - end - if self.state == "die" and anim ~= "die" and anim ~= "stand" then - return - end - - if self.jockey then - anim = "jockey" - end - - - if flight_check(self) and self.fly and anim == "walk" then anim = "fly" end - - self._current_animation = self._current_animation or "" - - if (anim == self._current_animation - or not self.animation[anim .. "_start"] - or not self.animation[anim .. "_end"]) and self.state ~= "die" then - return - end - - 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 - if a_start and a_end then - self.object:set_animation({ - x = a_start, - y = a_end}, - self.animation[anim .. "_speed"] or self.animation.speed_normal or 15, - 0, self.animation[anim .. "_loop"] ~= false) - end -end - - --- above function exported for mount.lua -function mcl_mobs:set_animation(self, anim) - set_animation(self, anim) -end - --- Returns true is node can deal damage to self -local is_node_dangerous = function(self, 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 -end - - --- Returns true if node is a water hazard -local is_node_waterhazard = function(self, 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 -end - - --- check line of sight (BrunoMine) -local line_of_sight = function(self, pos1, pos2, stepsize) - - stepsize = stepsize or 1 - - local s, pos = minetest.line_of_sight(pos1, pos2, stepsize) - - -- normal walking and flying mobs can see you through air - if s == true then - return true - end - - -- New pos1 to be analyzed - local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z} - - local r, pos = minetest.line_of_sight(npos1, pos2, stepsize) - - -- Checks the return - if r == true then return true end - - -- Nodename found - local nn = minetest.get_node(pos).name - - -- Target Distance (td) to travel - 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 - - -- Check if you can still move forward - if td < ad + stepsize then - return true -- Reached the target - end - - -- Moves the analyzed pos - 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 - npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z - - -- NaN checks - if d == 0 - or npos1.x ~= npos1.x - or npos1.y ~= npos1.y - or npos1.z ~= npos1.z then - return false - end - - ad = ad + stepsize - - -- scan again - r, pos = minetest.line_of_sight(npos1, pos2, stepsize) - - if r == true then return true end - - -- New Nodename found - nn = minetest.get_node(pos).name - - end - - return false -end - --- custom particle effects -local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow, go_down) - - radius = radius or 2 - min_size = min_size or 0.5 - max_size = max_size or 1 - gravity = gravity or DEFAULT_FALL_SPEED - glow = glow or 0 - go_down = go_down or false - - local ym - if go_down then - ym = 0 - else - ym = -radius - end - - minetest.add_particlespawner({ - amount = amount, - time = 0.25, - minpos = pos, - maxpos = pos, - minvel = {x = -radius, y = ym, z = -radius}, - maxvel = {x = radius, y = radius, z = radius}, - minacc = {x = 0, y = gravity, z = 0}, - maxacc = {x = 0, y = gravity, z = 0}, - minexptime = 0.1, - maxexptime = 1, - minsize = min_size, - maxsize = max_size, - texture = texture, - glow = glow, - }) -end - -local damage_effect = function(self, damage) - -- damage particles - if (not disable_blood) and damage > 0 then - - local amount_large = floor(damage / 2) - local amount_small = damage % 2 - - local pos = self.object:get_pos() - - pos.y = pos.y + (self.collisionbox[5] - self.collisionbox[2]) * .5 - - local texture = "mobs_blood.png" - -- full heart damage (one particle for each 2 HP damage) - if amount_large > 0 then - effect(pos, amount_large, texture, 2, 2, 1.75, 0, nil, true) - end - -- half heart damage (one additional particle if damage is an odd number) - if amount_small > 0 then - -- TODO: Use "half heart" - effect(pos, amount_small, texture, 1, 1, 1.75, 0, nil, true) - end - end -end - -mcl_mobs.death_effect = function(pos, yaw, collisionbox, rotate) - local min, max - if collisionbox then - min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]} - max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]} - else - min = { x = -0.5, y = 0, z = -0.5 } - max = { x = 0.5, y = 0.5, z = 0.5 } - end - if rotate then - min = vector.rotate(min, {x=0, y=yaw, z=pi/2}) - max = vector.rotate(max, {x=0, y=yaw, z=pi/2}) - min, max = vector.sort(min, max) - min = vector.multiply(min, 0.5) - max = vector.multiply(max, 0.5) - end - - minetest.add_particlespawner({ - amount = 50, - time = 0.001, - minpos = vector.add(pos, min), - maxpos = vector.add(pos, max), - minvel = vector.new(-5,-5,-5), - maxvel = vector.new(5,5,5), - minexptime = 1.1, - maxexptime = 1.5, - minsize = 1, - maxsize = 2, - collisiondetection = false, - vertical = false, - texture = "mcl_particles_mob_death.png^[colorize:#000000:255", - }) - - minetest.sound_play("mcl_mobs_mob_poof", { - pos = pos, - gain = 1.0, - max_hear_distance = 8, - }, true) -end - -local update_tag = function(self) +function mob_class:update_tag() --update nametag and/or the debug box local tag if mobs_debug then - tag = "nametag = '"..tostring(self.nametag).."'\n".. + local name = self.name + if self.nametag and self.nametag ~= "" then + name = self.nametag + end + tag = "name = '"..tostring(name).."'\n".. "state = '"..tostring(self.state).."'\n".. "order = '"..tostring(self.order).."'\n".. "attack = "..tostring(self.attack).."\n".. @@ -814,3255 +73,20 @@ local update_tag = function(self) "horny = "..tostring(self.horny).."\n".. "hornytimer = "..tostring(self.hornytimer).."\n".. "runaway_timer = "..tostring(self.runaway_timer).."\n".. - "following = "..tostring(self.following) + "following = "..tostring(self.following).."\n".. + "lifetimer = "..tostring(self.lifetimer) else tag = self.nametag end self.object:set_properties({ nametag = tag, }) - - update_roll(self) end --- drop items -local item_drop = function(self, cooked, looting_level) - - -- no drops if disabled by setting - if not mobs_drop_items then return end - - looting_level = looting_level or 0 - - -- no drops for child mobs (except monster) - if (self.child and self.type ~= "monster") then - return - end - - local obj, item, num - local pos = self.object:get_pos() - - self.drops = self.drops or {} -- nil check - - for n = 1, #self.drops do - local dropdef = self.drops[n] - local chance = 1 / dropdef.chance - local looting_type = dropdef.looting - - if looting_level > 0 then - local chance_function = dropdef.looting_chance_function - if chance_function then - chance = chance_function(looting_level) - elseif looting_type == "rare" then - chance = chance + (dropdef.looting_factor or 0.01) * looting_level - end - end - - local num = 0 - local do_common_looting = (looting_level > 0 and looting_type == "common") - 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 + floor(random(0, looting_level) + 0.5) - end - - if num > 0 then - item = dropdef.name - - -- cook items when true - if cooked then - - 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 - end - - -- add item if it exists - 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 = random(-10, 10) / 9, - y = 6, - z = random(-10, 10) / 9, - }) - elseif obj then - obj:remove() -- item does not exist - end - end - end - - self.drops = {} -end - - --- check if mob is dead or only hurt -local check_for_death = function(self, cause, cmi_cause) - - if self.state == "die" then - return true - end - - -- has health actually changed? - if self.health == self.old_health and self.health > 0 then - return false - end - - local damaged = self.health < self.old_health - self.old_health = self.health - - -- still got some health? - if self.health > 0 then - - -- make sure health isn't higher than max - if self.health > self.hp_max then - self.health = self.hp_max - end - - -- play damage sound if health was reduced and make mob flash red. - if damaged then - add_texture_mod(self, "^[colorize:#d42222:175") - minetest.after(1, function(self) - if self and self.object then - remove_texture_mod(self, "^[colorize:#d42222:175") - end - end, self) - mob_sound(self, "damage") - end - - -- backup nametag so we can show health stats - if not self.nametag2 then - self.nametag2 = self.nametag or "" - end - - if show_health - and (cmi_cause and cmi_cause.type == "punch") then - - self.htimer = 2 - self.nametag = "♥ " .. self.health .. " / " .. self.hp_max - - update_tag(self) - end - - return false - end - - mob_sound(self, "death") - - local function death_handle(self) - -- dropped cooked item if mob died in fire or lava - if cause == "lava" or cause == "fire" then - item_drop(self, true, 0) - else - local wielditem = ItemStack() - if cause == "hit" then - local puncher = cmi_cause.puncher - if puncher then - wielditem = puncher:get_wielded_item() - end - end - local cooked = mcl_burning.is_burning(self.object) or mcl_enchanting.has_enchantment(wielditem, "fire_aspect") - local looting = mcl_enchanting.get_enchantment(wielditem, "looting") - item_drop(self, cooked, looting) - - if ((not self.child) or self.type ~= "animal") and (minetest.get_us_time() - self.xp_timestamp <= 5000000) then - mcl_experience.throw_xp(self.object:get_pos(), random(self.xp_min, self.xp_max)) - end - end - end - - -- execute custom death function - if self.on_die then - - local pos = self.object:get_pos() - local on_die_exit = self.on_die(self, pos, cmi_cause) - if on_die_exit ~= true then - death_handle(self) - end - - if on_die_exit == true then - self.state = "die" - mcl_burning.extinguish(self.object) - self.object:remove() - return true - end - end - - local collisionbox - if self.collisionbox then - collisionbox = table.copy(self.collisionbox) - end - - self.state = "die" - self.attack = nil - self.v_start = false - self.fall_speed = DEFAULT_FALL_SPEED - self.timer = 0 - self.blinktimer = 0 - remove_texture_mod(self, "^[colorize:#FF000040") - remove_texture_mod(self, "^[brighten") - self.passive = true - - self.object:set_properties({ - pointable = false, - collide_with_objects = false, - }) - - set_velocity(self, 0) - local acc = self.object:get_acceleration() - acc.x, acc.y, acc.z = 0, DEFAULT_FALL_SPEED, 0 - self.object:set_acceleration(acc) - - local length - -- default death function and die animation (if defined) - if self.instant_death then - length = 0 - 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 = max(frames / speed, 0) + DEATH_DELAY - set_animation(self, "die") - else - length = 1 + DEATH_DELAY - set_animation(self, "stand", true) - end - - - -- Remove body after a few seconds and drop stuff - local kill = function(self) - if not self.object:get_luaentity() then - return - end - - death_handle(self) - local dpos = self.object:get_pos() - local cbox = self.collisionbox - local yaw = self.object:get_rotation().y - mcl_burning.extinguish(self.object) - self.object:remove() - mcl_mobs.death_effect(dpos, yaw, cbox, not self.instant_death) - end - if length <= 0 then - kill(self) - else - minetest.after(length, kill, self) - end - - return true -end - - --- check if within physical map limits (-30911 to 30927) -local function within_limits(pos, radius) - local wmin, wmax = -30912, 30928 - if mcl_vars then - if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then - wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max - end - end - if radius then - wmin = wmin - radius - wmax = wmax + radius - end - for _,v in pairs(pos) do - if v < wmin or v > wmax then return false end - end - return true -end - --- 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 - - -local can_jump_cliff = function(self) - local yaw = self.object:get_yaw() - local pos = self.object:get_pos() - local v = self.object:get_velocity() - - local v2 = abs(v.x)+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 - - -- where is front - local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+0.6 - local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+0.6 - - --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 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) - - then - --disable fear heigh while we make our jump - self._jumping_cliff = true - minetest.after(1, function() - if self and self.object then - self._jumping_cliff = false - end - end) - return true - else - return false - end -end - --- is mob facing a cliff or danger -local is_at_cliff_or_danger = function(self) - - if self.fear_height == 0 or can_jump_cliff(self) or self._jumping_cliff or not self.object:get_luaentity() then -- 0 for no falling protection! - return false - end - - local yaw = self.object:get_yaw() - local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5) - local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5) - local pos = self.object:get_pos() - local ypos = pos.y + self.collisionbox[2] -- just above floor - - local free_fall, blocker = minetest.line_of_sight( - {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z}, - {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}) - if free_fall then - return true - else - local bnode = minetest.get_node(blocker) - local danger = is_node_dangerous(self, 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 - end - - return false -end - - --- copy the 'mob facing cliff_or_danger check' from above, and rework to avoid water -local is_at_water_danger = function(self) - - - if not self.object:get_luaentity() or can_jump_cliff(self) or self._jumping_cliff then - return false - end - local yaw = self.object:get_yaw() - local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5) - local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5) - local pos = self.object:get_pos() - local ypos = pos.y + self.collisionbox[2] -- just above floor - - local free_fall, blocker = minetest.line_of_sight( - {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z}, - {x = pos.x + dir_x, y = ypos - 3, z = pos.z + dir_z}) - if free_fall then - return true - else - local bnode = minetest.get_node(blocker) - local waterdanger = is_node_waterhazard(self, bnode.name) - if - waterdanger and (is_node_waterhazard(self, self.standing_in) or is_node_waterhazard(self, self.standing_on)) then - return false - elseif waterdanger and (is_node_waterhazard(self, self.standing_in) or is_node_waterhazard(self, self.standing_on)) == false then - return true - else - local def = minetest.registered_nodes[bnode.name] - if def and def.walkable then - return false - end - end - end - - return false -end - - --- environmental damage (water, lava, fire, light etc.) -local do_env_damage = function(self) - - -- feed/tame text timer (so mob 'full' messages dont spam chat) - if self.htimer > 0 then - self.htimer = self.htimer - 1 - end - - -- reset nametag after showing health stats - if self.htimer < 1 and self.nametag2 then - - self.nametag = self.nametag2 - self.nametag2 = nil - - update_tag(self) - end - - local pos = self.object:get_pos() - - self.time_of_day = minetest.get_timeofday() - - -- remove mob if beyond map limits - if not within_limits(pos, 0) then - mcl_burning.extinguish(self.object) - self.object:remove() - return true - end - - - -- Deal light damage to mob, returns true if mob died - local deal_light_damage = function(self, pos, damage) - if not ((mcl_weather.rain.raining or mcl_weather.state == "snow") and mcl_weather.is_outdoor(pos)) then - self.health = self.health - damage - - effect(pos, 5, "mcl_particles_smoke.png") - - if check_for_death(self, "light", {type = "light"}) then - return true - end - end - end - - local sunlight = 10 - if within_limits(pos,0) then - sunlight = minetest.get_natural_light(pos, self.time_of_day) - end - - -- bright light harms mob - if self.light_damage ~= 0 and (sunlight or 0) > 12 then - if deal_light_damage(self, pos, self.light_damage) then - return true - end - end - local _, dim = mcl_worlds.y_to_layer(pos.y) - if (self.sunlight_damage ~= 0 or self.ignited_by_sunlight) and (sunlight or 0) >= minetest.LIGHT_MAX and dim == "overworld" then - if self.armor_list and not self.armor_list.helmet or not self.armor_list or self.armor_list and self.armor_list.helmet and self.armor_list.helmet == "" then - if self.ignited_by_sunlight then - mcl_burning.set_on_fire(self.object, 10) - else - deal_light_damage(self, pos, self.sunlight_damage) - return true - end - end - end - - local y_level = self.collisionbox[2] - - if self.child then - y_level = self.collisionbox[2] * 0.5 - end - - -- 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} - self.standing_in = node_ok(pos, "air").name - self.standing_on = node_ok(pos2, "air").name - - -- 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}) - end - - local nodef = minetest.registered_nodes[self.standing_in] - - -- rain - if self.rain_damage > 0 then - if mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then - - self.health = self.health - self.rain_damage - - if check_for_death(self, "rain", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end - end - end - - pos.y = pos.y + 1 -- for particle effect position - - -- water damage - if self.water_damage > 0 - and nodef.groups.water then - - if self.water_damage ~= 0 then - - self.health = self.health - self.water_damage - - effect(pos, 5, "mcl_particles_smoke.png", nil, nil, 1, nil) - - if check_for_death(self, "water", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end - end - - -- lava damage - elseif self.lava_damage > 0 - and (nodef.groups.lava) then - - if self.lava_damage ~= 0 then - - self.health = self.health - self.lava_damage - - effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil) - mcl_burning.set_on_fire(self.object, 10) - - if check_for_death(self, "lava", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end - end - - -- fire damage - elseif self.fire_damage > 0 - and (nodef.groups.fire) then - - if self.fire_damage ~= 0 then - - self.health = self.health - self.fire_damage - - effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil) - mcl_burning.set_on_fire(self.object, 5) - - if check_for_death(self, "fire", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end - end - - -- damage_per_second node check - elseif nodef.damage_per_second ~= 0 and not nodef.groups.lava and not nodef.groups.fire then - - self.health = self.health - nodef.damage_per_second - - effect(pos, 5, "mcl_particles_smoke.png") - - if check_for_death(self, "dps", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end - end - - -- Drowning damage - if self.breath_max ~= -1 then - local drowning = false - if self.breathes_in_water then - if minetest.get_item_group(self.standing_in, "water") == 0 then - drowning = true - end - elseif nodef.drowning > 0 then - drowning = true - end - if drowning then - - self.breath = max(0, self.breath - 1) - - effect(pos, 2, "bubble.png", nil, nil, 1, nil) - if self.breath <= 0 then - local dmg - if nodef.drowning > 0 then - dmg = nodef.drowning - else - dmg = 4 - end - damage_effect(self, dmg) - self.health = self.health - dmg - end - if check_for_death(self, "drowning", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end - else - self.breath = min(self.breath_max, self.breath + 1) - end - end - - --- suffocation inside solid node - -- FIXME: Redundant with mcl_playerplus - if (self.suffocation == true) - and (nodef.walkable == nil or nodef.walkable == true) - and (nodef.collision_box == nil or nodef.collision_box.type == "regular") - and (nodef.node_box == nil or nodef.node_box.type == "regular") - and (nodef.groups.disable_suffocation ~= 1) - and (nodef.groups.opaque == 1) then - - -- Short grace period before starting to take suffocation damage. - -- This is different from players, who take damage instantly. - -- This has been done because mobs might briefly be inside solid nodes - -- when e.g. climbing up stairs. - -- This is a bit hacky because it assumes that do_env_damage - -- is called roughly every second only. - self.suffocation_timer = self.suffocation_timer + 1 - if self.suffocation_timer >= 3 then - -- 2 damage per second - -- TODO: Deal this damage once every 1/2 second - self.health = self.health - 2 - - if check_for_death(self, "suffocation", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end - end - else - self.suffocation_timer = 0 - end - - return check_for_death(self, "", {type = "unknown"}) -end - - --- jump if facing a solid node (not fences or gates) -local do_jump = function(self) - if not self.jump - or self.jump_height == 0 - or self.fly - or (self.child and self.type ~= "monster") - or self.order == "stand" then - return false - end - - self.facing_fence = false - - -- something stopping us while moving? - if self.state ~= "stand" - and get_velocity(self) > 0.5 - and self.object:get_velocity().y ~= 0 then - return false - end - - local pos = self.object:get_pos() - 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 = abs(v.x)+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 - - -- where is front - local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+0.6 - local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+0.6 - - -- what is in front of mob? - nod = node_ok({ - x = pos.x + dir_x, - y = pos.y + 0.5, - z = pos.z + dir_z - }) - - -- 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") - - - -- 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 - return false - end - - -- thin blocks that do not need to be jumped - if nod.name == node_snow then - return false - end - - local ndef = minetest.registered_nodes[nod.name] - if self.walk_chance == 0 or ndef and ndef.walkable or can_jump_cliff(self) then - - 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 - - local v = self.object:get_velocity() - - v.y = self.jump_height + 0.1 * 3 - - if can_jump_cliff(self) then - v=vector.multiply(v, vector.new(2.8,1,2.8)) - end - - set_animation(self, "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 - mob_sound(self, "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 = set_yaw(self, yaw + 1.35, 8) - - self.jump_count = 0 - end - end - - return true - end - - return false -end - - --- blast damage to entities nearby -local entity_physics = function(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) - if dist < 1 then dist = 1 end - - local damage = floor((4 / dist) * radius) - local ent = objs[n]:get_luaentity() - - -- punches work on entities AND players - objs[n]:punch(objs[n], 1.0, { - full_punch_interval = 1.0, - damage_groups = {fleshy = damage}, - }, pos) - end -end - - --- should mob follow what I'm holding ? -local follow_holding = function(self, clicker) - if self.nofollow 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 - 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 - end - end - - return false -end - - --- find two animals of same type and breed if nearby and horny -local breed = function(self) - - --mcl_log("In breed function") - -- child takes a long time before growing into adult - if self.child == true then - - -- When a child, hornytimer is used to count age until adulthood - self.hornytimer = self.hornytimer + 1 - - if self.hornytimer >= CHILD_GROW_TIME then - - self.child = false - self.hornytimer = 0 - - self.object:set_properties({ - textures = self.base_texture, - mesh = self.base_mesh, - visual_size = self.base_size, - collisionbox = self.base_colbox, - selectionbox = self.base_selbox, - }) - - -- custom function when child grows up - if self.on_grown then - self.on_grown(self) - else - -- jump when fully grown so as not to fall into ground - self.object:set_velocity({ - x = 0, - y = self.jump_height*3, - z = 0 - }) - end - - self.animation = nil - local anim = self._current_animation - self._current_animation = nil -- Mobs Redo does nothing otherwise - mcl_mobs.set_animation(self, anim) - end - - return - end - - -- horny animal can mate for HORNY_TIME seconds, - -- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds - if self.horny == true - and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then - - self.hornytimer = self.hornytimer + 1 - - if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then - self.hornytimer = 0 - self.horny = false - end - end - - -- find another same animal who is also horny and mate if nearby - if self.horny == true - and self.hornytimer <= HORNY_TIME then - - mcl_log("In breed function. All good. Do the magic.") - - local pos = self.object:get_pos() - - effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1) - - local objs = minetest.get_objects_inside_radius(pos, 3) - local num = 0 - local ent = nil - - for n = 1, #objs do - - ent = objs[n]:get_luaentity() - - -- check for same animal with different colour - local canmate = false - - if ent then - - if ent.name == self.name then - canmate = true - else - local entname = string.split(ent.name,":") - local selfname = string.split(self.name,":") - - if entname[1] == selfname[1] then - entname = string.split(entname[2],"_") - selfname = string.split(selfname[2],"_") - - if entname[1] == selfname[1] then - canmate = true - end - end - end - end - - if canmate then mcl_log("In breed function. Can mate.") end - - if ent - and canmate == true - and ent.horny == true - and ent.hornytimer <= HORNY_TIME then - num = num + 1 - end - - -- found your mate? then have a baby - if num > 1 then - - self.hornytimer = HORNY_TIME + 1 - ent.hornytimer = HORNY_TIME + 1 - - -- spawn baby - - - minetest.after(5, function(parent1, parent2, pos) - if not parent1.object:get_luaentity() then - return - end - if not parent2.object:get_luaentity() then - return - end - - mcl_experience.throw_xp(pos, random(1, 7)) - - -- custom breed function - if parent1.on_breed then - -- when false, skip going any further - if parent1.on_breed(parent1, parent2) == false then - return - end - end - - local child = mcl_mobs:spawn_child(pos, parent1.name) - - local ent_c = child:get_luaentity() - - - -- Use texture of one of the parents - local p = random(1, 2) - if p == 1 then - ent_c.base_texture = parent1.base_texture - else - ent_c.base_texture = parent2.base_texture - end - child:set_properties({ - textures = ent_c.base_texture - }) - - -- tamed and owned by parents' owner - ent_c.tamed = true - ent_c.owner = parent1.owner - end, self, ent, pos) - - num = 0 - - break - end - end - end -end - - --- find and replace what mob is looking for (grass, wheat etc.) -local replace = function(self, pos) - - if not self.replace_rate - or not self.replace_what - or self.child == true - or self.object:get_velocity().y ~= 0 - 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 = random(#self.replace_what) - - what = self.replace_what[num][1] or "" - with = self.replace_what[num][2] or "" - y_offset = self.replace_what[num][3] or 0 - else - what = self.replace_what - with = self.replace_with or "" - y_offset = self.replace_offset or 0 - end - - pos.y = pos.y + y_offset - - 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 - - if self.on_replace then - on_replace_return = self.on_replace(self, pos, oldnode, newnode) - end - - if on_replace_return ~= false then - - if mobs_griefing then - minetest.set_node(pos, newnode) - end - - end - end -end - - --- check if daytime and also if mob is docile during daylight hours -local day_docile = function(self) - - 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 -end - - -local los_switcher = false -local height_switcher = false - --- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3 -local smart_mobs = function(self, s, p, dist, dtime) - - local s1 = self.path.lastpos - - local target_pos = self.attack:get_pos() - - -- is it becoming stuck? - 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} - - 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) - - -- 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 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 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 has_lineofsight then self.path.following = false end - end, self) - end - - if abs(vector.subtract(s,target_pos).y) > self.stepheight then - - if height_switcher then - use_pathfind = true - height_switcher = false - end - else - if not height_switcher then - use_pathfind = false - height_switcher = true - end - end - - if use_pathfind then - -- lets try find a path, first take care of positions - -- since pathfinder is very sensitive - local sheight = self.collisionbox[5] - self.collisionbox[2] - - -- round position to center of node to avoid stuck in walls - -- also adjust height for player models! - s.x = floor(s.x + 0.5) - s.z = floor(s.z + 0.5) - - local ssight, sground = minetest.line_of_sight(s, { - x = s.x, y = s.y - 4, z = s.z}, 1) - - -- determine node above ground - if not ssight then - s.y = sground.y + 1 - end - - local p1 = self.attack:get_pos() - - p1.x = floor(p1.x + 0.5) - p1.y = floor(p1.y + 0.5) - p1.z = 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 = min(ceil(self.jump_height / 4), 4) - elseif self.stepheight > 0.5 then - jumpheight = 1 - end - self.path.way = minetest.find_path(s, p1, 16, jumpheight, dropheight, "A*_noprefetch") - - self.state = "" - do_attack(self, self.attack) - - -- 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 = 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] - - if node1 ~= "air" - and node1 ~= "ignore" - and ndef1 - 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}) - - else -- dig 2 blocks to make door toward player direction - - local yaw1 = self.object:get_yaw() + pi / 2 - local p1 = { - x = s.x + cos(yaw1), - y = s.y, - z = s.z + 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" - and ndef1 - and not ndef1.groups.level - and not ndef1.groups.unbreakable - and not ndef1.groups.liquid then - - minetest.add_item(p1, ItemStack(node1)) - minetest.set_node(p1, {name = "air"}) - end - - p1.y = p1.y + 1 - node1 = node_ok(p1, "air").name - ndef1 = minetest.registered_nodes[node1] - - if node1 ~= "air" - and node1 ~= "ignore" - and ndef1 - and not ndef1.groups.level - and not ndef1.groups.unbreakable - and not ndef1.groups.liquid then - - minetest.add_item(p1, ItemStack(node1)) - minetest.set_node(p1, {name = "air"}) - end - - end - end - end - - -- will try again in 2 seconds - self.path.stuck_timer = stuck_timeout - 2 - elseif s.y < p1.y and (not self.fly) then - do_jump(self) --add jump to pathfinding - self.path.following = true - -- Yay, I found path! - -- TODO: Implement war_cry sound without being annoying - --mob_sound(self, "war_cry", true) - else - set_velocity(self, self.walk_velocity) - - -- follow path now that it has it - self.path.following = true - end - end -end - - --- specific attacks -local specific_attack = function(list, what) - - -- no list so attack default (player, animals etc.) - 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 - end - - return false -end - --- find someone to attack -local monster_attack = function(self) - if not damage_enabled - or self.passive ~= false - or self.state == "attack" - or day_docile(self) then - return - end - - local s = self.object:get_pos() - local p, sp, dist - local player, obj, min_player - local type, name = "", "" - local min_dist = self.view_range + 1 - local objs = minetest.get_objects_inside_radius(s, self.view_range) - local blacklist_attack = {} - - for n = 1, #objs do - if not objs[n]:is_player() then - obj = objs[n]:get_luaentity() - - if obj then - player = obj.object - name = obj.name or "" - end - if obj and obj.type == self.type and obj.passive == false and obj.state == "attack" and obj.attack then - table.insert(blacklist_attack, obj.attack) - end - end - end - - for n = 1, #objs do - - - if objs[n]:is_player() then - if mcl_mobs.invis[ objs[n]:get_player_name() ] or (not object_in_range(self, objs[n])) then - type = "" - elseif (self.type == "monster" or self._aggro) then - player = objs[n] - type = "player" - name = "player" - end - else - obj = objs[n]:get_luaentity() - - if obj then - player = obj.object - type = obj.type - name = obj.name or "" - end - - end - - -- find specific mob to attack, failing that attack player/npc/animal - if specific_attack(self.specific_attack, name) - and (type == "player" or ( type == "npc" and self.attack_npcs ) - or (type == "animal" and self.attack_animals == true)) then - - p = player:get_pos() - sp = s - - dist = vector.distance(p, s) - - -- aim higher to make looking up hills more realistic - p.y = p.y + 1 - sp.y = sp.y + 1 - - local attacked_p = false - for c=1, #blacklist_attack do - if blacklist_attack[c] == player then - attacked_p = true - end - end - -- choose closest player to attack - if dist < min_dist - and not attacked_p - and line_of_sight(self, sp, p, 2) == true then - min_dist = dist - min_player = player - end - end - end - if not min_player and #blacklist_attack > 0 then - min_player=blacklist_attack[math.random(#blacklist_attack)] - end - -- attack player - if min_player then - do_attack(self, min_player) - end -end - - --- npc, find closest monster to attack -local npc_attack = function(self) - - if self.type ~= "npc" - or not self.attacks_monsters - or self.state == "attack" then - return - end - - local p, sp, obj, min_player - local s = self.object:get_pos() - local min_dist = self.view_range + 1 - local objs = minetest.get_objects_inside_radius(s, self.view_range) - - for n = 1, #objs do - - obj = objs[n]:get_luaentity() - - if obj and obj.type == "monster" then - - p = obj.object:get_pos() - sp = s - - local dist = vector.distance(p, s) - - -- aim higher to make looking up hills more realistic - p.y = p.y + 1 - sp.y = sp.y + 1 - - if dist < min_dist - and line_of_sight(self, sp, p, 2) == true then - min_dist = dist - min_player = obj.object - end - end - end - - if min_player then - do_attack(self, min_player) - end -end - - --- specific runaway -local specific_runaway = function(list, what) - - -- no list so do not run - if list == nil then - return false - end - - -- found entity on list to attack? - for no = 1, #list do - - if list[no] == what then - return true - end - end - - return false -end - - --- find someone to runaway from -local runaway_from = function(self) - - if not self.runaway_from and self.state ~= "flop" then - return - end - - local s = self.object:get_pos() - local p, sp, dist - local player, obj, min_player - local type, name = "", "" - local min_dist = self.view_range + 1 - 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 object_in_range(self, objs[n])) then - type = "" - else - player = objs[n] - type = "player" - name = "player" - end - else - obj = objs[n]:get_luaentity() - - if obj then - player = obj.object - type = obj.type - name = obj.name or "" - end - end - - -- 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) - - - -- choose closest player/mpb to runaway from - if dist < min_dist - and line_of_sight(self, sp, p, 2) == true then - min_dist = dist - min_player = player - end - end - 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 * pi / 2) - self.rotate - - if lp.x > s.x then - yaw = yaw + pi - end - - yaw = set_yaw(self, yaw, 4) - self.state = "runaway" - self.runaway_timer = 3 - self.following = nil - end -end - - --- follow player if owner or holding item, if fish outta water then flop -local follow_flop = function(self) - - -- find player to follow - if (self.follow ~= "" - or self.order == "follow") - and not self.following - and self.state ~= "attack" - and self.order ~= "sit" - and self.state ~= "runaway" then - - local s = self.object:get_pos() - local players = minetest.get_connected_players() - - for n = 1, #players do - - if (object_in_range(self, players[n])) - and not mcl_mobs.invis[ players[n]:get_player_name() ] then - - self.following = players[n] - - break - end - end - end - - if self.type == "npc" - and self.order == "follow" - and self.state ~= "attack" - and self.order ~= "sit" - and self.owner ~= "" then - - -- npc stop following player if not owner - if self.following - and self.owner - and self.owner ~= self.following:get_player_name() then - self.following = nil - end - else - -- stop following player if not holding specific item, - -- mob is horny, fleeing or attacking - if self.following - and self.following:is_player() - and (follow_holding(self, self.following) == false or - self.horny or self.state == "runaway") then - self.following = nil - end - - end - - -- 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 - - if p then - - local dist = vector.distance(p, s) - - -- dont follow if out of range - if (not object_in_range(self, 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) + pi / 2) - self.rotate - - if p.x > s.x then yaw = yaw + pi end - - set_yaw(self, yaw, 2.35) - - -- anyone but standing npc's can move along - if dist > 3 - and self.order ~= "stand" then - - set_velocity(self, self.follow_velocity) - - if self.walk_chance ~= 0 then - set_animation(self, "run") - end - else - set_velocity(self, 0) - set_animation(self, "stand") - end - - return - end - end - end - - -- swimmers flop when out of their element, and swim again when back in - if self.fly then - local s = self.object:get_pos() - if flight_check(self, s) == false then - - self.state = "flop" - self.object:set_acceleration({x = 0, y = DEFAULT_FALL_SPEED, z = 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] - -- Flop on ground - if sdef and sdef.walkable then - if self.object:get_velocity().y < 0.1 then - mob_sound(self, "flop") - self.object:set_velocity({ - x = random(-FLOP_HOR_SPEED, FLOP_HOR_SPEED), - y = FLOP_HEIGHT, - z = random(-FLOP_HOR_SPEED, FLOP_HOR_SPEED), - }) - end - end - - set_animation(self, "stand", true) - - return - elseif self.state == "flop" then - self.state = "stand" - self.object:set_acceleration({x = 0, y = 0, z = 0}) - set_velocity(self, 0) - end - end -end - - --- dogshoot attack switch and counter function -local dogswitch = function(self, dtime) - - -- switch mode not activated - 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 - - self.dogshoot_count = 0 - - if self.dogshoot_switch == 1 then - self.dogshoot_switch = 2 - else - self.dogshoot_switch = 1 - end - end - - return self.dogshoot_switch -end - -local function go_to_pos(entity,b) - if not entity then return end - local s=entity.object:get_pos() - if not b then - --self.state = "stand" - return end - if vector.distance(b,s) < 1 then - --set_velocity(entity,0) - return true - end - local v = { x = b.x - s.x, z = b.z - s.z } - local yaw = (atann(v.z / v.x) + pi / 2) - entity.rotate - if b.x > s.x then yaw = yaw + pi end - entity.object:set_yaw(yaw) - set_velocity(entity,entity.follow_velocity) - mcl_mobs:set_animation(entity, "walk") -end - -local function interact_with_door(self, action, target) - local p = self.object:get_pos() - --local t = minetest.get_timeofday() - --local dd = minetest.find_nodes_in_area(vector.offset(p,-1,-1,-1),vector.offset(p,1,1,1),{"group:door"}) - --for _,d in pairs(dd) do - if target then - mcl_log("Door target is: ".. minetest.pos_to_string(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 - else - mcl_log("Not door") - end - else - mcl_log("no target. cannot try and open or close door") - end - --end -end - -local function do_pathfind_action (self, action) - if action then - mcl_log("Action present") - local type = action["type"] - local action_val = action["action"] - local target = action["target"] - if target then - mcl_log("Target: ".. minetest.pos_to_string(target)) - end - if type and type == "door" then - mcl_log("Type is door") - interact_with_door(self, action_val, target) - end - end -end - -local gowp_etime = 0 - -local function check_gowp(self,dtime) - gowp_etime = gowp_etime + dtime - - -- 0.1 is optimal. - --less frequently = villager will get sent back after passing a point. - --more frequently = villager will fail points they shouldn't they just didn't get there yet - - --if gowp_etime < 0.05 then return end - --gowp_etime = 0 - local p = self.object:get_pos() - - -- no destination - if not p or not self._target then - mcl_log("p: ".. tostring(p)) - mcl_log("self._target: ".. tostring(self._target)) - return - end - - -- arrived at location, finish gowp - local distance_to_targ = vector.distance(p,self._target) - --mcl_log("Distance to targ: ".. tostring(distance_to_targ)) - if distance_to_targ < 2 then - mcl_log("Arrived at _target") - self.waypoints = nil - self._target = nil - 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}) - if self.callback_arrived then return self.callback_arrived(self) end - return true - end - - -- 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"]) - 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 - -- We have waypoints, and no current target, or we're at it. We need a new current_target. - do_pathfind_action (self, 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) - - self.current_target = table.remove(self.waypoints, 1) - go_to_pos(self, self.current_target["pos"]) - return - elseif self.current_target and self.current_target["pos"] then - -- No waypoints left, but have current target. 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 >= 50 then - mcl_log("Failed to reach position (" .. minetest.pos_to_string(self.current_target["pos"]) .. ") too many times. Abandon route. Times tried: " .. failed_attempts) - 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}) - 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) - go_to_pos(self, self.current_target["pos"]) - -- Do i just delete current_target, and return so we can find final path. - else - -- Not at target, no current waypoints or current_target. Through the door and should be able to path to target. - -- Is a little sensitive and could take 1 - 7 times. A 10 fail count might be a good exit condition. - - mcl_log("We don't have waypoints or a current target. Let's try to path to target") - local final_wp = minetest.find_path(p,self._target,150,1,4) - if final_wp then - mcl_log("We might be able to get to target here.") - -- self.waypoints = final_wp - --go_to_pos(self,self._target) - else - -- Abandon route? - mcl_log("Cannot plot final route to target") - end - end - - -- I don't think we need the following anymore, but test first. - -- Maybe just need something to path to target if no waypoints left - 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"]) - - mcl_log("Distance to current target: ".. tostring(distance_to_cur_targ)) - 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 - mcl_log("not close to current target: ".. minetest.pos_to_string(self.current_target["pos"])) - go_to_pos(self,self._current_target) - else - mcl_log("close to current target: ".. minetest.pos_to_string(self.current_target["pos"])) - self.current_target = nil - end - - return - end -end - --- execute current state (stand, walk, run, attacks) --- returns true if mob has died -local do_states = function(self, dtime) - --if self.can_open_doors then check_doors(self) end - - local yaw = self.object:get_yaw() or 0 - - if self.state == "stand" then - if random(1, 4) == 1 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 - 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) + pi / 2) - self.rotate - - if lp.x > s.x then yaw = yaw + pi end - else - yaw = yaw + random(-0.5, 0.5) - end - - yaw = set_yaw(self, yaw, 8) - end - if self.order == "sit" then - set_animation(self, "sit") - set_velocity(self, 0) - else - set_animation(self, "stand") - set_velocity(self, 0) - end - - -- npc's ordered to stand stay standing - if self.order == "stand" or self.order == "sleep" or self.order == "work" then - - else - if self.walk_chance ~= 0 - and self.facing_fence ~= true - and random(1, 100) <= self.walk_chance - and is_at_cliff_or_danger(self) == false then - - set_velocity(self, self.walk_velocity) - self.state = "walk" - set_animation(self, "walk") - end - end - - elseif self.state == PATHFINDING then - check_gowp(self,dtime) - - elseif self.state == "walk" then - 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 (is_node_dangerous(self, self.standing_in) or - is_node_dangerous(self, self.standing_on)) or (is_node_waterhazard(self, self.standing_in) or is_node_waterhazard(self, 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[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) + pi / 2) - self.rotate - - - if lp.x > s.x then yaw = yaw + pi end - - -- look towards land and move in that direction - yaw = set_yaw(self, yaw, 6) - set_velocity(self, self.walk_velocity) - - end - end - - -- A danger is near but mob is not inside - else - - -- Randomly turn - if random(1, 100) <= 30 then - yaw = yaw + random(-0.5, 0.5) - yaw = set_yaw(self, yaw, 8) - end - end - - yaw = set_yaw(self, yaw, 8) - - -- otherwise randomly turn - elseif random(1, 100) <= 30 then - - yaw = yaw + random(-0.5, 0.5) - yaw = set_yaw(self, 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 = is_at_cliff_or_danger(self) - end - if self.facing_fence == true - or cliff_or_danger - or random(1, 100) <= 30 then - - set_velocity(self, 0) - self.state = "stand" - set_animation(self, "stand") - local yaw = self.object:get_yaw() or 0 - yaw = set_yaw(self, yaw + 0.78, 8) - else - - set_velocity(self, self.walk_velocity) - - if flight_check(self) - and self.animation - and self.animation.fly_start - and self.animation.fly_end then - set_animation(self, "fly") - else - set_animation(self, "walk") - end - end - - -- runaway when punched - elseif self.state == "runaway" then - - self.runaway_timer = self.runaway_timer + 1 - - -- stop after 5 seconds or when at cliff - if self.runaway_timer > 5 - or is_at_cliff_or_danger(self) then - self.runaway_timer = 0 - set_velocity(self, 0) - self.state = "stand" - set_animation(self, "stand") - local yaw = self.object:get_yaw() or 0 - yaw = set_yaw(self, yaw + 0.78, 8) - else - set_velocity(self, self.run_velocity) - set_animation(self, "run") - end - - -- attack routines (explode, dogfight, shoot, dogshoot) - elseif self.state == "attack" then - - local s = self.object:get_pos() - local p = self.attack:get_pos() or s - - -- stop attacking if player invisible or out of range - if not self.attack - or not self.attack:get_pos() - or not object_in_range(self, self.attack) - or self.attack:get_hp() <= 0 - or (self.attack:is_player() and mcl_mobs.invis[ self.attack:get_player_name() ]) then - - self.state = "stand" - set_velocity(self, 0) - set_animation(self, "stand") - self.attack = nil - self.v_start = false - self.timer = 0 - self.blinktimer = 0 - self.path.way = nil - - return - end - - -- calculate distance from mob and enemy - local dist = vector.distance(p, s) - - if self.attack_type == "explode" then - - local vec = { - x = p.x - s.x, - z = p.z - s.z - } - - yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate - - if p.x > s.x then yaw = yaw + pi end - - yaw = set_yaw(self, yaw, 0, dtime) - - local node_break_radius = self.explosion_radius or 1 - 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 line_of_sight(self, s, p, 2) then - - self.v_start = true - self.timer = 0 - self.blinktimer = 0 - mob_sound(self, "fuse", nil, false) - - -- stop timer if out of reach or direct line of sight - elseif self.allow_fuse_reset - and self.v_start - and (dist >= self.explosiontimer_reset_radius - or not line_of_sight(self, s, p, 2)) then - self.v_start = false - self.timer = 0 - self.blinktimer = 0 - self.blinkstatus = false - remove_texture_mod(self, "^[brighten") - end - - -- walk right up to player unless the timer is active - if self.v_start and (self.stop_to_explode or dist < self.reach) then - set_velocity(self, 0) - else - set_velocity(self, self.run_velocity) - end - - if self.animation and self.animation.run_start then - set_animation(self, "run") - else - set_animation(self, "walk") - end - - if self.v_start then - - self.timer = self.timer + dtime - self.blinktimer = (self.blinktimer or 0) + dtime - - if self.blinktimer > 0.2 then - - self.blinktimer = 0 - - if self.blinkstatus then - remove_texture_mod(self, "^[brighten") - else - add_texture_mod(self, "^[brighten") - end - - self.blinkstatus = not self.blinkstatus - end - - if self.timer > self.explosion_timer then - - local pos = self.object:get_pos() - - if mobs_griefing and not minetest.is_protected(pos, "") then - mcl_explosions.explode(mcl_util.get_object_center(self.object), self.explosion_strength, { drop_chance = 1.0 }, self.object) - else - minetest.sound_play(self.sounds.explode, { - pos = pos, - gain = 1.0, - max_hear_distance = self.sounds.distance or 32 - }, true) - - entity_physics(pos, entity_damage_radius) - effect(pos, 32, "mcl_particles_smoke.png", nil, nil, node_break_radius, 1, 0) - end - mcl_burning.extinguish(self.object) - self.object:remove() - - return true - end - end - - elseif self.attack_type == "dogfight" - or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 2) and (dist >= self.avoid_distance or not self.shooter_avoid_enemy) - or (self.attack_type == "dogshoot" and dist <= self.reach and dogswitch(self) == 0) then - - if self.fly - and dist > self.reach then - - local p1 = s - local me_y = floor(p1.y) - local p2 = p - local p_y = floor(p2.y + 1) - local v = self.object:get_velocity() - - if flight_check(self, s) then - - if me_y < p_y then - - self.object:set_velocity({ - x = v.x, - y = 1 * self.walk_velocity, - z = v.z - }) - - elseif me_y > p_y then - - self.object:set_velocity({ - x = v.x, - y = -1 * self.walk_velocity, - z = v.z - }) - end - else - if me_y < p_y then - - self.object:set_velocity({ - x = v.x, - y = 0.01, - z = v.z - }) - - elseif me_y > p_y then - - self.object:set_velocity({ - x = v.x, - y = -0.01, - z = v.z - }) - end - end - - end - - -- rnd: new movement direction - 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 - self.path.following = false - return - end - - local p1 = self.path.way[1] - - if not p1 then - self.path.following = false - return - end - - 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} - end - - local vec = { - x = p.x - s.x, - z = p.z - s.z - } - - yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate - - if p.x > s.x then yaw = yaw + pi end - - yaw = set_yaw(self, yaw, 0, dtime) - - -- move towards enemy if beyond mob reach - if dist > self.reach then - - -- path finding by rnd - if self.pathfinding -- only if mob has pathfinding enabled - and enable_pathfinding then - - smart_mobs(self, s, p, dist, dtime) - end - - if is_at_cliff_or_danger(self) then - - set_velocity(self, 0) - set_animation(self, "stand") - local yaw = self.object:get_yaw() or 0 - yaw = set_yaw(self, yaw + 0.78, 8) - else - - if self.path.stuck then - set_velocity(self, self.walk_velocity) - else - set_velocity(self, self.run_velocity) - end - - if self.animation and self.animation.run_start then - set_animation(self, "run") - else - set_animation(self, "walk") - end - end - - else -- rnd: if inside reach range - - self.path.stuck = false - self.path.stuck_timer = 0 - self.path.following = false -- not stuck anymore - - set_velocity(self, 0) - - if not self.custom_attack then - - if self.timer > 1 then - - self.timer = 0 - - if self.double_melee_attack - and random(1, 2) == 1 then - set_animation(self, "punch2") - else - set_animation(self, "punch") - end - - local p2 = p - local s2 = s - - p2.y = p2.y + .5 - s2.y = s2.y + .5 - - if line_of_sight(self, p2, s2) == true then - - -- play attack sound - mob_sound(self, "attack") - - -- punch player (or what player is attached to) - local attached = self.attack:get_attach() - if attached then - self.attack = attached - end - self.attack:punch(self.object, 1.0, { - full_punch_interval = 1.0, - damage_groups = {fleshy = self.damage} - }, nil) - end - end - else -- call custom attack every second - if self.custom_attack - and self.timer > 1 then - - self.timer = 0 - - self.custom_attack(self, p) - end - end - end - - elseif self.attack_type == "shoot" - or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 1) - or (self.attack_type == "dogshoot" and (dist > self.reach or dist < self.avoid_distance and self.shooter_avoid_enemy) and dogswitch(self) == 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) + pi / 2) - self.rotate - - if p.x > s.x then yaw = yaw + pi end - - yaw = set_yaw(self, yaw, 0, dtime) - - local stay_away_from_player = vector.new(0,0,0) - - --strafe back and fourth - - --stay away from player so as to shoot them - if dist < self.avoid_distance and self.shooter_avoid_enemy then - set_animation(self, "shoot") - stay_away_from_player=vector.multiply(vector.direction(p, s), 0.33) - end - - 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 - self.acc = vector.add(vector.multiply(vector.rotate_around_axis(vector.direction(s, p), vector.new(0,1,0), self.strafe_direction), 0.3*self.walk_velocity), stay_away_from_player) - else - set_velocity(self, 0) - end - - local p = self.object:get_pos() - p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2 - - if self.shoot_interval - and self.timer > self.shoot_interval - and not minetest.raycast(vector.add(p, vector.new(0,self.shoot_offset,0)), vector.add(self.attack:get_pos(), vector.new(0,1.5,0)), false, false):next() - and random(1, 100) <= 60 then - - self.timer = 0 - set_animation(self, "shoot") - - -- play shoot attack sound - mob_sound(self, "shoot_attack") - - -- Shoot arrow - if minetest.registered_entities[self.arrow] then - - local arrow, ent - local v = 1 - if not self.shoot_arrow then - self.firing = true - minetest.after(1, function() - self.firing = false - end) - arrow = minetest.add_entity(p, self.arrow) - ent = arrow:get_luaentity() - if ent.velocity then - v = ent.velocity - end - ent.switch = 1 - ent.owner_id = tostring(self.object) -- add unique owner id to arrow - - -- important for mcl_shields - ent._shooter = self.object - ent._saved_shooter_pos = self.object:get_pos() - 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) - if self.shoot_arrow then - vec = vector.normalize(vec) - self:shoot_arrow(p, vec) - else - arrow:set_velocity(vec) - end - end - end - else - - end - end -end - -function output_table (wp) - if not wp then return end - mcl_log("wp items: ".. tostring(#wp)) - for a,b in pairs(wp) do - mcl_log(a.. ": ".. tostring(b)) - end -end - -function append_paths (wp1, wp2) - mcl_log("Start append") - if not wp1 or not wp2 then - mcl_log("Cannot append wp's") - return - end - output_table(wp1) - output_table(wp2) - for _,a in pairs (wp2) do - table.insert(wp1, a) - end - mcl_log("End append") -end - -local function output_enriched (wp_out) - 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("type: " .. action["type"]) - mcl_log("action: " .. action["action"]) - mcl_log("target: " .. minetest.pos_to_string(action["target"])) - end - mcl_log("failed attempts: " .. outy["failed_attempts"]) - end -end - --- This function will take a list of paths, and enrich it with: --- a var for failed attempts --- 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 = {} - for i, cur_pos in pairs(wp_in) do - local action = nil - - local one_down = vector.new(0,-1,0) - local cur_pos_to_add = vector.add(cur_pos, one_down) - if door_open_pos and vector.equals (cur_pos, door_open_pos) then - mcl_log ("Door open match") - --action = {type = "door", action = "open"} - action = {} - action["type"] = "door" - action["action"] = "open" - action["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 = "closed"} - action = {} - action["type"] = "door" - action["action"] = "close" - action["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") - cur_pos_to_add = vector.add(cur_pos, one_down) - action = {} - action["type"] = "door" - action["action"] = "open" - action["target"] = cur_door_pos - 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]["failed_attempts"] = 0 - wp_out[i]["action"] = action - - --wp_out[i] = {"pos" = cur_pos, "failed_attempts" = 0, "action" = action} - --output_pos(cur_pos, i) - end - output_enriched(wp_out) - return wp_out -end - -local plane_adjacents = { - vector.new(1,0,0), - vector.new(-1,0,0), - vector.new(0,0,1), - vector.new(0,0,-1), -} - --- 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, t, target) - -- target is the same as t, just 1 square difference. Maybe we don't need target - mcl_log("Plot route from mob: " .. minetest.pos_to_string(p) .. ", to target: " .. minetest.pos_to_string(t)) - - local enriched_path = nil - - local cur_door_pos = nil - local pos_closest_to_door = nil - local other_side_of_door = nil - - --Path to door first - local wp = minetest.find_path(p,t,150,1,4) - if not wp then - mcl_log("No direct path. Path through door") - - -- This could improve. There could be multiple doors. Check you can path from door to target first. - local cur_door_pos = minetest.find_node_near(target,16,{"group:door"}) - if cur_door_pos then - mcl_log("Found a door near: " .. minetest.pos_to_string(cur_door_pos)) - for _,v in pairs(plane_adjacents) do - pos_closest_to_door = vector.add(cur_door_pos,v) - - local n = minetest.get_node(pos_closest_to_door) - if n.name == "air" then - wp = minetest.find_path(p,pos_closest_to_door,150,1,4) - if wp then - mcl_log("Found a path to next to door".. minetest.pos_to_string(pos_closest_to_door)) - other_side_of_door = vector.add(cur_door_pos,-v) - mcl_log("Opposite is: ".. minetest.pos_to_string(other_side_of_door)) - - local wp_otherside_door_to_target = minetest.find_path(other_side_of_door,t,150,1,4) - if wp_otherside_door_to_target and #wp_otherside_door_to_target > 0 then - table.insert(wp, cur_door_pos) - append_paths (wp, wp_otherside_door_to_target) - enriched_path = generate_enriched_path(wp, pos_closest_to_door, other_side_of_door, cur_door_pos) - mcl_log("We have a path from outside door to target") - else - mcl_log("We cannot path from outside door to target") - end - break - else - mcl_log("This block next to door doesn't work.") - end - else - mcl_log("Block is not air, it is: ".. n.name) - end - - end - else - mcl_log("No door found") - end - else - mcl_log("We have a direct route") - end - - if wp and not enriched_path then - enriched_path = generate_enriched_path(wp) - end - return enriched_path -end - -local gopath_last = os.time() -function mcl_mobs:gopath(self,target,callback_arrived) - if self.state == PATHFINDING then mcl_log("Already pathfinding, don't set another until done.") return end - - if self._pf_last_failed and (os.time() - self._pf_last_failed) < 30 then - mcl_log("We are not ready to path as last fail is less than threshold: " .. (os.time() - self._pf_last_failed)) - return - else - mcl_log("We are ready to pathfind, no previous fail or we are past threshold") - end - - --if os.time() - gopath_last < 5 then - -- mcl_log("Not ready to path yet") - -- return - --end - --gopath_last = os.time() - - self.order = nil - - local p = self.object:get_pos() - local t = vector.offset(target,0,1,0) - - local wp = calculate_path_through_door(p, t, target) - if not wp then - mcl_log("Could not calculate path") - self._pf_last_failed = os.time() - -- Cover for a flaw in pathfind where it chooses the wrong door and gets stuck. Take a break, allow others. - end - --output_table(wp) - - if wp and #wp > 0 then - 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") - end - self.current_target = current_location - self.waypoints = wp - self.state = PATHFINDING - return true - else - self.state = "walk" - self.waypoints = nil - self.current_target = nil - -- minetest.log("no path found") - end -end - -local function player_near(pos) - for _,o in pairs(minetest.get_objects_inside_radius(pos,2)) do - if o:is_player() then return true end - end -end - -local function get_armor_texture(armor_name) - if armor_name == "" then - return "" - end - if armor_name=="blank.png" then - return "blank.png" - end - local seperator = string.find(armor_name, ":") - return "mcl_armor_"..string.sub(armor_name, seperator+1, -1)..".png^" -end - -local function set_armor_texture(self) - if self.armor_list then - local chestplate=minetest.registered_items[self.armor_list.chestplate] or {name=""} - local boots=minetest.registered_items[self.armor_list.boots] or {name=""} - local leggings=minetest.registered_items[self.armor_list.leggings] or {name=""} - local helmet=minetest.registered_items[self.armor_list.helmet] or {name=""} - - if helmet.name=="" and chestplate.name=="" and leggings.name=="" and boots.name=="" then - helmet={name="blank.png"} - end - local texture = get_armor_texture(chestplate.name)..get_armor_texture(helmet.name)..get_armor_texture(boots.name)..get_armor_texture(leggings.name) - if string.sub(texture, -1,-1) == "^" then - texture=string.sub(texture,1,-2) - end - if self.textures[self.wears_armor] then - self.textures[self.wears_armor]=texture - end - self.object:set_properties({textures=self.textures}) - - local armor_ - if type(self.armor) == "table" then - armor_ = table.copy(self.armor) - armor_.immortal = 1 - else - armor_ = {immortal=1, fleshy = self.armor} - end - - for _,item in pairs(self.armor_list) do - if not item then return end - if type(minetest.get_item_group(item, "mcl_armor_points")) == "number" then - armor_.fleshy=armor_.fleshy-(minetest.get_item_group(item, "mcl_armor_points")*3.5) - end - end - self.object:set_armor_groups(armor_) - end -end - -local function check_item_pickup(self) - if self.pick_up and #self.pick_up > 0 or self.wears_armor then - local p = self.object:get_pos() - for _,o in pairs(minetest.get_objects_inside_radius(p,2)) do - local l=o:get_luaentity() - if l and l.name == "__builtin:item" then - if not player_near(p) and l.itemstring:find("mcl_armor") and self.wears_armor then - local armor_type - if l.itemstring:find("chestplate") then - armor_type = "chestplate" - elseif l.itemstring:find("boots") then - armor_type = "boots" - elseif l.itemstring:find("leggings") then - armor_type = "leggings" - elseif l.itemstring:find("helmet") then - armor_type = "helmet" - end - if not armor_type then - return - end - if not self.armor_list then - self.armor_list={helmet="",chestplate="",boots="",leggings=""} - elseif self.armor_list[armor_type] and self.armor_list[armor_type] ~= "" then - return - end - self.armor_list[armor_type]=ItemStack(l.itemstring):get_name() - o:remove() - end - if self.pick_up then - for k,v in pairs(self.pick_up) do - if not player_near(p) and self.on_pick_up and l.itemstring:find(v) then - local r = self.on_pick_up(self,l) - if r and r.is_empty and not r:is_empty() then - l.itemstring = r:to_string() - elseif r and r.is_empty and r:is_empty() then - o:remove() - end - end - end - end - end - end - end -end - -local check_herd_timer = 0 -local function check_herd(self,dtime) - local pos = self.object:get_pos() - if not pos then return end - check_herd_timer = check_herd_timer + dtime - if check_herd_timer < 4 then return end - 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() - else - y = o:get_yaw() - end - if p then - go_to_pos(self,p) - elseif y then - set_yaw(self,y) - end - end - end -end - -local function damage_mob(self,reason,damage) - if not self.health then return end - damage = floor(damage) - if damage > 0 then - self.health = self.health - damage - - effect(self.object:get_pos(), 5, "mcl_particles_smoke.png", 1, 2, 2, nil) - - if check_for_death(self, reason, {type = reason}) then - return true - end - end -end - -local function check_entity_cramming(self) - local p = self.object:get_pos() - if not p then return end - local oo = minetest.get_objects_inside_radius(p,1) - local mobs = {} - for _,o in pairs(oo) do - local l = o:get_luaentity() - if l and l.is_mob and l.health > 0 then table.insert(mobs,l) end - end - local clear = #mobs < ENTITY_CRAMMING_MAX - local ncram = {} - for _,l in pairs(mobs) do - if l then - if clear then - l.cram = nil - elseif l.cram == nil and not self.child then - table.insert(ncram,l) - elseif l.cram then - damage_mob(l,"cramming",CRAMMING_DAMAGE) - end - end - end - for i,l in pairs(ncram) do - if i > ENTITY_CRAMMING_MAX then - l.cram = true - else - l.cram = nil - end - end -end - --- falling and fall damage --- returns true if mob died -local falling = function(self, pos) - - if self.fly and self.state ~= "die" then - return - 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.y > 0 then - - -- apply gravity when moving up - self.object:set_acceleration({ - x = 0, - y = DEFAULT_FALL_SPEED, - z = 0 - }) - - elseif v.y <= 0 and v.y > self.fall_speed then - - -- fall downwards at set speed - self.object:set_acceleration({ - x = 0, - y = self.fall_speed, - z = 0 - }) - else - -- stop accelerating once max fall speed hit - self.object:set_acceleration({x = 0, y = 0, z = 0}) - end - - if minetest.registered_nodes[node_ok(pos).name].groups.lava then - - if self.floats_on_lava == 1 then - - self.object:set_acceleration({ - x = 0, - y = -self.fall_speed / (max(1, v.y) ^ 2), - z = 0 - }) - end - end - - -- in water then float up - if minetest.registered_nodes[node_ok(pos).name].groups.water then - - if self.floats == 1 then - - self.object:set_acceleration({ - x = 0, - y = -self.fall_speed / (max(1, v.y) ^ 2), - z = 0 - }) - end - else - - -- fall damage onto solid ground - if self.fall_damage == 1 - and self.object:get_velocity().y == 0 then - local n = node_ok(vector.offset(pos,0,-1,0)).name - local d = (self.old_y or 0) - self.object:get_pos().y - - if d > 5 and n ~= "air" and n ~= "ignore" then - local add = minetest.get_item_group(self.standing_on, "fall_damage_add_percent") - local damage = d - 5 - if add ~= 0 then - damage = damage + damage * (add/100) - end - damage_mob(self,"fall",damage) - end - - self.old_y = self.object:get_pos().y - end - end -end - -local teleport = function(self, target) - if self.do_teleport then - if self.do_teleport(self, target) == false then - return - end - end -end - - --- deal damage and effects when mob punched -local mob_punch = function(self, hitter, tflp, tool_capabilities, dir) - - -- 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 - end - end - - -- error checking when mod profiling is enabled - if not tool_capabilities then - minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled") - return - end - - local is_player = hitter:is_player() - - if is_player then - -- is mob protected? - if self.protected and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then - return - 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 = minetest.get_us_time() - end - - - -- punch interval - local weapon = hitter:get_wielded_item() - local punch_interval = 1.4 - - -- exhaust attacker - if is_player then - mcl_hunger.exhaust(hitter:get_player_name(), mcl_hunger.EXHAUST_ATTACK) - end - - -- calculate mob damage - local damage = 0 - local armor = self.object:get_armor_groups() or {} - local tmp - - -- quick error check incase it ends up 0 (serialize.h check test) - if tflp == 0 then - tflp = 0.2 - end - - - 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) - end - - if weapon then - local fire_aspect_level = mcl_enchanting.get_enchantment(weapon, "fire_aspect") - if fire_aspect_level > 0 then - mcl_burning.set_on_fire(self.object, fire_aspect_level * 4) - end - end - - -- 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 - end - - -- healing - if damage <= -1 then - self.health = self.health - floor(damage) - return - end - - if tool_capabilities then - punch_interval = tool_capabilities.full_punch_interval or 1.4 - end - - -- add weapon wear manually - -- Required because we have custom health handling ("health" property) - if minetest.is_creative_enabled("") ~= true - and tool_capabilities then - if tool_capabilities.punch_attack_uses then - -- Without this delay, the wear does not work. Quite hacky ... - minetest.after(0, function(name) - local player = minetest.get_player_by_name(name) - if not player then return end - 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 = floor(65535/tool_capabilities.punch_attack_uses) - weapon:add_wear(wear) - hitter:set_wielded_item(weapon) - end - end, hitter:get_player_name()) - end - end - - 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 = random(0, #weapon:get_definition().sounds) - - minetest.sound_play(weapon:get_definition().sounds[s], { - object = self.object, --hitter, - max_hear_distance = 8 - }, true) - else - minetest.sound_play("default_punch", { - object = self.object, - max_hear_distance = 5 - }, true) - end - - damage_effect(self, damage) - - -- do damage - self.health = self.health - damage - - -- skip future functions if dead, except alerting others - if check_for_death(self, "hit", {type = "punch", puncher = hitter}) then - die = true - end - end - -- knock back effect (only on full punch) - if self.knock_back - and tflp >= punch_interval then - -- direction error check - dir = dir or {x = 0, y = 0, z = 0} - - local v = self.object:get_velocity() - if not v then return end - local r = 1.4 - min(punch_interval, 1.4) - local kb = r * (abs(v.x)+abs(v.z)) - local up = 2 - - if die==true then - kb=kb*2 - end - - -- if already in air then dont go up anymore when hit - 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 - kb = tool_capabilities.damage_groups["knockback"] - else - kb = kb * 1.5 - end - - - local luaentity - if hitter then - luaentity = hitter:get_luaentity() - end - if hitter and is_player then - local wielditem = hitter:get_wielded_item() - kb = kb + 3 * mcl_enchanting.get_enchantment(wielditem, "knockback") - elseif luaentity and luaentity._knockback then - kb = kb + luaentity._knockback - end - self._kb_turn = true - self._turn_to=self.object:get_yaw()-1.57 - self.frame_speed_multiplier=2.3 - if self.animation.run_end then - set_animation(self, "run") - elseif self.animation.walk_end then - set_animation(self, "walk") - end - minetest.after(0.2, function() - if self and self.object then - self.frame_speed_multiplier=1 - self._kb_turn = false - end - end) - self.object:add_velocity({ - x = dir.x * kb, - y = up*2, - z = dir.z * kb - }) - - self.pause_timer = 0.25 - end - end -- END if damage - - -- 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 = set_yaw(self, minetest.dir_to_yaw(vector.direction(hitter:get_pos(), self.object:get_pos()))) - 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 = set_yaw(self, minetest.dir_to_yaw(vector.direction(hitter:get_pos(), self.object:get_pos()))) - set_velocity(self, self.run_velocity) - end - end) - self.state = "runaway" - self.runaway_timer = 0 - self.following = nil - end - - local name = hitter:get_player_name() or "" - - -- attack puncher and call other mobs for help - if self.passive == false - and self.state ~= "flop" - and (self.child == false or self.type == "monster") - and hitter:get_player_name() ~= self.owner - and not mcl_mobs.invis[ name ] then - if not die then - -- attack whoever punched mob - self.state = "" - do_attack(self, hitter) - self._aggro= true - end - - -- alert others to the attack - local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range) - local obj = nil - - for n = 1, #objs do - - obj = objs[n]:get_luaentity() - - if obj then - -- only alert members of same mob or friends - if obj.group_attack - and obj.state ~= "attack" - and obj.owner ~= name then - if obj.name == self.name then - do_attack(obj, hitter) - elseif type(obj.group_attack) == "table" then - for i=1, #obj.group_attack do - if obj.name == obj.group_attack[i] then - obj._aggro = true - do_attack(obj, hitter) - break - end - end - end - end - - -- have owned mobs attack player threat - if obj.owner == name and obj.owner_loyal then - do_attack(obj, self.object) - end - end - end - end -end - -local mob_detach_child = function(self, 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 - -end - --- get entity staticdata -local mob_staticdata = function(self) +function mob_class:get_staticdata() for _,p in pairs(minetest.get_connected_players()) do - remove_particlespawners(p:get_player_name(),self) + self:remove_particlespawners(p:get_player_name()) end -- remove mob when out of range unless tamed if remove_far @@ -4099,15 +123,12 @@ local mob_staticdata = function(self) return minetest.serialize(tmp) end - --- activate mob and reload settings -local mob_activate = function(self, staticdata, def, dtime) +function mob_class:mob_activate(staticdata, def, dtime) if not self.object:get_pos() or staticdata == "remove" then mcl_burning.extinguish(self.object) self.object:remove() return end - -- remove monsters in peaceful mode if self.type == "monster" and minetest.settings:get_bool("only_peaceful_mobs", false) then mcl_burning.extinguish(self.object) @@ -4115,7 +136,6 @@ local mob_activate = function(self, staticdata, def, dtime) return end - -- load entity variables local tmp = minetest.deserialize(staticdata) if tmp then @@ -4124,7 +144,6 @@ local mob_activate = function(self, staticdata, def, dtime) end end - -- select random texture, set model and size if not self.base_texture then -- compatiblity with old simple mobs textures @@ -4135,38 +154,33 @@ local mob_activate = function(self, staticdata, def, dtime) local c = 1 if #def.textures > c then c = #def.textures end - self.base_texture = def.textures[random(c)] + self.base_texture = def.textures[math.random(c)] self.base_mesh = def.mesh self.base_size = self.visual_size self.base_colbox = self.collisionbox self.base_selbox = self.selectionbox end - -- for current mobs that dont have this set if not self.base_selbox then self.base_selbox = self.selectionbox or self.base_colbox end - -- set texture, model and size local textures = self.base_texture local mesh = self.base_mesh local vis_size = self.base_size local colbox = self.base_colbox local selbox = self.base_selbox - -- specific texture if gotten if self.gotten == true and def.gotten_texture then textures = def.gotten_texture end - -- specific mesh if gotten if self.gotten == true and def.gotten_mesh then mesh = def.gotten_mesh end - -- set child objects to half size if self.child == true then vis_size = { @@ -4197,13 +211,12 @@ local mob_activate = function(self, staticdata, def, dtime) end if self.health == 0 then - self.health = random (self.hp_min, self.hp_max) + self.health = math.random (self.hp_min, self.hp_max) end if self.breath == nil then self.breath = self.breath_max end - -- pathfinding init self.path = {} self.path.way = {} -- path to follow, table of positions self.path.lastpos = {x = 0, y = 0, z = 0} @@ -4243,12 +256,10 @@ local mob_activate = function(self, staticdata, def, dtime) self.blinktimer = 0 self.blinkstatus = false - -- check existing nametag if not self.nametag then self.nametag = def.nametag end if not self.custom_visual_size then - -- Remove saved visual_size on old existing entites. self.visual_size = nil self.base_size = self.visual_size if self.child then @@ -4259,17 +270,15 @@ local mob_activate = function(self, staticdata, def, dtime) end end - -- set anything changed above self.object:set_properties(self) - set_yaw(self, (random(0, 360) - 180) / 180 * pi, 6) - update_tag(self) + self:set_yaw( (math.random(0, 360) - 180) / 180 * math.pi, 6) + self:update_tag() self._current_animation = nil - set_animation(self, "stand") + self:set_animation( "stand") - -- run on_spawn function if found if self.on_spawn and not self.on_spawn_run then if self.on_spawn(self) then - self.on_spawn_run = true -- if true, set flag to run once only + self.on_spawn_run = true end end @@ -4279,105 +288,70 @@ local mob_activate = function(self, staticdata, def, dtime) if not self._run_armor_init and self.wears_armor then self.armor_list={helmet="",chestplate="",boots="",leggings=""} - set_armor_texture(self) + self:set_armor_texture() self._run_armor_init = true end - - -- run after_activate if def.after_activate then - def.after_activate(self, staticdata, def, dtime) end end -local function check_aggro(self,dtime) - if not self._aggro or not self.attack then return end - if not self._check_aggro_timer or self._check_aggro_timer > 5 then - self._check_aggro_timer = 0 - 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" - end - end - self._check_aggro_timer = self._check_aggro_timer + dtime -end + -- main mob function -local mob_step = function(self, dtime) +function mob_class:on_step(dtime) self.lifetimer = self.lifetimer - dtime - local pos = self.object:get_pos() - -- Despawning: when lifetimer expires, remove mob - if remove_far - and self.can_despawn == true - and ((not self.nametag) or (self.nametag == "")) - and self.state ~= "attack" - and self.following == nil then - if self.despawn_immediately or self.lifetimer <= 0 then - if spawn_logging then - minetest.log("action", "[mcl_mobs] Mob "..self.name.." despawns at "..minetest.pos_to_string(pos, 1) .. " lifetimer ran out") - end - mcl_burning.extinguish(self.object) - self.object:remove() - return - elseif self.lifetimer <= 10 then - if random(10) < 4 then - self.despawn_immediately = true - else - self.lifetimer = 20 - end - end - end + if not pos then return end + if self:check_despawn(pos) then return true end + + local d = 0.85 + if self:check_dying() then d = 0.92 end local v = self.object:get_velocity() - local d = 0.85 - - if (self.state and self.state=="die" or check_for_death(self)) and not self.animation.die_end then - d = 0.92 - local rot = self.object:get_rotation() - rot.z = ((pi/2-rot.z)*.2)+rot.z - self.object:set_rotation(rot) - end - - if not player_in_active_range(self) then - set_animation(self, "stand", true) - local node_under = node_ok(vector.offset(pos,0,-1,0)).name - local acc = self.object:get_acceleration() - if acc.y > 0 or node_under ~= "air" then - self.object:set_acceleration(vector.new(0,0,0)) - self.object:set_velocity(vector.new(0,0,0)) - end - if acc.y == 0 and node_under == "air" then - falling(self, pos) - end - return - end - if v then --diffuse object velocity self.object:set_velocity({x = v.x*d, y = v.y, z = v.z*d}) end - check_item_pickup(self) - check_aggro(self,dtime) - particlespawner_check(self,dtime) + if self:falling(pos) then return end + + self:check_suspend() + self:check_water_flow() + + local yaw = 0 + if self:is_at_water_danger() and self.state ~= "attack" then + if math.random(1, 10) <= 6 then + self:set_velocity(0) + self.state = "stand" + self:set_animation( "stand") + yaw = yaw + math.random(-0.5, 0.5) + yaw = self:set_yaw( yaw, 8) + end + else + if self.move_in_group ~= false then + self:check_herd(dtime) + end + end + + if self:is_at_cliff_or_danger() 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) + end + if not self.fire_resistant then mcl_burning.tick(self.object, dtime, self) -- mcl_burning.tick may remove object immediately if not self.object:get_pos() then return end end - local yaw = 0 + if mobs_debug then self:update_tag() end - if mobs_debug then - update_tag(self) - end - - if self.state == "die" then - return - end + if self.state == "die" then return end if self.jump_sound_cooloff > 0 then self.jump_sound_cooloff = self.jump_sound_cooloff - dtime @@ -4385,153 +359,22 @@ local mob_step = function(self, dtime) if self.opinion_sound_cooloff > 0 then self.opinion_sound_cooloff = self.opinion_sound_cooloff - dtime end - if falling(self, pos) then - -- Return if mob died after falling - return - end --Mob following code. - follow_flop(self) - + self:follow_flop() --set animation speed relitive to velocity - local v = self.object:get_velocity() - if v then - if self.frame_speed_multiplier then - local v2 = abs(v.x)+abs(v.z)*.833 - if not self.animation.walk_speed then - self.animation.walk_speed = 25 - end - if abs(v.x)+abs(v.z) > 0.5 then - self.object:set_animation_frame_speed((v2/math.max(1,self.run_velocity))*self.animation.walk_speed*self.frame_speed_multiplier) - else - self.object:set_animation_frame_speed(25) - end - end + self:set_animation_speed() + self:check_smooth_rotation(dtime) + self:check_head_swivel(dtime) - --set_speed - if self.acc then - self.object:add_velocity(self.acc) - end - end - - - -- smooth rotation by ThomasMonroe314 - if self._turn_to then - set_yaw(self, 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 = abs(yaw - self.target_yaw) - - if yaw > self.target_yaw then - - if dif > pi then - dif = 2 * 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 > pi then - dif = 2 * pi - dif - yaw = yaw - dif / self.delay -- need to subtract - else - yaw = yaw + dif / self.delay -- need to add - end - end - - if yaw > (pi * 2) then yaw = yaw - (pi * 2) end - if yaw < 0 then yaw = yaw + (pi * 2) end - end - - self.delay = self.delay - 1 - if self.shaking then - yaw = yaw + (random() * 2 - 1) * 5 * dtime - end - self.object:set_yaw(yaw) - update_roll(self) - end - - -- end rotation - - if self.head_swivel and type(self.head_swivel) == "string" then - local final_rotation = vector.new(0,0,0) - local oldp,oldr = self.object:get_bone_position(self.head_swivel) - - for _, obj in pairs(minetest.get_objects_inside_radius(pos, 10)) do - if obj:is_player() and not self.attack or obj:get_luaentity() and obj:get_luaentity().name == self.name and self ~= obj:get_luaentity() then - if not self._locked_object then - if math.random(5000/self.curiosity) == 1 or vector.distance(pos,obj:get_pos())<4 and obj:is_player() then - self._locked_object = obj - end - else - if math.random(10000/self.curiosity) == 1 then - self._locked_object = nil - end - end - end - end - - if self.attack or self.following then - self._locked_object = self.attack or self.following - end - - if self._locked_object and (self._locked_object:is_player() or self._locked_object:get_luaentity()) and self._locked_object:get_hp() > 0 then - local _locked_object_eye_height = 1.5 - if self._locked_object:get_luaentity() then - _locked_object_eye_height = self._locked_object:get_luaentity().head_eye_height - end - if self._locked_object:is_player() then - _locked_object_eye_height = self._locked_object:get_properties().eye_height - end - if _locked_object_eye_height then - local self_rot = self.object:get_rotation() - if self.object:get_attach() then - self_rot = self.object:get_attach():get_rotation() - end - if self.rot then - local player_pos = self._locked_object:get_pos() - local direction_player = vector.direction(vector.add(self.object:get_pos(), vector.new(0, self.head_eye_height*.7, 0)), vector.add(player_pos, vector.new(0, _locked_object_eye_height, 0))) - local mob_yaw = math.deg(-(-(self_rot.y)-(-minetest.dir_to_yaw(direction_player))))+self.head_yaw_offset - local mob_pitch = math.deg(-dir_to_pitch(direction_player))*self.head_pitch_multiplier - - if (mob_yaw < -60 or mob_yaw > 60) and not (self.attack and self.state == "attack" and not self.runaway) then - final_rotation = vector.multiply(oldr, 0.9) - elseif self.attack and self.state == "attack" and not self.runaway then - if self.head_yaw == "y" then - final_rotation = vector.new(mob_pitch, mob_yaw, 0) - elseif self.head_yaw == "z" then - final_rotation = vector.new(mob_pitch, 0, -mob_yaw) - end - - else - - if self.head_yaw == "y" then - final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, ((mob_yaw-oldr.y)*.3)+oldr.y, 0) - elseif self.head_yaw == "z" then - final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, 0, -(((mob_yaw-oldr.y)*.3)+oldr.y)*3) - end - end - end - end - elseif not self._locked_object and math.abs(oldr.y) > 3 and math.abs(oldr.x) < 3 then - final_rotation = vector.multiply(oldr, 0.9) - else - final_rotation = vector.new(0,0,0) - end - - mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horrizonatal_head_height), final_rotation) - - end + self:do_jump() + self:set_armor_texture() + self:check_runaway_from() + self:monster_attack() + self:npc_attack() + self:check_breeding() + self:check_aggro(dtime) -- run custom function (defined in mob lua file) if self.do_custom then @@ -4557,9 +400,10 @@ local mob_step = function(self, dtime) if self.timer < 1 then return end - self.timer = 0 end + self:check_particlespawners(dtime) + self:check_item_pickup() -- never go over 100 if self.timer > 100 then @@ -4567,8 +411,8 @@ local mob_step = function(self, dtime) end -- mob plays random sound at times - if random(1, 70) == 1 then - mob_sound(self, "random", true) + if math.random(1, 70) == 1 then + self:mob_sound("random", true) end -- environmental damage timer (every 1 second) @@ -4576,783 +420,25 @@ local mob_step = function(self, dtime) if (self.state == "attack" and self.env_damage_timer > 1) or self.state ~= "attack" then - check_entity_cramming(self) + self:check_entity_cramming() self.env_damage_timer = 0 -- check for environmental damage (water, fire, lava etc.) - if do_env_damage(self) then + if self:do_env_damage() then return end -- node replace check (cow eats grass etc.) - replace(self, pos) + self:replace(pos) end - monster_attack(self) - - npc_attack(self) - - breed(self) - - if do_states(self, dtime) then + if self:do_states(dtime) then return end if not self.object:get_luaentity() then return false end - - do_jump(self) - - set_armor_texture(self) - - runaway_from(self) - - if is_at_water_danger(self) and self.state ~= "attack" then - if random(1, 10) <= 6 then - set_velocity(self, 0) - self.state = "stand" - set_animation(self, "stand") - yaw = yaw + random(-0.5, 0.5) - yaw = set_yaw(self, yaw, 8) - end - else - if self.move_in_group ~= false then - check_herd(self,dtime) - end - end - - -- 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 - - -- Move item around on flowing liquids - if def and def.liquidtype == "flowing" then - - --[[ Get flowing direction (function call from flowlib), if there's a liquid. - NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7. - Luckily, this is exactly what we need if we only care about water, which has this flowing distance. ]] - local vec = flowlib.quick_flow(p, node) - -- Just to make sure we don't manipulate the speed for no reason - if vec.x ~= 0 or vec.y ~= 0 or vec.z ~= 0 then - -- Minecraft Wiki: Flowing speed is "about 1.39 meters per second" - 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.physical_state = true - self._flowing = true - self.object:set_properties({ - physical = true - }) - return - end - elseif self._flowing == true then - -- Disable flowing physics if not on/in flowing liquid - self._flowing = false - enable_physics(self.object, self, true) - return - end - - if is_at_cliff_or_danger(self) then - set_velocity(self, 0) - self.state = "stand" - set_animation(self, "stand") - local yaw = self.object:get_yaw() or 0 - yaw = set_yaw(self, yaw + 0.78, 8) - end -end - - --- default function when mobs are blown up with TNT -local do_tnt = function(obj, damage) - - obj.object:punch(obj.object, 1.0, { - full_punch_interval = 1.0, - damage_groups = {fleshy = damage}, - }, nil) - - return false, true, {} -end - - -mcl_mobs.spawning_mobs = {} - --- Code to execute before custom on_rightclick handling -local on_rightclick_prefix = function(self, clicker) - local item = clicker:get_wielded_item() - - -- Name mob with nametag - if not self.ignores_nametag and item:get_name() == "mcl_mobs:nametag" then - - local tag = item:get_meta():get_string("name") - if tag ~= "" then - if string.len(tag) > MAX_MOB_NAME_LENGTH then - tag = string.sub(tag, 1, MAX_MOB_NAME_LENGTH) - end - self.nametag = tag - - update_tag(self) - - if not minetest.is_creative_enabled(clicker:get_player_name()) then - item:take_item() - clicker:set_wielded_item(item) - end - return true - end - - end - return false -end - -local create_mob_on_rightclick = function(on_rightclick) - return function(self, clicker) - local stop = on_rightclick_prefix(self, clicker) - if (not stop) and (on_rightclick) then - on_rightclick(self, clicker) - end - end -end - --- register mob entity -function mcl_mobs:register_mob(name, def) - - mcl_mobs.spawning_mobs[name] = true - -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 function scale_difficulty(value, default, min, special) - if (not value) or (value == default) or (value == special) then - return default - else - return max(min, value * difficulty) - end -end - -local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25} --- Workaround for : --- Increase upper Y limit to avoid mobs glitching through solid nodes. --- FIXME: Remove workaround if it's no longer needed. -if collisionbox[5] < 0.79 then - collisionbox[5] = 0.79 -end - -minetest.register_entity(name, { - - use_texture_alpha = def.use_texture_alpha, - head_swivel = def.head_swivel or nil, -- bool to activate this function - head_yaw_offset = def.head_yaw_offset or 0, -- for wonkey model bones - head_pitch_multiplier = def.head_pitch_multiplier or 1, --for inverted pitch - bone_eye_height = def.bone_eye_height or 1.4, -- head bone offset - head_eye_height = def.head_eye_height or def.bone_eye_height or 0, -- how hight aproximatly the mobs head is fromm the ground to tell the mob how high to look up at the player - curiosity = def.curiosity or 1, -- how often mob will look at player on idle - head_yaw = def.head_yaw or "y", -- axis to rotate head on - horrizonatal_head_height = def.horrizonatal_head_height or 0, - wears_armor = def.wears_armor, -- a number value used to index texture slot for armor - stepheight = def.stepheight or 0.6, - name = name, - description = def.description, - type = def.type, - attack_type = def.attack_type, - fly = def.fly, - fly_in = def.fly_in or {"air", "__airlike"}, - owner = def.owner or "", - order = def.order or "", - on_die = def.on_die, - spawn_small_alternative = def.spawn_small_alternative, - do_custom = def.do_custom, - detach_child = def.detach_child, - jump_height = def.jump_height or 4, -- was 6 - rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2 - lifetimer = def.lifetimer or 57.73, - hp_min = scale_difficulty(def.hp_min, 5, 1), - hp_max = scale_difficulty(def.hp_max, 10, 1), - xp_min = def.xp_min or 0, - xp_max = def.xp_max or 0, - xp_timestamp = 0, - breath_max = def.breath_max or 15, - breathes_in_water = def.breathes_in_water or false, - physical = true, - collisionbox = collisionbox, - selectionbox = def.selectionbox or def.collisionbox, - visual = def.visual, - visual_size = def.visual_size or {x = 1, y = 1}, - mesh = def.mesh, - makes_footstep_sound = def.makes_footstep_sound or false, - view_range = def.view_range or 16, - walk_velocity = def.walk_velocity or 1, - run_velocity = def.run_velocity or 2, - damage = scale_difficulty(def.damage, 0, 0), - light_damage = def.light_damage or 0, - sunlight_damage = def.sunlight_damage or 0, - water_damage = def.water_damage or 0, - lava_damage = def.lava_damage or 8, - fire_damage = def.fire_damage or 1, - suffocation = def.suffocation or true, - fall_damage = def.fall_damage or 1, - fall_speed = def.fall_speed or DEFAULT_FALL_SPEED, -- must be lower than -2 - drops = def.drops or {}, - armor = def.armor or 100, - on_rightclick = create_mob_on_rightclick(def.on_rightclick), - arrow = def.arrow, - shoot_interval = def.shoot_interval, - sounds = def.sounds or {}, - animation = def.animation or {}, - follow = def.follow, - nofollow = def.nofollow, - can_open_doors = def.can_open_doors, - jump = def.jump ~= false, - automatic_face_movement_max_rotation_per_sec = 300, - walk_chance = def.walk_chance or 50, - attacks_monsters = def.attacks_monsters or false, - group_attack = def.group_attack or false, - passive = def.passive or false, - knock_back = def.knock_back ~= false, - shoot_offset = def.shoot_offset or 0, - floats = def.floats or 1, -- floats in water by default - floats_on_lava = def.floats_on_lava or 0, - replace_rate = def.replace_rate, - replace_what = def.replace_what, - replace_with = def.replace_with, - replace_offset = def.replace_offset or 0, - on_replace = def.on_replace, - timer = 0, - env_damage_timer = 0, - tamed = false, - pause_timer = 0, - horny = false, - hornytimer = 0, - gotten = false, - health = 0, - frame_speed_multiplier = 1, - reach = def.reach or 3, - htimer = 0, - texture_list = def.textures, - child_texture = def.child_texture, - docile_by_day = def.docile_by_day or false, - time_of_day = 0.5, - fear_height = def.fear_height or 0, - runaway = def.runaway, - runaway_timer = 0, - pathfinding = def.pathfinding, - immune_to = def.immune_to or {}, - explosion_radius = def.explosion_radius, -- LEGACY - explosion_damage_radius = def.explosion_damage_radius, -- LEGACY - explosiontimer_reset_radius = def.explosiontimer_reset_radius, - explosion_timer = def.explosion_timer or 3, - allow_fuse_reset = def.allow_fuse_reset ~= false, - stop_to_explode = def.stop_to_explode ~= false, - custom_attack = def.custom_attack, - double_melee_attack = def.double_melee_attack, - dogshoot_switch = def.dogshoot_switch, - dogshoot_count = 0, - dogshoot_count_max = def.dogshoot_count_max or 5, - dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5), - attack_animals = def.attack_animals or false, - attack_npcs = def.attack_npcs or false, - specific_attack = def.specific_attack, - runaway_from = def.runaway_from, - owner_loyal = def.owner_loyal, - facing_fence = false, - is_mob = true, - pushable = def.pushable or true, - - - -- MCL2 extensions - shooter_avoid_enemy = def.shooter_avoid_enemy, - strafes = def.strafes, - avoid_distance = def.avoid_distance or 9, - teleport = teleport, - do_teleport = def.do_teleport, - spawn_class = def.spawn_class, - can_spawn = def.can_spawn, - ignores_nametag = def.ignores_nametag or false, - rain_damage = def.rain_damage or 0, - glow = def.glow, - can_despawn = can_despawn, - child = def.child or false, - texture_mods = {}, - shoot_arrow = def.shoot_arrow, - sounds_child = def.sounds_child, - _child_animations = def.child_animations, - pick_up = def.pick_up, - explosion_strength = def.explosion_strength, - suffocation_timer = 0, - follow_velocity = def.follow_velocity or 2.4, - instant_death = def.instant_death or false, - fire_resistant = def.fire_resistant or false, - fire_damage_resistant = def.fire_damage_resistant or false, - ignited_by_sunlight = def.ignited_by_sunlight or false, - spawn_in_group = def.spawn_in_group, - spawn_in_group_min = def.spawn_in_group_min, - noyaw = def.noyaw or false, - particlespawners = def.particlespawners, - -- End of MCL2 extensions - - on_spawn = def.on_spawn, - - on_blast = def.on_blast or do_tnt, - - on_step = mob_step, - - do_punch = def.do_punch, - - on_punch = mob_punch, - - on_breed = def.on_breed, - - on_grown = def.on_grown, - - on_pick_up = def.on_pick_up, - - on_detach_child = mob_detach_child, - - on_activate = function(self, staticdata, dtime) - --this is a temporary hack so mobs stop - --glitching and acting really weird with the - --default built in engine collision detection - self.is_mob = true - self.object:set_properties({ - collide_with_objects = false, - }) - - return mob_activate(self, staticdata, def, dtime) - end, - - get_staticdata = function(self) - return mob_staticdata(self) - end, - - harmed_by_heal = def.harmed_by_heal, - - on_lightning_strike = def.on_lightning_strike -}) - -if minetest.get_modpath("doc_identifier") ~= nil then - doc.sub.identifier.register_object(name, "basics", "mobs") -end - -end -- END mcl_mobs:register_mob function - - --- register arrow for shoot attack -function mcl_mobs:register_arrow(name, def) - - if not name or not def then return end -- errorcheck - - minetest.register_entity(name, { - - physical = false, - visual = def.visual, - visual_size = def.visual_size, - textures = def.textures, - velocity = def.velocity, - hit_player = def.hit_player, - hit_node = def.hit_node, - hit_mob = def.hit_mob, - hit_object = def.hit_object, - drop = def.drop or false, -- drops arrow as registered item when true - collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows - timer = 0, - switch = 0, - owner_id = def.owner_id, - rotate = def.rotate, - on_punch = function(self) - local vel = self.object:get_velocity() - self.object:set_velocity({x=vel.x * -1, y=vel.y * -1, z=vel.z * -1}) - end, - collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0}, - automatic_face_movement_dir = def.rotate - and (def.rotate - (pi / 180)) or false, - - on_activate = def.on_activate, - - on_step = def.on_step or function(self, dtime) - - self.timer = self.timer + 1 - - local pos = self.object:get_pos() - - if self.switch == 0 - or self.timer > 150 - or not within_limits(pos, 0) then - mcl_burning.extinguish(self.object) - self.object:remove(); - - return - end - - -- does arrow have a tail (fireball) - if def.tail - and def.tail == 1 - and def.tail_texture then - - minetest.add_particle({ - pos = pos, - velocity = {x = 0, y = 0, z = 0}, - acceleration = {x = 0, y = 0, z = 0}, - expirationtime = def.expire or 0.25, - collisiondetection = false, - texture = def.tail_texture, - size = def.tail_size or 5, - glow = def.glow or 0, - }) - end - - if self.hit_node then - - local node = node_ok(pos).name - - if minetest.registered_nodes[node].walkable then - - self.hit_node(self, pos, node) - - if self.drop == true then - - pos.y = pos.y + 1 - - self.lastpos = (self.lastpos or pos) - - minetest.add_item(self.lastpos, self.object:get_luaentity().name) - end - - self.object:remove(); - - return - end - end - - if self.hit_player or self.hit_mob or self.hit_object then - - for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do - - if self.hit_player - and player:is_player() then - - self.hit_player(self, player) - self.object:remove(); - return - end - - local entity = player:get_luaentity() - - if entity - and self.hit_mob - and entity.is_mob == true - and tostring(player) ~= self.owner_id - and entity.name ~= self.object:get_luaentity().name then - self.hit_mob(self, player) - self.object:remove(); - return - end - - if entity - and self.hit_object - and (not entity.is_mob) - and tostring(player) ~= self.owner_id - and entity.name ~= self.object:get_luaentity().name then - self.hit_object(self, player) - self.object:remove(); - return - end - end - end - - self.lastpos = pos - end - }) -end - - --- no damage to nodes explosion -function mcl_mobs:safe_boom(self, pos, strength) - minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", { - pos = pos, - gain = 1.0, - max_hear_distance = self.sounds and self.sounds.distance or 32 - }, true) - local radius = strength - entity_physics(pos, radius) - effect(pos, 32, "mcl_particles_smoke.png", radius * 3, radius * 5, radius, 1, 0) -end - - --- make explosion with protection and tnt mod check -function mcl_mobs:boom(self, pos, strength, fire) - if mobs_griefing and not minetest.is_protected(pos, "") then - mcl_explosions.explode(pos, strength, { drop_chance = 1.0, fire = fire }, self.object) - else - mcl_mobs:safe_boom(self, pos, strength) - end - - -- delete the object after it punched the player to avoid nil entities in e.g. mcl_shields!! - self.object:remove() -end - - --- Register spawn eggs - --- Note: This also introduces the “spawn_egg” group: --- * spawn_egg=1: Spawn egg (generic mob, no metadata) --- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata) -function mcl_mobs:register_egg(mob, desc, background_color, overlay_color, addegg, no_creative) - - local grp = {spawn_egg = 1} - - -- do NOT add this egg to creative inventory (e.g. dungeon master) - if no_creative == true then - grp.not_in_creative_inventory = 1 - end - - local invimg = "(spawn_egg.png^[multiply:" .. background_color ..")^(spawn_egg_overlay.png^[multiply:" .. overlay_color .. ")" - if old_spawn_icons then - local mobname = mob:gsub("mobs_mc:","") - local fn = "mobs_mc_spawn_icon_"..mobname..".png" - if mcl_util.file_exists(minetest.get_modpath("mobs_mc").."/textures/"..fn) then - invimg = fn - end - end - if addegg == 1 then - invimg = "mobs_chicken_egg.png^(" .. invimg .. - "^[mask:mobs_chicken_egg_overlay.png)" - end - - -- register old stackable mob egg - minetest.register_craftitem(mob, { - - description = desc, - inventory_image = invimg, - groups = grp, - - _doc_items_longdesc = S("This allows you to place a single mob."), - _doc_items_usagehelp = S("Just place it where you want the mob to appear. Animals will spawn tamed, unless you hold down the sneak key while placing. If you place this on a mob spawner, you change the mob it spawns."), - - on_place = function(itemstack, placer, pointed_thing) - - local pos = pointed_thing.above - - -- am I clicking on something with existing on_rightclick function? - local under = minetest.get_node(pointed_thing.under) - local def = minetest.registered_nodes[under.name] - if def and def.on_rightclick then - return def.on_rightclick(pointed_thing.under, under, placer, itemstack) - end - - if pos - and within_limits(pos, 0) - and not minetest.is_protected(pos, placer:get_player_name()) then - - local name = placer:get_player_name() - local privs = minetest.get_player_privs(name) - if under.name == "mcl_mobspawners:spawner" then - if minetest.is_protected(pointed_thing.under, name) then - minetest.record_protection_violation(pointed_thing.under, name) - return itemstack - end - if not privs.maphack then - minetest.chat_send_player(name, S("You need the “maphack” privilege to change the mob spawner.")) - return itemstack - end - mcl_mobspawners.setup_spawner(pointed_thing.under, itemstack:get_name()) - if not minetest.is_creative_enabled(name) then - itemstack:take_item() - end - return itemstack - end - - if not minetest.registered_entities[mob] then - return itemstack - end - - if minetest.settings:get_bool("only_peaceful_mobs", false) - and minetest.registered_entities[mob].type == "monster" then - minetest.chat_send_player(name, S("Only peaceful mobs allowed!")) - return itemstack - end - - pos.y = pos.y - 0.5 - - local mob = minetest.add_entity(pos, mob) - local entityname = itemstack:get_name() - minetest.log("action", "Player " ..name.." spawned "..entityname.." at "..minetest.pos_to_string(pos)) - local ent = mob:get_luaentity() - - -- don't set owner if monster or sneak pressed - if ent.type ~= "monster" - and not placer:get_player_control().sneak then - ent.owner = placer:get_player_name() - ent.tamed = true - end - - -- set nametag - local nametag = itemstack:get_meta():get_string("name") - if nametag ~= "" then - if string.len(nametag) > MAX_MOB_NAME_LENGTH then - nametag = string.sub(nametag, 1, MAX_MOB_NAME_LENGTH) - end - ent.nametag = nametag - update_tag(ent) - end - - -- if not in creative then take item - if not minetest.is_creative_enabled(placer:get_player_name()) then - itemstack:take_item() - end - end - - return itemstack - end, - }) - -end - - --- No-op in MCL2 (capturing mobs is not possible). --- Provided for compability with Mobs Redo -function mcl_mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith) - return false -end - - --- No-op in MCL2 (protecting mobs is not possible). -function mcl_mobs:protect(self, clicker) - return false -end - - --- feeding, taming and breeding (thanks blert2112) -function mcl_mobs:feed_tame(self, clicker, feed_count, breed, tame, notake) - if not self.follow then - return false - end - -- can eat/tame with item in hand - if self.nofollow or follow_holding(self, clicker) then - local consume_food = false - - -- tame if not still a baby - - if tame and not self.child then - if not self.owner or self.owner == "" then - self.tamed = true - self.owner = clicker:get_player_name() - consume_food = true - end - end - - -- increase health - - if self.health < self.hp_max and not consume_food then - consume_food = true - self.health = min(self.health + 4, self.hp_max) - - if self.htimer < 1 then - self.htimer = 5 - end - self.object:set_hp(self.health) - end - - -- make children grow quicker - - if not consume_food and self.child == true then - consume_food = true - -- deduct 10% of the time to adulthood - self.hornytimer = self.hornytimer + ((CHILD_GROW_TIME - self.hornytimer) * 0.1) - end - - -- breed animals - - if breed and not consume_food and self.hornytimer == 0 and not self.horny then - self.food = (self.food or 0) + 1 - consume_food = true - if self.food >= feed_count then - self.food = 0 - self.horny = true - end - end - - update_tag(self) - -- play a sound if the animal used the item and take the item if not in creative - if consume_food then - -- don't consume food if clicker is in creative - if not minetest.is_creative_enabled(clicker:get_player_name()) and not notake then - local item = clicker:get_wielded_item() - item:take_item() - clicker:set_wielded_item(item) - end - -- always play the eat sound if food is used, even in creative - mob_sound(self, "eat", nil, true) - - else - -- make sound when the mob doesn't want food - mob_sound(self, "random", true) - end - return true - end - return false -end - --- Spawn a child -function mcl_mobs:spawn_child(pos, mob_type) - local child = minetest.add_entity(pos, mob_type) - if not child then - return - end - - local ent = child:get_luaentity() - effect(pos, 15, "mcl_particles_smoke.png", 1, 2, 2, 15, 5) - - ent.child = true - - local textures - -- using specific child texture (if found) - if ent.child_texture then - textures = ent.child_texture[1] - end - - -- and resize to half height - child:set_properties({ - textures = textures, - visual_size = { - x = ent.base_size.x * .5, - y = ent.base_size.y * .5, - }, - collisionbox = { - ent.base_colbox[1] * .5, - ent.base_colbox[2] * .5, - ent.base_colbox[3] * .5, - ent.base_colbox[4] * .5, - ent.base_colbox[5] * .5, - ent.base_colbox[6] * .5, - }, - selectionbox = { - ent.base_selbox[1] * .5, - ent.base_selbox[2] * .5, - ent.base_selbox[3] * .5, - ent.base_selbox[4] * .5, - ent.base_selbox[5] * .5, - ent.base_selbox[6] * .5, - }, - }) - - ent.animation = ent._child_animations - ent._current_animation = nil - set_animation(ent, "stand") - - return child end local timer = 0 @@ -5364,10 +450,29 @@ minetest.register_globalstep(function(dtime) for _, obj in pairs(minetest.get_objects_inside_radius(pos, 47)) do local lua = obj:get_luaentity() if lua and lua.is_mob then - lua.lifetimer = max(20, lua.lifetimer) + lua.lifetimer = math.max(20, lua.lifetimer) lua.despawn_immediately = false end end end timer = 0 end) + +minetest.register_chatcommand("clearmobs",{ + privs={maphack=true}, + params = "||", + description=S("Removes all spawned mobs except nametagged and tamed ones. all removes all mobs, nametagged only nametagged ones and with the range paramter all mobs in a distance of the current player are removed."), + func=function(n,param) + local p = minetest.get_player_by_name(n) + local num=tonumber(param) + for _,o in pairs(minetest.luaentities) do + if o.is_mob then + if param == "all" or + ( param == "nametagged" and o.nametag ) or + ( param == "" and ( not o.nametag or o.nametag == "" ) and not o.tamed ) or + ( num and num > 0 and vector.distance(p:get_pos(),o.object:get_pos()) <= num ) then + o.object:remove() + end + end + end +end}) diff --git a/mods/ENTITIES/mcl_mobs/breeding.lua b/mods/ENTITIES/mcl_mobs/breeding.lua new file mode 100644 index 000000000..9284163cd --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/breeding.lua @@ -0,0 +1,352 @@ +local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs +local mob_class = mcl_mobs.mob_class + +local MAX_MOB_NAME_LENGTH = 30 +local HORNY_TIME = 30 +local HORNY_AGAIN_TIME = 300 +local CHILD_GROW_TIME = 60*20 + +local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager",false) + +local LOG_MODULE = "[mcl_mobs]" +local function mcl_log (message) + if LOGGING_ON and message then + minetest.log(LOG_MODULE .. " " .. message) + end +end + +-- No-op in MCL2 (capturing mobs is not possible). +-- Provided for compability with Mobs Redo +function mcl_mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith) + return false +end + + +-- No-op in MCL2 (protecting mobs is not possible). +function mcl_mobs:protect(self, clicker) + return false +end + + +-- feeding, taming and breeding (thanks blert2112) +function mob_class:feed_tame(clicker, feed_count, breed, tame, notake) + if not self.follow then + return false + end + -- can eat/tame with item in hand + if self.nofollow or self:follow_holding(clicker) then + local consume_food = false + + -- tame if not still a baby + + if tame and not self.child then + if not self.owner or self.owner == "" then + self.tamed = true + self.owner = clicker:get_player_name() + consume_food = true + end + end + + -- increase health + + if self.health < self.hp_max and not consume_food then + consume_food = true + self.health = math.min(self.health + 4, self.hp_max) + + if self.htimer < 1 then + self.htimer = 5 + end + self.object:set_hp(self.health) + end + + -- make children grow quicker + + if not consume_food and self.child == true then + consume_food = true + -- deduct 10% of the time to adulthood + self.hornytimer = self.hornytimer + ((CHILD_GROW_TIME - self.hornytimer) * 0.1) + end + + -- breed animals + + if breed and not consume_food and self.hornytimer == 0 and not self.horny then + self.food = (self.food or 0) + 1 + consume_food = true + if self.food >= feed_count then + self.food = 0 + self.horny = true + end + end + + self:update_tag() + -- play a sound if the animal used the item and take the item if not in creative + if consume_food then + -- don't consume food if clicker is in creative + if not minetest.is_creative_enabled(clicker:get_player_name()) and not notake then + local item = clicker:get_wielded_item() + item:take_item() + clicker:set_wielded_item(item) + end + -- always play the eat sound if food is used, even in creative + self:mob_sound("eat", nil, true) + + else + -- make sound when the mob doesn't want food + self:mob_sound("random", true) + end + return true + end + return false +end + +-- Spawn a child +function mcl_mobs.spawn_child(pos, mob_type) + local child = minetest.add_entity(pos, mob_type) + if not child then + return + end + + local ent = child:get_luaentity() + mcl_mobs.effect(pos, 15, "mcl_particles_smoke.png", 1, 2, 2, 15, 5) + + ent.child = true + + local textures + -- using specific child texture (if found) + if ent.child_texture then + textures = ent.child_texture[1] + end + + -- and resize to half height + child:set_properties({ + textures = textures, + visual_size = { + x = ent.base_size.x * .5, + y = ent.base_size.y * .5, + }, + collisionbox = { + ent.base_colbox[1] * .5, + ent.base_colbox[2] * .5, + ent.base_colbox[3] * .5, + ent.base_colbox[4] * .5, + ent.base_colbox[5] * .5, + ent.base_colbox[6] * .5, + }, + selectionbox = { + ent.base_selbox[1] * .5, + ent.base_selbox[2] * .5, + ent.base_selbox[3] * .5, + ent.base_selbox[4] * .5, + ent.base_selbox[5] * .5, + ent.base_selbox[6] * .5, + }, + }) + + ent.animation = ent._child_animations + ent._current_animation = nil + ent:set_animation("stand") + + return child +end + +-- find two animals of same type and breed if nearby and horny +function mob_class:check_breeding() + + --mcl_log("In breed function") + -- child takes a long time before growing into adult + if self.child == true then + + -- When a child, hornytimer is used to count age until adulthood + self.hornytimer = self.hornytimer + 1 + + if self.hornytimer >= CHILD_GROW_TIME then + + self.child = false + self.hornytimer = 0 + + self.object:set_properties({ + textures = self.base_texture, + mesh = self.base_mesh, + visual_size = self.base_size, + collisionbox = self.base_colbox, + selectionbox = self.base_selbox, + }) + + -- custom function when child grows up + if self.on_grown then + self.on_grown(self) + else + -- jump when fully grown so as not to fall into ground + self.object:set_velocity({ + x = 0, + y = self.jump_height*3, + z = 0 + }) + end + + self.animation = nil + local anim = self._current_animation + self._current_animation = nil -- Mobs Redo does nothing otherwise + self:set_animation(anim) + end + + return + end + + -- horny animal can mate for HORNY_TIME seconds, + -- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds + if self.horny == true + and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then + + self.hornytimer = self.hornytimer + 1 + + if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then + self.hornytimer = 0 + self.horny = false + end + end + + -- find another same animal who is also horny and mate if nearby + if self.horny == true + and self.hornytimer <= HORNY_TIME then + + mcl_log("In breed function. All good. Do the magic.") + + local pos = self.object:get_pos() + + mcl_mobs.effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1) + + local objs = minetest.get_objects_inside_radius(pos, 3) + local num = 0 + local ent = nil + + for n = 1, #objs do + + ent = objs[n]:get_luaentity() + + -- check for same animal with different colour + local canmate = false + + if ent then + + if ent.name == self.name then + canmate = true + else + local entname = string.split(ent.name,":") + local selfname = string.split(self.name,":") + + if entname[1] == selfname[1] then + entname = string.split(entname[2],"_") + selfname = string.split(selfname[2],"_") + + if entname[1] == selfname[1] then + canmate = true + end + end + end + end + + if canmate then mcl_log("In breed function. Can mate.") end + + if ent + and canmate == true + and ent.horny == true + and ent.hornytimer <= HORNY_TIME then + num = num + 1 + end + + -- found your mate? then have a baby + if num > 1 then + + self.hornytimer = HORNY_TIME + 1 + ent.hornytimer = HORNY_TIME + 1 + + -- spawn baby + + + minetest.after(5, function(parent1, parent2, pos) + if not parent1.object:get_luaentity() then + return + end + if not parent2.object:get_luaentity() then + return + end + + mcl_experience.throw_xp(pos, math.random(1, 7)) + + -- custom breed function + if parent1.on_breed then + -- when false, skip going any further + if parent1.on_breed(parent1, parent2) == false then + return + end + end + + local child = mcl_mobs.spawn_child(pos, parent1.name) + + local ent_c = child:get_luaentity() + + + -- Use texture of one of the parents + local p = math.random(1, 2) + if p == 1 then + ent_c.base_texture = parent1.base_texture + else + ent_c.base_texture = parent2.base_texture + end + child:set_properties({ + textures = ent_c.base_texture + }) + + -- tamed and owned by parents' owner + ent_c.tamed = true + ent_c.owner = parent1.owner + end, self, ent, pos) + + num = 0 + + break + end + end + end +end + +function mob_class:toggle_sit(clicker,p) + if not self.tamed or self.child or self.owner ~= clicker:get_player_name() then + return + end + local pos = self.object:get_pos() + local particle + if not self.order or self.order == "" or self.order == "sit" then + particle = "mobs_mc_wolf_icon_roam.png" + self.order = "roam" + self.state = "stand" + self.walk_chance = default_walk_chance + self.jump = true + self:set_animation("stand") + -- TODO: Add sitting model + else + particle = "mobs_mc_wolf_icon_sit.png" + self.order = "sit" + self.state = "stand" + self.walk_chance = 0 + self.jump = false + if self.animation.sit_start then + self:set_animation("sit") + else + self:set_animation("stand") + end + end + local pp = vector.new(0,1.4,0) + if p then pp = vector.offset(pp,0,p,0) end + -- Display icon to show current order (sit or roam) + minetest.add_particle({ + pos = vector.add(pos, pp), + velocity = {x=0,y=0.2,z=0}, + expirationtime = 1, + size = 4, + texture = particle, + playername = self.owner, + glow = minetest.LIGHT_MAX, + }) +end diff --git a/mods/ENTITIES/mcl_mobs/combat.lua b/mods/ENTITIES/mcl_mobs/combat.lua new file mode 100644 index 000000000..d8508b3c4 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/combat.lua @@ -0,0 +1,796 @@ +local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs +local mob_class = mcl_mobs.mob_class + +local damage_enabled = minetest.settings:get_bool("enable_damage") +local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false + +-- pathfinding settings +local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching +local stuck_path_timeout = 10 -- how long will mob follow path before giving up + +-- 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 +end + +-- attack player/mob +function mob_class:do_attack(player) + + if self.state == "attack" or self.state == "die" then + return + end + + self.attack = player + self.state = "attack" + + -- TODO: Implement war_cry sound without being annoying + --if random(0, 100) < 90 then + --self:mob_sound("war_cry", true) + --end +end + +-- blast damage to entities nearby +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) + if dist < 1 then dist = 1 end + + local damage = math.floor((4 / dist) * radius) + local ent = objs[n]:get_luaentity() + + -- punches work on entities AND players + objs[n]:punch(objs[n], 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = damage}, + }, pos) + end +end + +function mob_class:entity_physics(self,pos,radius) return entity_physics(pos,radius) end + +local los_switcher = false +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 + 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} + + 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) + + -- 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 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 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 has_lineofsight then self.path.following = false end + end, self) + end + + if math.abs(vector.subtract(s,target_pos).y) > self.stepheight then + + if height_switcher then + use_pathfind = true + height_switcher = false + end + else + if not height_switcher then + use_pathfind = false + height_switcher = true + end + end + + if use_pathfind then + -- lets try find a path, first take care of positions + -- since pathfinder is very sensitive + local sheight = self.collisionbox[5] - self.collisionbox[2] + + -- 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) + + local ssight, sground = minetest.line_of_sight(s, { + x = s.x, y = s.y - 4, z = s.z}, 1) + + -- determine node above ground + 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) + + local dropheight = 12 + if self.fear_height ~= 0 then dropheight = self.fear_height end + local jumpheight = 0 + if self.jump and self.jump_height >= 4 then + jumpheight = math.min(math.ceil(self.jump_height / 4), 4) + elseif self.stepheight > 0.5 then + jumpheight = 1 + end + self.path.way = minetest.find_path(s, p1, 16, jumpheight, dropheight, "A*_noprefetch") + + self.state = "" + self:do_attack(self.attack) + + -- 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 + + -- 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] + + if node1 ~= "air" + and node1 ~= "ignore" + and ndef1 + 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}) + + 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) + } + + 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" + and ndef1 + and not ndef1.groups.level + and not ndef1.groups.unbreakable + and not ndef1.groups.liquid then + + minetest.add_item(p1, ItemStack(node1)) + minetest.set_node(p1, {name = "air"}) + end + + p1.y = p1.y + 1 + node1 = node_ok(p1, "air").name + ndef1 = minetest.registered_nodes[node1] + + if node1 ~= "air" + and node1 ~= "ignore" + and ndef1 + and not ndef1.groups.level + and not ndef1.groups.unbreakable + and not ndef1.groups.liquid then + + minetest.add_item(p1, ItemStack(node1)) + minetest.set_node(p1, {name = "air"}) + end + + end + end + end + + -- will try again in 2 seconds + self.path.stuck_timer = stuck_timeout - 2 + elseif s.y < p1.y and (not self.fly) then + self:do_jump() --add jump to pathfinding + self.path.following = true + -- Yay, I found path! + -- TODO: Implement war_cry sound without being annoying + --self:mob_sound("war_cry", true) + else + self:set_velocity(self.walk_velocity) + + -- follow path now that it has it + self.path.following = true + end + end +end + + +-- specific attacks +local specific_attack = function(list, what) + + -- no list so attack default (player, animals etc.) + 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 + 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 + + local s = self.object:get_pos() + local p, sp, dist + local player, obj, min_player + local type, name = "", "" + local min_dist = self.view_range + 1 + local objs = minetest.get_objects_inside_radius(s, self.view_range) + local blacklist_attack = {} + + for n = 1, #objs do + if not objs[n]:is_player() then + obj = objs[n]:get_luaentity() + + if obj then + player = obj.object + name = obj.name or "" + end + if obj and obj.type == self.type and obj.passive == false and obj.state == "attack" and obj.attack then + table.insert(blacklist_attack, obj.attack) + end + end + end + + for n = 1, #objs do + + + if objs[n]:is_player() then + if mcl_mobs.invis[ objs[n]:get_player_name() ] or (not self:object_in_range(objs[n])) then + type = "" + elseif (self.type == "monster" or self._aggro) then + player = objs[n] + type = "player" + name = "player" + end + else + obj = objs[n]:get_luaentity() + + if obj then + player = obj.object + type = obj.type + name = obj.name or "" + end + + end + + -- find specific mob to attack, failing that attack player/npc/animal + if specific_attack(self.specific_attack, name) + and (type == "player" or ( type == "npc" and self.attack_npcs ) + or (type == "animal" and self.attack_animals == true)) then + + p = player:get_pos() + sp = s + + dist = vector.distance(p, s) + + -- aim higher to make looking up hills more realistic + p.y = p.y + 1 + sp.y = sp.y + 1 + + local attacked_p = false + for c=1, #blacklist_attack do + if blacklist_attack[c] == player then + attacked_p = true + end + end + -- choose closest player to attack + if dist < min_dist + and not attacked_p + and self:line_of_sight( sp, p, 2) == true then + min_dist = dist + min_player = player + end + end + end + if not min_player and #blacklist_attack > 0 then + min_player=blacklist_attack[math.random(#blacklist_attack)] + end + -- attack player + if min_player then + self:do_attack(min_player) + end +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 + return + end + + local p, sp, obj, min_player + local s = self.object:get_pos() + local min_dist = self.view_range + 1 + local objs = minetest.get_objects_inside_radius(s, self.view_range) + + for n = 1, #objs do + + obj = objs[n]:get_luaentity() + + if obj and obj.type == "monster" then + + p = obj.object:get_pos() + sp = s + + local dist = vector.distance(p, s) + + -- aim higher to make looking up hills more realistic + p.y = p.y + 1 + sp.y = sp.y + 1 + + if dist < min_dist + and self:line_of_sight( sp, p, 2) == true then + min_dist = dist + min_player = obj.object + end + end + end + + if min_player then + self:do_attack(min_player) + end +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 + + 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 + + self.dogshoot_count = 0 + + if self.dogshoot_switch == 1 then + self.dogshoot_switch = 2 + else + self.dogshoot_switch = 1 + end + end + + return self.dogshoot_switch +end + +-- no damage to nodes explosion +function mob_class:safe_boom(pos, strength) + minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", { + pos = pos, + gain = 1.0, + max_hear_distance = self.sounds and self.sounds.distance or 32 + }, true) + local radius = strength + entity_physics(pos, radius) + mcl_mobs.effect(pos, 32, "mcl_particles_smoke.png", radius * 3, radius * 5, radius, 1, 0) +end + + +-- make explosion with protection and tnt mod check +function mob_class:boom(pos, strength, fire) + if mobs_griefing and not minetest.is_protected(pos, "") then + mcl_explosions.explode(pos, strength, { drop_chance = 1.0, fire = fire }, self.object) + else + mcl_mobs.mob_class.safe_boom(self, pos, strength) --need to call it this way bc self is the "arrow" object here + end + + -- delete the object after it punched the player to avoid nil entities in e.g. mcl_shields!! + self.object:remove() +end + +-- deal damage and effects when mob punched +function mob_class:on_punch(hitter, tflp, tool_capabilities, dir) + + -- 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 + end + end + + -- error checking when mod profiling is enabled + if not tool_capabilities then + minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled") + return + end + + local is_player = hitter:is_player() + + if is_player then + -- is mob protected? + if self.protected and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then + return + 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 = minetest.get_us_time() + end + + + -- punch interval + local weapon = hitter:get_wielded_item() + local punch_interval = 1.4 + + -- exhaust attacker + if is_player then + mcl_hunger.exhaust(hitter:get_player_name(), mcl_hunger.EXHAUST_ATTACK) + end + + -- calculate mob damage + local damage = 0 + local armor = self.object:get_armor_groups() or {} + local tmp + + -- quick error check incase it ends up 0 (serialize.h check test) + if tflp == 0 then + tflp = 0.2 + end + + + 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) + end + + if weapon then + local fire_aspect_level = mcl_enchanting.get_enchantment(weapon, "fire_aspect") + if fire_aspect_level > 0 then + mcl_burning.set_on_fire(self.object, fire_aspect_level * 4) + end + end + + -- 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 + end + + -- healing + if damage <= -1 then + self.health = self.health - math.floor(damage) + return + end + + if tool_capabilities then + punch_interval = tool_capabilities.full_punch_interval or 1.4 + end + + -- add weapon wear manually + -- Required because we have custom health handling ("health" property) + if minetest.is_creative_enabled("") ~= true + and tool_capabilities then + if tool_capabilities.punch_attack_uses then + -- Without this delay, the wear does not work. Quite hacky ... + minetest.after(0, function(name) + local player = minetest.get_player_by_name(name) + if not player then return end + 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) + weapon:add_wear(wear) + hitter:set_wielded_item(weapon) + end + end, hitter:get_player_name()) + end + end + + 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) + + minetest.sound_play(weapon:get_definition().sounds[s], { + object = self.object, --hitter, + max_hear_distance = 8 + }, true) + else + minetest.sound_play("default_punch", { + object = self.object, + max_hear_distance = 5 + }, true) + end + + self:damage_effect(damage) + + -- do damage + self.health = self.health - damage + + -- skip future functions if dead, except alerting others + if self:check_for_death( "hit", {type = "punch", puncher = hitter}) then + die = true + end + end + -- knock back effect (only on full punch) + if self.knock_back + and tflp >= punch_interval then + -- direction error check + dir = dir or {x = 0, y = 0, z = 0} + + 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 up = 2 + + if die==true then + kb=kb*2 + 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 + + + -- check if tool already has specific knockback value + if tool_capabilities.damage_groups["knockback"] then + kb = tool_capabilities.damage_groups["knockback"] + else + kb = kb * 1.5 + end + + + local luaentity + if hitter then + luaentity = hitter:get_luaentity() + end + if hitter and is_player then + local wielditem = hitter:get_wielded_item() + kb = kb + 3 * mcl_enchanting.get_enchantment(wielditem, "knockback") + elseif luaentity and luaentity._knockback then + kb = kb + luaentity._knockback + end + self._kb_turn = true + self._turn_to=self.object:get_yaw()-1.57 + self.frame_speed_multiplier=2.3 + if self.animation.run_end then + self:set_animation( "run") + elseif self.animation.walk_end then + self:set_animation( "walk") + end + minetest.after(0.2, function() + if self and self.object then + self.frame_speed_multiplier=1 + self._kb_turn = false + end + end) + self.object:add_velocity({ + x = dir.x * kb, + y = up*2, + z = dir.z * kb + }) + + self.pause_timer = 0.25 + end + end -- END if damage + + -- 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()))) + 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) + end + end) + self.state = "runaway" + self.runaway_timer = 0 + self.following = nil + end + + local name = hitter:get_player_name() or "" + + -- attack puncher and call other mobs for help + if self.passive == false + and self.state ~= "flop" + and (self.child == false or self.type == "monster") + and hitter:get_player_name() ~= self.owner + and not mcl_mobs.invis[ name ] then + if not die then + -- attack whoever punched mob + self.state = "" + self:do_attack(hitter) + self._aggro= true + end + + -- alert others to the attack + local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range) + local obj = nil + + for n = 1, #objs do + + obj = objs[n]:get_luaentity() + + if obj then + -- only alert members of same mob or friends + if obj.group_attack + and obj.state ~= "attack" + and obj.owner ~= name then + if obj.name == self.name then + obj:do_attack(hitter) + elseif type(obj.group_attack) == "table" then + for i=1, #obj.group_attack do + if obj.name == obj.group_attack[i] then + obj._aggro = true + obj:do_attack(hitter) + break + end + end + end + end + + -- have owned mobs attack player threat + if obj.owner == name and obj.owner_loyal then + obj:do_attack(self.object) + end + end + end + end +end + +function mob_class:check_aggro(dtime) + if not self._aggro or not self.attack then return end + if not self._check_aggro_timer or self._check_aggro_timer > 5 then + self._check_aggro_timer = 0 + 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" + end + end + self._check_aggro_timer = self._check_aggro_timer + dtime +end diff --git a/mods/ENTITIES/mcl_mobs/compat.lua b/mods/ENTITIES/mcl_mobs/compat.lua new file mode 100644 index 000000000..020a089b2 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/compat.lua @@ -0,0 +1,32 @@ + +-- this is to make the register_mob and register egg functions commonly used by mods not break +-- when they use the weird old : notation AND self as first argument +local oldregmob = mcl_mobs.register_mob +function mcl_mobs.register_mob(self,name,def) + if type(self) == "string" then + def = name + name = self + end + return oldregmob(name,def) +end +local oldregegg = mcl_mobs.register_egg +function mcl_mobs.register_egg(self, mob, desc, background_color, overlay_color, addegg, no_creative) + if type(self) == "string" then + no_creative = addegg + addegg = overlay_color + overlay_color = background_color + background_color = desc + desc = mob + mob = self + end + return oldregegg(mob, desc, background_color, overlay_color, addegg, no_creative) +end + +local oldregarrow = mcl_mobs.register_mob +function mcl_mobs.register_mob(self,name,def) + if type(self) == "string" then + def = name + name = self + end + return oldregarrow(name,def) +end diff --git a/mods/ENTITIES/mcl_mobs/effects.lua b/mods/ENTITIES/mcl_mobs/effects.lua new file mode 100644 index 000000000..476c13469 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/effects.lua @@ -0,0 +1,384 @@ +local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs +local mob_class = mcl_mobs.mob_class +local active_particlespawners = {} +local disable_blood = minetest.settings:get_bool("mobs_disable_blood") +local DEFAULT_FALL_SPEED = -9.81*1.5 + +local player_transfer_distance = tonumber(minetest.settings:get("player_transfer_distance")) or 128 +if player_transfer_distance == 0 then player_transfer_distance = math.huge end + + +-- custom particle effects +function mcl_mobs.effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, go_down) + + radius = radius or 2 + min_size = min_size or 0.5 + max_size = max_size or 1 + gravity = gravity or DEFAULT_FALL_SPEED + glow = glow or 0 + go_down = go_down or false + + local ym + if go_down then + ym = 0 + else + ym = -radius + end + + minetest.add_particlespawner({ + amount = amount, + time = 0.25, + minpos = pos, + maxpos = pos, + minvel = {x = -radius, y = ym, z = -radius}, + maxvel = {x = radius, y = radius, z = radius}, + minacc = {x = 0, y = gravity, z = 0}, + maxacc = {x = 0, y = gravity, z = 0}, + minexptime = 0.1, + maxexptime = 1, + minsize = min_size, + maxsize = max_size, + texture = texture, + glow = glow, + }) +end + +function mcl_mobs.death_effect(pos, yaw, collisionbox, rotate) + local min, max + if collisionbox then + min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]} + max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]} + else + min = { x = -0.5, y = 0, z = -0.5 } + max = { x = 0.5, y = 0.5, z = 0.5 } + end + if rotate then + min = vector.rotate(min, {x=0, y=yaw, z=math.pi/2}) + max = vector.rotate(max, {x=0, y=yaw, z=math.pi/2}) + min, max = vector.sort(min, max) + min = vector.multiply(min, 0.5) + max = vector.multiply(max, 0.5) + end + + minetest.add_particlespawner({ + amount = 50, + time = 0.001, + minpos = vector.add(pos, min), + maxpos = vector.add(pos, max), + minvel = vector.new(-5,-5,-5), + maxvel = vector.new(5,5,5), + minexptime = 1.1, + maxexptime = 1.5, + minsize = 1, + maxsize = 2, + collisiondetection = false, + vertical = false, + texture = "mcl_particles_mob_death.png^[colorize:#000000:255", + }) + + minetest.sound_play("mcl_mobs_mob_poof", { + pos = pos, + gain = 1.0, + max_hear_distance = 8, + }, true) +end + + +-- play sound +function mob_class:mob_sound(soundname, is_opinion, fixed_pitch) + + local soundinfo + if self.sounds_child and self.child then + soundinfo = self.sounds_child + elseif self.sounds then + soundinfo = self.sounds + end + if not soundinfo then + return + end + local sound = soundinfo[soundname] + if sound then + if is_opinion and self.opinion_sound_cooloff > 0 then + return + end + local pitch + if not fixed_pitch then + local base_pitch = soundinfo.base_pitch + if not base_pitch then + base_pitch = 1 + end + if self.child and (not self.sounds_child) then + -- Children have higher pitch + pitch = base_pitch * 1.5 + else + pitch = base_pitch + end + -- randomize the pitch a bit + pitch = pitch + math.random(-10, 10) * 0.005 + end + minetest.sound_play(sound, { + object = self.object, + gain = 1.0, + max_hear_distance = self.sounds.distance, + pitch = pitch, + }, true) + self.opinion_sound_cooloff = 1 + end +end + +function mob_class:add_texture_mod(mod) + local full_mod = "" + local already_added = false + for i=1, #self.texture_mods do + if mod == self.texture_mods[i] then + already_added = true + end + full_mod = full_mod .. self.texture_mods[i] + end + if not already_added then + full_mod = full_mod .. mod + table.insert(self.texture_mods, mod) + end + self.object:set_texture_mod(full_mod) +end + +function mob_class:remove_texture_mod(mod) + local full_mod = "" + local remove = {} + for i=1, #self.texture_mods do + if self.texture_mods[i] ~= mod then + full_mod = full_mod .. self.texture_mods[i] + else + table.insert(remove, i) + end + end + for i=#remove, 1 do + table.remove(self.texture_mods, remove[i]) + end + self.object:set_texture_mod(full_mod) +end + +function mob_class:damage_effect(damage) + -- damage particles + if (not disable_blood) and damage > 0 then + + local amount_large = math.floor(damage / 2) + local amount_small = damage % 2 + + local pos = self.object:get_pos() + + pos.y = pos.y + (self.collisionbox[5] - self.collisionbox[2]) * .5 + + local texture = "mobs_blood.png" + -- full heart damage (one particle for each 2 HP damage) + if amount_large > 0 then + mcl_mobs.effect(pos, amount_large, texture, 2, 2, 1.75, 0, nil, true) + end + -- half heart damage (one additional particle if damage is an odd number) + if amount_small > 0 then + -- TODO: Use "half heart" + mcl_mobs.effect(pos, amount_small, texture, 1, 1, 1.75, 0, nil, true) + end + end +end + +function mob_class:remove_particlespawners(pn) + if not active_particlespawners[pn] then return end + if not active_particlespawners[pn][self.object] then return end + for k,v in pairs(active_particlespawners[pn][self.object]) do + minetest.delete_particlespawner(v) + end +end + +function mob_class:add_particlespawners(pn) + if not active_particlespawners[pn] then active_particlespawners[pn] = {} end + if not active_particlespawners[pn][self.object] then active_particlespawners[pn][self.object] = {} end + for _,ps in pairs(self.particlespawners) do + ps.attached = self.object + ps.playername = pn + table.insert(active_particlespawners[pn][self.object],minetest.add_particlespawner(ps)) + end +end + +function mob_class:check_particlespawners(dtime) + if not self.particlespawners then return end + --minetest.log(dump(active_particlespawners)) + if self._particle_timer and self._particle_timer >= 1 then + self._particle_timer = 0 + local players = {} + for _,player in pairs(minetest.get_connected_players()) do + local pn = player:get_player_name() + table.insert(players,pn) + if not active_particlespawners[pn] then + active_particlespawners[pn] = {} end + + local dst = vector.distance(player:get_pos(),self.object:get_pos()) + if dst < player_transfer_distance and not active_particlespawners[pn][self.object] then + self:add_particlespawners(pn) + elseif dst >= player_transfer_distance and active_particlespawners[pn][self.object] then + self:remove_particlespawners(pn) + end + end + elseif not self._particle_timer then + self._particle_timer = 0 + end + self._particle_timer = self._particle_timer + dtime +end + + +-- set defined animation +function mob_class:set_animation(anim, fixed_frame) + if not self.animation or not anim then + return + end + if self.state == "die" and anim ~= "die" and anim ~= "stand" then + return + end + + if self.jockey then + anim = "jockey" + end + + + if self:flight_check() and self.fly and anim == "walk" then anim = "fly" end + + self._current_animation = self._current_animation or "" + + if (anim == self._current_animation + or not self.animation[anim .. "_start"] + or not self.animation[anim .. "_end"]) and self.state ~= "die" then + return + end + + 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 + if a_start and a_end then + self.object:set_animation({ + x = a_start, + y = a_end}, + self.animation[anim .. "_speed"] or self.animation.speed_normal or 15, + 0, self.animation[anim .. "_loop"] ~= false) + end +end + +-- above function exported for mount.lua +function mcl_mobs:set_animation(self, anim) + self:set_animation(anim) +end + +local function dir_to_pitch(dir) + --local dir2 = vector.normalize(dir) + local xz = math.abs(dir.x) + math.abs(dir.z) + return -math.atan2(-dir.y, xz) +end + +function mob_class:check_head_swivel(dtime) + if not self.head_swivel or type(self.head_swivel) ~= "string" then return end + local final_rotation = vector.new(0,0,0) + local oldp,oldr = self.object:get_bone_position(self.head_swivel) + + local pos = self.object:get_pos() + + for _, obj in pairs(minetest.get_objects_inside_radius(pos, 10)) do + if obj:is_player() and not self.attack or obj:get_luaentity() and obj:get_luaentity().name == self.name and self ~= obj:get_luaentity() then + if not self._locked_object then + if math.random(5000/self.curiosity) == 1 or vector.distance(pos,obj:get_pos())<4 and obj:is_player() then + self._locked_object = obj + end + else + if math.random(10000/self.curiosity) == 1 then + self._locked_object = nil + end + end + end + end + + if self.attack or self.following then + self._locked_object = self.attack or self.following + end + + if self._locked_object and (self._locked_object:is_player() or self._locked_object:get_luaentity()) and self._locked_object:get_hp() > 0 then + local _locked_object_eye_height = 1.5 + if self._locked_object:get_luaentity() then + _locked_object_eye_height = self._locked_object:get_luaentity().head_eye_height + end + if self._locked_object:is_player() then + _locked_object_eye_height = self._locked_object:get_properties().eye_height + end + if _locked_object_eye_height then + local self_rot = self.object:get_rotation() + if self.object:get_attach() then + self_rot = self.object:get_attach():get_rotation() + end + if self.rot then + local player_pos = self._locked_object:get_pos() + local direction_player = vector.direction(vector.add(self.object:get_pos(), vector.new(0, self.head_eye_height*.7, 0)), vector.add(player_pos, vector.new(0, _locked_object_eye_height, 0))) + local mob_yaw = math.deg(-(-(self_rot.y)-(-minetest.dir_to_yaw(direction_player))))+self.head_yaw_offset + local mob_pitch = math.deg(-dir_to_pitch(direction_player))*self.head_pitch_multiplier + + if (mob_yaw < -60 or mob_yaw > 60) and not (self.attack and self.state == "attack" and not self.runaway) then + final_rotation = vector.multiply(oldr, 0.9) + elseif self.attack and self.state == "attack" and not self.runaway then + if self.head_yaw == "y" then + final_rotation = vector.new(mob_pitch, mob_yaw, 0) + elseif self.head_yaw == "z" then + final_rotation = vector.new(mob_pitch, 0, -mob_yaw) + end + + else + + if self.head_yaw == "y" then + final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, ((mob_yaw-oldr.y)*.3)+oldr.y, 0) + elseif self.head_yaw == "z" then + final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, 0, -(((mob_yaw-oldr.y)*.3)+oldr.y)*3) + end + end + end + end + elseif not self._locked_object and math.abs(oldr.y) > 3 and math.abs(oldr.x) < 3 then + final_rotation = vector.multiply(oldr, 0.9) + else + final_rotation = vector.new(0,0,0) + end + + mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horrizonatal_head_height), final_rotation) +end + +function mob_class:set_animation_speed() + local v = self.object:get_velocity() + if v then + if self.frame_speed_multiplier then + local v2 = math.abs(v.x)+math.abs(v.z)*.833 + if not self.animation.walk_speed then + self.animation.walk_speed = 25 + end + if math.abs(v.x)+math.abs(v.z) > 0.5 then + self.object:set_animation_frame_speed((v2/math.max(1,self.run_velocity))*self.animation.walk_speed*self.frame_speed_multiplier) + else + self.object:set_animation_frame_speed(25) + end + end + --set_speed + if self.acc then + self.object:add_velocity(self.acc) + end + end +end + +minetest.register_on_leaveplayer(function(player) + local pn = player:get_player_name() + if not active_particlespawners[pn] then return end + for _,m in pairs(active_particlespawners[pn]) do + for k,v in pairs(m) do + minetest.delete_particlespawner(v) + end + end + active_particlespawners[pn] = nil +end) diff --git a/mods/ENTITIES/mcl_mobs/init.lua b/mods/ENTITIES/mcl_mobs/init.lua index 69246b470..ed3641dc5 100644 --- a/mods/ENTITIES/mcl_mobs/init.lua +++ b/mods/ENTITIES/mcl_mobs/init.lua @@ -1,14 +1,566 @@ - -local path = minetest.get_modpath(minetest.get_current_modname()) - --- Mob API +mcl_mobs = {} +mcl_mobs.mob_class = {} +mcl_mobs.mob_class_meta = {__index = mcl_mobs.mob_class} +mcl_mobs.registered_mobs = {} +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" +--api and helpers +-- effects: sounds and particles mostly +dofile(path .. "/effects.lua") +-- physics: involuntary mob movement - particularly falling and death +dofile(path .. "/physics.lua") +-- movement: general voluntary mob movement, walking avoiding cliffs etc. +dofile(path .. "/movement.lua") +-- items: item management for mobs +dofile(path .. "/items.lua") +-- pathfinding: pathfinding to target positions +dofile(path .. "/pathfinding.lua") +-- combat: attack logic +dofile(path .. "/combat.lua") +-- the enity functions themselves dofile(path .. "/api.lua") --- Spawning Algorithm + +--utility functions +dofile(path .. "/breeding.lua") dofile(path .. "/spawning.lua") - --- Rideable Mobs dofile(path .. "/mount.lua") +dofile(path .. "/crafts.lua") +dofile(path .. "/compat.lua") --- Mob Items -dofile(path .. "/crafts.lua") \ No newline at end of file +local DEFAULT_FALL_SPEED = -9.81*1.5 +local MAX_MOB_NAME_LENGTH = 30 + +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 +local on_rightclick_prefix = function(self, clicker) + if not clicker:is_player() then return end + local item = clicker:get_wielded_item() + if extended_pet_control and self.tamed and self.owner == clicker:get_player_name() then + self:toggle_sit(clicker) + end + -- Name mob with nametag + if not self.ignores_nametag and item:get_name() == "mcl_mobs:nametag" then + + local tag = item:get_meta():get_string("name") + if tag ~= "" then + if string.len(tag) > MAX_MOB_NAME_LENGTH then + tag = string.sub(tag, 1, MAX_MOB_NAME_LENGTH) + end + self.nametag = tag + + self:update_tag() + + if not minetest.is_creative_enabled(clicker:get_player_name()) then + item:take_item() + clicker:set_wielded_item(item) + end + return true + end + + end + + return false +end + +local create_mob_on_rightclick = function(on_rightclick) + return function(self, clicker) + local stop = on_rightclick_prefix(self, clicker) + if (not stop) and (on_rightclick) then + on_rightclick(self, clicker) + end + end +end + +-- check if within physical map limits (-30911 to 30927) +local function within_limits(pos, radius) + local wmin, wmax = -30912, 30928 + if mcl_vars then + if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then + wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max + end + end + if radius then + wmin = wmin - radius + wmax = wmax + radius + end + for _,v in pairs(pos) do + if v < wmin or v > wmax then return false end + end + return true +end + +mcl_mobs.spawning_mobs = {} +-- register mob entity +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 function scale_difficulty(value, default, min, special) + if (not value) or (value == default) or (value == special) then + return default + else + return math.max(min, value * difficulty) + end + end + + local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25} + -- Workaround for : + -- Increase upper Y limit to avoid mobs glitching through solid nodes. + -- FIXME: Remove workaround if it's no longer needed. + if collisionbox[5] < 0.79 then + collisionbox[5] = 0.79 + end + local final_def = { + use_texture_alpha = def.use_texture_alpha, + head_swivel = def.head_swivel or nil, -- bool to activate this function + head_yaw_offset = def.head_yaw_offset or 0, -- for wonkey model bones + head_pitch_multiplier = def.head_pitch_multiplier or 1, --for inverted pitch + bone_eye_height = def.bone_eye_height or 1.4, -- head bone offset + head_eye_height = def.head_eye_height or def.bone_eye_height or 0, -- how hight aproximatly the mobs head is fromm the ground to tell the mob how high to look up at the player + curiosity = def.curiosity or 1, -- how often mob will look at player on idle + head_yaw = def.head_yaw or "y", -- axis to rotate head on + horrizonatal_head_height = def.horrizonatal_head_height or 0, + wears_armor = def.wears_armor, -- a number value used to index texture slot for armor + stepheight = def.stepheight or 0.6, + name = name, + description = def.description, + type = def.type, + attack_type = def.attack_type, + fly = def.fly or false, + fly_in = def.fly_in or {"air", "__airlike"}, + owner = def.owner or "", + order = def.order or "", + on_die = def.on_die, + spawn_small_alternative = def.spawn_small_alternative, + do_custom = def.do_custom, + detach_child = def.detach_child, + jump_height = def.jump_height or 4, -- was 6 + rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2 + lifetimer = def.lifetimer or 57.73, + hp_min = scale_difficulty(def.hp_min, 5, 1), + hp_max = scale_difficulty(def.hp_max, 10, 1), + xp_min = def.xp_min or 0, + xp_max = def.xp_max or 0, + xp_timestamp = 0, + breath_max = def.breath_max or 15, + breathes_in_water = def.breathes_in_water or false, + physical = true, + collisionbox = collisionbox, + selectionbox = def.selectionbox or def.collisionbox, + visual = def.visual, + visual_size = def.visual_size or {x = 1, y = 1}, + mesh = def.mesh, + makes_footstep_sound = def.makes_footstep_sound or false, + view_range = def.view_range or 16, + walk_velocity = def.walk_velocity or 1, + run_velocity = def.run_velocity or 2, + damage = scale_difficulty(def.damage, 0, 0), + light_damage = def.light_damage or 0, + sunlight_damage = def.sunlight_damage or 0, + water_damage = def.water_damage or 0, + lava_damage = def.lava_damage or 8, + fire_damage = def.fire_damage or 1, + suffocation = def.suffocation or true, + fall_damage = def.fall_damage or 1, + fall_speed = def.fall_speed or DEFAULT_FALL_SPEED, -- must be lower than -2 + drops = def.drops or {}, + armor = def.armor or 100, + on_rightclick = create_mob_on_rightclick(def.on_rightclick), + arrow = def.arrow, + shoot_interval = def.shoot_interval, + sounds = def.sounds or {}, + animation = def.animation or {}, + follow = def.follow, + nofollow = def.nofollow, + can_open_doors = def.can_open_doors, + jump = def.jump ~= false, + automatic_face_movement_max_rotation_per_sec = 300, + walk_chance = def.walk_chance or 50, + attacks_monsters = def.attacks_monsters or false, + group_attack = def.group_attack or false, + passive = def.passive or false, + knock_back = def.knock_back ~= false, + shoot_offset = def.shoot_offset or 0, + floats = def.floats or 1, -- floats in water by default + floats_on_lava = def.floats_on_lava or 0, + replace_rate = def.replace_rate, + replace_what = def.replace_what, + replace_with = def.replace_with, + replace_offset = def.replace_offset or 0, + on_replace = def.on_replace, + timer = 0, + env_damage_timer = 0, + tamed = false, + pause_timer = 0, + horny = false, + hornytimer = 0, + gotten = false, + health = 0, + frame_speed_multiplier = 1, + reach = def.reach or 3, + htimer = 0, + texture_list = def.textures, + child_texture = def.child_texture, + docile_by_day = def.docile_by_day or false, + time_of_day = 0.5, + fear_height = def.fear_height or 0, + runaway = def.runaway, + runaway_timer = 0, + pathfinding = def.pathfinding, + immune_to = def.immune_to or {}, + explosion_radius = def.explosion_radius, -- LEGACY + explosion_damage_radius = def.explosion_damage_radius, -- LEGACY + explosiontimer_reset_radius = def.explosiontimer_reset_radius, + explosion_timer = def.explosion_timer or 3, + allow_fuse_reset = def.allow_fuse_reset ~= false, + stop_to_explode = def.stop_to_explode ~= false, + custom_attack = def.custom_attack, + double_melee_attack = def.double_melee_attack, + dogshoot_switch = def.dogshoot_switch, + dogshoot_count = 0, + dogshoot_count_max = def.dogshoot_count_max or 5, + dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5), + attack_animals = def.attack_animals or false, + attack_npcs = def.attack_npcs or false, + specific_attack = def.specific_attack, + runaway_from = def.runaway_from, + owner_loyal = def.owner_loyal, + facing_fence = false, + is_mob = true, + pushable = def.pushable or true, + + -- MCL2 extensions + shooter_avoid_enemy = def.shooter_avoid_enemy, + strafes = def.strafes, + avoid_distance = def.avoid_distance or 9, + do_teleport = def.do_teleport, + spawn_class = def.spawn_class, + can_spawn = def.can_spawn, + ignores_nametag = def.ignores_nametag or false, + rain_damage = def.rain_damage or 0, + glow = def.glow, + can_despawn = can_despawn, + child = def.child or false, + texture_mods = {}, + shoot_arrow = def.shoot_arrow, + sounds_child = def.sounds_child, + _child_animations = def.child_animations, + pick_up = def.pick_up, + explosion_strength = def.explosion_strength, + suffocation_timer = 0, + follow_velocity = def.follow_velocity or 2.4, + instant_death = def.instant_death or false, + fire_resistant = def.fire_resistant or false, + fire_damage_resistant = def.fire_damage_resistant or false, + ignited_by_sunlight = def.ignited_by_sunlight or false, + spawn_in_group = def.spawn_in_group, + spawn_in_group_min = def.spawn_in_group_min, + noyaw = def.noyaw or false, + particlespawners = def.particlespawners, + -- End of MCL2 extensions + on_spawn = def.on_spawn, + on_blast = def.on_blast or function(self,damage) + self.object:punch(self.object, 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = damage}, + }, nil) + return false, true, {} + end, + do_punch = def.do_punch, + on_breed = def.on_breed, + on_grown = def.on_grown, + on_pick_up = def.on_pick_up, + on_activate = function(self, staticdata, dtime) + --this is a temporary hack so mobs stop + --glitching and acting really weird with the + --default built in engine collision detection + self.is_mob = true + self.object:set_properties({ + collide_with_objects = false, + }) + + return self:mob_activate(staticdata, def, dtime) + end, + harmed_by_heal = def.harmed_by_heal, + on_lightning_strike = def.on_lightning_strike + } + minetest.register_entity(name, setmetatable(final_def,mcl_mobs.mob_class_meta)) + + if minetest.get_modpath("doc_identifier") ~= nil then + doc.sub.identifier.register_object(name, "basics", "mobs") + end + +end -- END mcl_mobs.register_mob function + + +-- register arrow for shoot attack +function mcl_mobs.register_arrow(name, def) + + if not name or not def then return end -- errorcheck + + minetest.register_entity(name, { + + physical = false, + visual = def.visual, + visual_size = def.visual_size, + textures = def.textures, + velocity = def.velocity, + hit_player = def.hit_player, + hit_node = def.hit_node, + hit_mob = def.hit_mob, + hit_object = def.hit_object, + drop = def.drop or false, -- drops arrow as registered item when true + collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows + timer = 0, + switch = 0, + owner_id = def.owner_id, + rotate = def.rotate, + on_punch = function(self) + local vel = self.object:get_velocity() + self.object:set_velocity({x=vel.x * -1, y=vel.y * -1, z=vel.z * -1}) + end, + collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0}, + automatic_face_movement_dir = def.rotate + and (def.rotate - (math.pi / 180)) or false, + + on_activate = def.on_activate, + + on_step = def.on_step or function(self, dtime) + + self.timer = self.timer + 1 + + local pos = self.object:get_pos() + + if self.switch == 0 + or self.timer > 150 + or not within_limits(pos, 0) then + mcl_burning.extinguish(self.object) + self.object:remove(); + + return + end + + -- does arrow have a tail (fireball) + if def.tail + and def.tail == 1 + and def.tail_texture then + + minetest.add_particle({ + pos = pos, + velocity = {x = 0, y = 0, z = 0}, + acceleration = {x = 0, y = 0, z = 0}, + expirationtime = def.expire or 0.25, + collisiondetection = false, + texture = def.tail_texture, + size = def.tail_size or 5, + glow = def.glow or 0, + }) + end + + if self.hit_node then + + local node = node_ok(pos).name + + if minetest.registered_nodes[node].walkable then + + self.hit_node(self, pos, node) + + if self.drop == true then + + pos.y = pos.y + 1 + + self.lastpos = (self.lastpos or pos) + + minetest.add_item(self.lastpos, self.object:get_luaentity().name) + end + + self.object:remove(); + + return + end + end + + if self.hit_player or self.hit_mob or self.hit_object then + + for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do + + if self.hit_player + and player:is_player() then + + self.hit_player(self, player) + self.object:remove(); + return + end + + local entity = player:get_luaentity() + + if entity + and self.hit_mob + and entity.is_mob == true + and tostring(player) ~= self.owner_id + and entity.name ~= self.object:get_luaentity().name then + self.hit_mob(self, player) + self.object:remove(); + return + end + + if entity + and self.hit_object + and (not entity.is_mob) + and tostring(player) ~= self.owner_id + and entity.name ~= self.object:get_luaentity().name then + self.hit_object(self, player) + self.object:remove(); + return + end + end + end + + self.lastpos = pos + end + }) +end + +-- Register spawn eggs + +-- Note: This also introduces the “spawn_egg” group: +-- * spawn_egg=1: Spawn egg (generic mob, no metadata) +-- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata) +function mcl_mobs.register_egg(mob, desc, background_color, overlay_color, addegg, no_creative) + + local grp = {spawn_egg = 1} + + -- do NOT add this egg to creative inventory (e.g. dungeon master) + if no_creative == true then + grp.not_in_creative_inventory = 1 + end + + local invimg = "(spawn_egg.png^[multiply:" .. background_color ..")^(spawn_egg_overlay.png^[multiply:" .. overlay_color .. ")" + if old_spawn_icons then + local mobname = mob:gsub("mobs_mc:","") + local fn = "mobs_mc_spawn_icon_"..mobname..".png" + if mcl_util.file_exists(minetest.get_modpath("mobs_mc").."/textures/"..fn) then + invimg = fn + end + end + if addegg == 1 then + invimg = "mobs_chicken_egg.png^(" .. invimg .. + "^[mask:mobs_chicken_egg_overlay.png)" + end + + -- register old stackable mob egg + minetest.register_craftitem(mob, { + + description = desc, + inventory_image = invimg, + groups = grp, + + _doc_items_longdesc = S("This allows you to place a single mob."), + _doc_items_usagehelp = S("Just place it where you want the mob to appear. Animals will spawn tamed, unless you hold down the sneak key while placing. If you place this on a mob spawner, you change the mob it spawns."), + + on_place = function(itemstack, placer, pointed_thing) + + local pos = pointed_thing.above + + -- am I clicking on something with existing on_rightclick function? + local under = minetest.get_node(pointed_thing.under) + local def = minetest.registered_nodes[under.name] + if def and def.on_rightclick then + return def.on_rightclick(pointed_thing.under, under, placer, itemstack) + end + + if pos + and within_limits(pos, 0) + and not minetest.is_protected(pos, placer:get_player_name()) then + + local name = placer:get_player_name() + local privs = minetest.get_player_privs(name) + if under.name == "mcl_mobspawners:spawner" then + if minetest.is_protected(pointed_thing.under, name) then + minetest.record_protection_violation(pointed_thing.under, name) + return itemstack + end + if not privs.maphack then + minetest.chat_send_player(name, S("You need the “maphack” privilege to change the mob spawner.")) + return itemstack + end + mcl_mobspawners.setup_spawner(pointed_thing.under, itemstack:get_name()) + if not minetest.is_creative_enabled(name) then + itemstack:take_item() + end + return itemstack + end + + if not minetest.registered_entities[mob] then + return itemstack + end + + if minetest.settings:get_bool("only_peaceful_mobs", false) + and minetest.registered_entities[mob].type == "monster" then + minetest.chat_send_player(name, S("Only peaceful mobs allowed!")) + return itemstack + end + + pos.y = pos.y - 0.5 + + local mob = minetest.add_entity(pos, mob) + local entityname = itemstack:get_name() + minetest.log("action", "Player " ..name.." spawned "..entityname.." at "..minetest.pos_to_string(pos)) + local ent = mob:get_luaentity() + + -- don't set owner if monster or sneak pressed + if ent.type ~= "monster" + and not placer:get_player_control().sneak then + ent.owner = placer:get_player_name() + ent.tamed = true + end + + -- set nametag + local nametag = itemstack:get_meta():get_string("name") + if nametag ~= "" then + if string.len(nametag) > MAX_MOB_NAME_LENGTH then + nametag = string.sub(nametag, 1, MAX_MOB_NAME_LENGTH) + end + ent.nametag = nametag + update_tag(ent) + end + + -- if not in creative then take item + if not minetest.is_creative_enabled(placer:get_player_name()) then + itemstack:take_item() + end + end + + return itemstack + end, + }) + +end diff --git a/mods/ENTITIES/mcl_mobs/items.lua b/mods/ENTITIES/mcl_mobs/items.lua new file mode 100644 index 000000000..267dd8595 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/items.lua @@ -0,0 +1,103 @@ +local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs +local mob_class = mcl_mobs.mob_class +--- Item and armor management + +local function player_near(pos) + for _,o in pairs(minetest.get_objects_inside_radius(pos,2)) do + if o:is_player() then return true end + end +end + +local function get_armor_texture(armor_name) + if armor_name == "" then + return "" + end + if armor_name=="blank.png" then + return "blank.png" + end + local seperator = string.find(armor_name, ":") + return "mcl_armor_"..string.sub(armor_name, seperator+1, -1)..".png^" +end + +function mob_class:set_armor_texture() + if self.armor_list then + local chestplate=minetest.registered_items[self.armor_list.chestplate] or {name=""} + local boots=minetest.registered_items[self.armor_list.boots] or {name=""} + local leggings=minetest.registered_items[self.armor_list.leggings] or {name=""} + local helmet=minetest.registered_items[self.armor_list.helmet] or {name=""} + + if helmet.name=="" and chestplate.name=="" and leggings.name=="" and boots.name=="" then + helmet={name="blank.png"} + end + local texture = get_armor_texture(chestplate.name)..get_armor_texture(helmet.name)..get_armor_texture(boots.name)..get_armor_texture(leggings.name) + if string.sub(texture, -1,-1) == "^" then + texture=string.sub(texture,1,-2) + end + if self.textures[self.wears_armor] then + self.textures[self.wears_armor]=texture + end + self.object:set_properties({textures=self.textures}) + + local armor_ + if type(self.armor) == "table" then + armor_ = table.copy(self.armor) + armor_.immortal = 1 + else + armor_ = {immortal=1, fleshy = self.armor} + end + + for _,item in pairs(self.armor_list) do + if not item then return end + if type(minetest.get_item_group(item, "mcl_armor_points")) == "number" then + armor_.fleshy=armor_.fleshy-(minetest.get_item_group(item, "mcl_armor_points")*3.5) + end + end + self.object:set_armor_groups(armor_) + end +end + +function mob_class:check_item_pickup() + if self.pick_up and #self.pick_up > 0 or self.wears_armor then + local p = self.object:get_pos() + if not p then return end + for _,o in pairs(minetest.get_objects_inside_radius(p,2)) do + local l=o:get_luaentity() + if l and l.name == "__builtin:item" then + if not player_near(p) and l.itemstring:find("mcl_armor") and self.wears_armor then + local armor_type + if l.itemstring:find("chestplate") then + armor_type = "chestplate" + elseif l.itemstring:find("boots") then + armor_type = "boots" + elseif l.itemstring:find("leggings") then + armor_type = "leggings" + elseif l.itemstring:find("helmet") then + armor_type = "helmet" + end + if not armor_type then + return + end + if not self.armor_list then + self.armor_list={helmet="",chestplate="",boots="",leggings=""} + elseif self.armor_list[armor_type] and self.armor_list[armor_type] ~= "" then + return + end + self.armor_list[armor_type]=ItemStack(l.itemstring):get_name() + o:remove() + end + if self.pick_up then + for k,v in pairs(self.pick_up) do + if not player_near(p) and self.on_pick_up and l.itemstring:find(v) then + local r = self.on_pick_up(self,l) + if r and r.is_empty and not r:is_empty() then + l.itemstring = r:to_string() + elseif r and r.is_empty and r:is_empty() then + o:remove() + end + end + end + end + end + end + end +end diff --git a/mods/ENTITIES/mcl_mobs/mount.lua b/mods/ENTITIES/mcl_mobs/mount.lua index 95a145a4b..21c52157a 100644 --- a/mods/ENTITIES/mcl_mobs/mount.lua +++ b/mods/ENTITIES/mcl_mobs/mount.lua @@ -1,4 +1,5 @@ - +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) local enable_crash = false @@ -385,7 +386,6 @@ function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime) 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) @@ -446,3 +446,18 @@ function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_an mcl_mobs:set_animation(entity, moving_anim) end end + +mcl_mobs.mob_class.drive = mcl_mobs.drive +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 +end diff --git a/mods/ENTITIES/mcl_mobs/movement.lua b/mods/ENTITIES/mcl_mobs/movement.lua new file mode 100644 index 000000000..3eed56cbe --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/movement.lua @@ -0,0 +1,1404 @@ +local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs +local mob_class = mcl_mobs.mob_class +local DEFAULT_FALL_SPEED = -9.81*1.5 +local FLOP_HEIGHT = 6 +local FLOP_HOR_SPEED = 1.5 +local PATHFINDING = "gowp" +local enable_pathfinding = true + +local node_ice = "mcl_core:ice" +local node_snowblock = "mcl_core:snowblock" +local node_snow = "mcl_core:snow" + + +local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false + +local atann = math.atan +local function atan(x) + if not x or x ~= x then + return 0 + else + return atann(x) + end +end + +-- 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 + +-- 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 +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 +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) + + -- normal walking and flying mobs can see you through air + if s == true then + return true + end + + -- New pos1 to be analyzed + local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z} + + local r, pos = minetest.line_of_sight(npos1, pos2, stepsize) + + -- Checks the return + if r == true then return true end + + -- Nodename found + local nn = minetest.get_node(pos).name + + -- Target Distance (td) to travel + 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 + + -- Check if you can still move forward + if td < ad + stepsize then + return true -- Reached the target + end + + -- Moves the analyzed pos + 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 + npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z + + -- NaN checks + if d == 0 + or npos1.x ~= npos1.x + or npos1.y ~= npos1.y + or npos1.z ~= npos1.z then + return false + end + + ad = ad + stepsize + + -- scan again + r, pos = minetest.line_of_sight(npos1, pos2, stepsize) + + if r == true then return true end + + -- New Nodename found + nn = minetest.get_node(pos).name + + end + + return false +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 + + -- 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 + + --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 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) + + then + --disable fear heigh while we make our jump + self._jumping_cliff = true + minetest.after(1, function() + if self and self.object then + self._jumping_cliff = false + end + end) + return true + else + return false + end +end + +-- is mob facing a cliff or danger +function mob_class:is_at_cliff_or_danger() + if self.fear_height == 0 or self:can_jump_cliff() or self._jumping_cliff or not self.object:get_luaentity() then -- 0 for no falling protection! + 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 pos = self.object:get_pos() + local ypos = pos.y + self.collisionbox[2] -- just above floor + + local free_fall, blocker = minetest.line_of_sight( + {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z}, + {x = pos.x + dir_x, y = ypos - self.fear_height, z = 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 + end + + return false +end + + +-- copy the 'mob facing cliff_or_danger check' from above, and rework to avoid water +function mob_class:is_at_water_danger() + if not self.object:get_luaentity() or self:can_jump_cliff() or self._jumping_cliff then + 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 pos = self.object:get_pos() + local ypos = pos.y + self.collisionbox[2] -- just above floor + + local free_fall, blocker = minetest.line_of_sight( + {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z}, + {x = pos.x + dir_x, y = ypos - 3, z = pos.z + dir_z}) + if free_fall then + return true + else + local bnode = minetest.get_node(blocker) + local waterdanger = self:is_node_waterhazard(bnode.name) + if + waterdanger and (self:is_node_waterhazard(self.standing_in) or self:is_node_waterhazard( self.standing_on)) then + return false + elseif waterdanger and (self:is_node_waterhazard(self.standing_in) or self:is_node_waterhazard(self.standing_on)) == false then + return true + else + local def = minetest.registered_nodes[bnode.name] + if def and def.walkable then + return false + end + end + end + + return false +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.child and self.type ~= "monster") + or self.order == "stand" then + return false + end + + self.facing_fence = 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 + + local pos = self.object:get_pos() + 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 + + -- 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 + + -- what is in front of mob? + nod = node_ok({ + x = pos.x + dir_x, + y = pos.y + 0.5, + z = pos.z + dir_z + }) + + -- 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") + + + -- 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 + return false + end + + -- thin blocks that do not need to be jumped + if nod.name == node_snow then + return false + end + + local ndef = minetest.registered_nodes[nod.name] + if self.walk_chance == 0 or ndef and ndef.walkable or self:can_jump_cliff() then + + 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 + + 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 + end + + return false +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 + + local item = clicker:get_wielded_item() + local t = type(self.follow) + + -- single item + 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 + end + end + + return false +end + + +-- find and replace what mob is looking for (grass, wheat etc.) +function mob_class:replace(pos) + + if not self.replace_rate + or not self.replace_what + or self.child == true + or self.object:get_velocity().y ~= 0 + or math.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) + + what = self.replace_what[num][1] or "" + with = self.replace_what[num][2] or "" + y_offset = self.replace_what[num][3] or 0 + else + what = self.replace_what + with = self.replace_with or "" + y_offset = self.replace_offset or 0 + end + + pos.y = pos.y + y_offset + + 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 + + if self.on_replace then + on_replace_return = self.on_replace(self, pos, oldnode, newnode) + end + + if on_replace_return ~= false then + + if mobs_griefing then + minetest.set_node(pos, newnode) + end + + end + end +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 + + -- found entity on list to attack? + for no = 1, #list do + + 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 + + local s = self.object:get_pos() + local p, sp, dist + local player, obj, min_player + local type, name = "", "" + local min_dist = self.view_range + 1 + 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 + type = "" + else + player = objs[n] + type = "player" + name = "player" + end + else + obj = objs[n]:get_luaentity() + + if obj then + player = obj.object + type = obj.type + name = obj.name or "" + end + end + + -- 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) + + + -- choose closest player/mpb to runaway from + if dist < min_dist + and self:line_of_sight(sp, p, 2) == true then + min_dist = dist + min_player = player + end + end + 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.state = "runaway" + self.runaway_timer = 3 + self.following = nil + end +end + + +-- follow player if owner or holding item, if fish outta water then flop +function mob_class:follow_flop() + + -- find player to follow + if (self.follow ~= "" + or self.order == "follow") + and not self.following + and self.state ~= "attack" + and self.order ~= "sit" + and self.state ~= "runaway" then + + local s = self.object:get_pos() + local players = minetest.get_connected_players() + + for n = 1, #players do + + if (self:object_in_range(players[n])) + and not mcl_mobs.invis[ players[n]:get_player_name() ] then + + self.following = players[n] + + break + end + end + end + + if self.type == "npc" + and self.order == "follow" + and self.state ~= "attack" + and self.order ~= "sit" + and self.owner ~= "" then + + -- npc stop following player if not owner + if self.following + and self.owner + and self.owner ~= self.following:get_player_name() then + self.following = nil + end + else + -- stop following player if not holding specific item, + -- mob is horny, fleeing or attacking + if self.following + and self.following:is_player() + and (self:follow_holding(self.following) == false or + self.horny or self.state == "runaway") then + self.following = nil + end + + end + + -- 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 + + if p then + + local dist = vector.distance(p, s) + + -- dont follow if out of range + 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) + + -- anyone but standing npc's can move along + if dist > 3 + and self.order ~= "stand" then + + self:set_velocity(self.follow_velocity) + + if self.walk_chance ~= 0 then + self:set_animation( "run") + end + else + self:set_velocity(0) + self:set_animation( "stand") + end + + return + end + end + end + + -- swimmers flop when out of their element, and swim again when back in + if self.fly then + local s = self.object:get_pos() + if self:flight_check( s) == false then + + self.state = "flop" + self.object:set_acceleration({x = 0, y = DEFAULT_FALL_SPEED, z = 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] + -- 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(-FLOP_HOR_SPEED, FLOP_HOR_SPEED), + y = FLOP_HEIGHT, + z = math.random(-FLOP_HOR_SPEED, FLOP_HOR_SPEED), + }) + end + end + + self:set_animation( "stand", true) + + return + elseif self.state == "flop" then + self.state = "stand" + self.object:set_acceleration({x = 0, y = 0, z = 0}) + self:set_velocity(0) + end + end +end + +function mob_class:go_to_pos(b) + 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") +end + +local check_herd_timer = 0 +function mob_class:check_herd(dtime) + local pos = self.object:get_pos() + if not pos then return end + check_herd_timer = check_herd_timer + dtime + if check_herd_timer < 4 then return end + 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() + else + y = o:get_yaw() + end + if p then + go_to_pos(self,p) + elseif y then + self:set_yaw(y) + end + end + end +end + +function mob_class:teleport(target) + if self.do_teleport then + if self.do_teleport(self, target) == false then + return + end + end +end + +-- execute current state (stand, walk, run, attacks) +-- returns true if mob has died +function mob_class:do_states(dtime) + --if self.can_open_doors then check_doors(self) end + + local yaw = self.object:get_yaw() or 0 + + if self.state == "stand" then + if math.random(1, 4) == 1 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 + 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 + else + yaw = yaw + math.random(-0.5, 0.5) + end + + yaw = self:set_yaw( yaw, 8) + end + if self.order == "sit" then + self:set_animation( "sit") + self:set_velocity(0) + else + self:set_animation( "stand") + self:set_velocity(0) + end + + -- npc's ordered to stand stay standing + if self.order == "stand" or self.order == "sleep" or self.order == "work" then + + else + 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") + end + end + + elseif self.state == PATHFINDING then + self:check_gowp(dtime) + + elseif self.state == "walk" then + 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, 0.5) + yaw = self:set_yaw( yaw, 8) + end + end + + yaw = self:set_yaw( yaw, 8) + + -- otherwise randomly turn + elseif math.random(1, 100) <= 30 then + yaw = yaw + math.random(-0.5, 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") + end + end + + -- runaway when punched + elseif self.state == "runaway" then + + 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) + else + self:set_velocity( self.run_velocity) + self:set_animation( "run") + end + + -- attack routines (explode, dogfight, shoot, dogshoot) + elseif self.state == "attack" then + + local s = self.object:get_pos() + local p = self.attack:get_pos() or s + + -- stop attacking if player invisible or out of range + if not self.attack + or not self.attack:get_pos() + or not self:object_in_range(self.attack) + or self.attack:get_hp() <= 0 + or (self.attack:is_player() and mcl_mobs.invis[ self.attack:get_player_name() ]) then + + self.state = "stand" + self:set_velocity( 0) + self:set_animation( "stand") + self.attack = nil + self.v_start = false + self.timer = 0 + self.blinktimer = 0 + self.path.way = nil + + return + end + + -- calculate distance from mob and enemy + local dist = vector.distance(p, s) + + if self.attack_type == "explode" 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) + + local node_break_radius = self.explosion_radius or 1 + 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 self:line_of_sight( s, p, 2) then + + self.v_start = true + self.timer = 0 + self.blinktimer = 0 + self:mob_sound("fuse", nil, false) + + -- stop timer if out of reach or direct line of sight + elseif self.allow_fuse_reset + and self.v_start + and (dist >= self.explosiontimer_reset_radius + or not self:line_of_sight( s, p, 2)) then + self.v_start = false + self.timer = 0 + self.blinktimer = 0 + self.blinkstatus = false + self:remove_texture_mod("^[brighten") + end + + -- walk right up to player unless the timer is active + if self.v_start and (self.stop_to_explode or dist < self.reach) then + self:set_velocity( 0) + else + self:set_velocity( self.run_velocity) + end + + if self.animation and self.animation.run_start then + self:set_animation( "run") + else + self:set_animation( "walk") + end + + if self.v_start then + + self.timer = self.timer + dtime + self.blinktimer = (self.blinktimer or 0) + dtime + + if self.blinktimer > 0.2 then + + self.blinktimer = 0 + + if self.blinkstatus then + self:remove_texture_mod("^[brighten") + else + self:add_texture_mod("^[brighten") + end + + self.blinkstatus = not self.blinkstatus + end + + if self.timer > self.explosion_timer then + + local pos = self.object:get_pos() + + if mobs_griefing and not minetest.is_protected(pos, "") then + mcl_explosions.explode(mcl_util.get_object_center(self.object), self.explosion_strength, { drop_chance = 1.0 }, self.object) + else + minetest.sound_play(self.sounds.explode, { + pos = pos, + gain = 1.0, + max_hear_distance = self.sounds.distance or 32 + }, true) + self:entity_physics(pos,entity_damage_radius) + mcl_mobs.effect(pos, 32, "mcl_particles_smoke.png", nil, nil, node_break_radius, 1, 0) + end + mcl_burning.extinguish(self.object) + self.object:remove() + + return true + end + end + + elseif self.attack_type == "dogfight" + 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) + 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 + }) + + elseif me_y > p_y then + + self.object:set_velocity({ + x = v.x, + y = -1 * self.walk_velocity, + z = v.z + }) + end + else + if me_y < p_y then + + self.object:set_velocity({ + x = v.x, + y = 0.01, + z = v.z + }) + + elseif me_y > p_y then + + self.object:set_velocity({ + x = v.x, + y = -0.01, + z = v.z + }) + end + end + + end + + -- rnd: new movement direction + 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 + 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 + -- 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} + 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) + + -- move towards enemy if beyond mob reach + if dist > self.reach then + + -- path finding by rnd + if self.pathfinding -- only if mob has pathfinding enabled + and enable_pathfinding then + + self:smart_mobs(s, p, dist, 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) + else + + if self.path.stuck then + self:set_velocity( self.walk_velocity) + else + self:set_velocity( self.run_velocity) + end + + if self.animation and self.animation.run_start then + self:set_animation( "run") + else + self:set_animation( "walk") + end + end + + else -- rnd: if inside reach range + + self.path.stuck = false + self.path.stuck_timer = 0 + self.path.following = false -- not stuck anymore + + self:set_velocity( 0) + + if not self.custom_attack then + + if self.timer > 1 then + + self.timer = 0 + + if self.double_melee_attack + and math.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 + + -- play attack sound + self:mob_sound("attack") + + -- punch player (or what player is attached to) + local attached = self.attack:get_attach() + if attached then + self.attack = attached + end + self.attack:punch(self.object, 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = self.damage} + }, nil) + end + end + else -- call custom attack every second + if self.custom_attack + and self.timer > 1 then + + self.timer = 0 + + self.custom_attack(self, p) + end + end + end + + 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.new(0,0,0) + + --strafe back and fourth + + --stay away from player so as to shoot them + if dist < self.avoid_distance and self.shooter_avoid_enemy then + self:set_animation( "shoot") + stay_away_from_player=vector.multiply(vector.direction(p, s), 0.33) + end + + if self.strafes then + if 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 + self.acc = vector.add(vector.multiply(vector.rotate_around_axis(vector.direction(s, p), vector.new(0,1,0), self.strafe_direction), 0.3*self.walk_velocity), stay_away_from_player) + else + self:set_velocity( 0) + end + + local p = self.object:get_pos() + p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2 + + if self.shoot_interval + and self.timer > self.shoot_interval + and not minetest.raycast(vector.add(p, vector.new(0,self.shoot_offset,0)), vector.add(self.attack:get_pos(), vector.new(0,1.5,0)), false, false):next() + and math.random(1, 100) <= 60 then + + self.timer = 0 + self:set_animation( "shoot") + + -- play shoot attack sound + self:mob_sound("shoot_attack") + + -- Shoot arrow + if minetest.registered_entities[self.arrow] then + + local arrow, ent + local v = 1 + if not self.shoot_arrow then + self.firing = true + minetest.after(1, function() + self.firing = false + end) + arrow = minetest.add_entity(p, self.arrow) + ent = arrow:get_luaentity() + if ent.velocity then + v = ent.velocity + end + ent.switch = 1 + ent.owner_id = tostring(self.object) -- add unique owner id to arrow + + -- important for mcl_shields + ent._shooter = self.object + ent._saved_shooter_pos = self.object:get_pos() + 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) + if self.shoot_arrow then + vec = vector.normalize(vec) + self:shoot_arrow(p, vec) + else + arrow:set_velocity(vec) + end + end + end + else + + end + 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 new file mode 100644 index 000000000..6cb37434f --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/pathfinding.lua @@ -0,0 +1,382 @@ +local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs +local mob_class = mcl_mobs.mob_class + +local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager",false) +local PATHFINDING = "gowp" +local enable_pathfinding = true + +local LOG_MODULE = "[Mobs]" +local function mcl_log (message) + if LOGGING_ON and message then + minetest.log(LOG_MODULE .. " " .. message) + end +end + +function output_table (wp) + if not wp then return end + mcl_log("wp items: ".. tostring(#wp)) + for a,b in pairs(wp) do + mcl_log(a.. ": ".. tostring(b)) + end +end + +function append_paths (wp1, wp2) + mcl_log("Start append") + if not wp1 or not wp2 then + mcl_log("Cannot append wp's") + return + end + output_table(wp1) + output_table(wp2) + for _,a in pairs (wp2) do + table.insert(wp1, a) + end + mcl_log("End append") +end + +local function output_enriched (wp_out) + 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("type: " .. action["type"]) + mcl_log("action: " .. action["action"]) + mcl_log("target: " .. minetest.pos_to_string(action["target"])) + end + mcl_log("failed attempts: " .. outy["failed_attempts"]) + end +end + +-- This function will take a list of paths, and enrich it with: +-- a var for failed attempts +-- 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 = {} + for i, cur_pos in pairs(wp_in) do + local action = nil + + local one_down = vector.new(0,-1,0) + local cur_pos_to_add = vector.add(cur_pos, one_down) + if door_open_pos and vector.equals (cur_pos, door_open_pos) then + mcl_log ("Door open match") + --action = {type = "door", action = "open"} + action = {} + action["type"] = "door" + action["action"] = "open" + action["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 = "closed"} + action = {} + action["type"] = "door" + action["action"] = "close" + action["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") + cur_pos_to_add = vector.add(cur_pos, one_down) + action = {} + action["type"] = "door" + action["action"] = "open" + action["target"] = cur_door_pos + 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]["failed_attempts"] = 0 + wp_out[i]["action"] = action + + --wp_out[i] = {"pos" = cur_pos, "failed_attempts" = 0, "action" = action} + --output_pos(cur_pos, i) + end + output_enriched(wp_out) + return wp_out +end + +local plane_adjacents = { + vector.new(1,0,0), + vector.new(-1,0,0), + vector.new(0,0,1), + vector.new(0,0,-1), +} + +-- 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, t, target) + -- target is the same as t, just 1 square difference. Maybe we don't need target + mcl_log("Plot route from mob: " .. minetest.pos_to_string(p) .. ", to target: " .. minetest.pos_to_string(t)) + + local enriched_path = nil + + local cur_door_pos = nil + local pos_closest_to_door = nil + local other_side_of_door = nil + + --Path to door first + local wp = minetest.find_path(p,t,150,1,4) + if not wp then + mcl_log("No direct path. Path through door") + + -- This could improve. There could be multiple doors. Check you can path from door to target first. + local cur_door_pos = minetest.find_node_near(target,16,{"group:door"}) + if cur_door_pos then + mcl_log("Found a door near: " .. minetest.pos_to_string(cur_door_pos)) + for _,v in pairs(plane_adjacents) do + pos_closest_to_door = vector.add(cur_door_pos,v) + + local n = minetest.get_node(pos_closest_to_door) + if n.name == "air" then + wp = minetest.find_path(p,pos_closest_to_door,150,1,4) + if wp then + mcl_log("Found a path to next to door".. minetest.pos_to_string(pos_closest_to_door)) + other_side_of_door = vector.add(cur_door_pos,-v) + mcl_log("Opposite is: ".. minetest.pos_to_string(other_side_of_door)) + + local wp_otherside_door_to_target = minetest.find_path(other_side_of_door,t,150,1,4) + if wp_otherside_door_to_target and #wp_otherside_door_to_target > 0 then + table.insert(wp, cur_door_pos) + append_paths (wp, wp_otherside_door_to_target) + enriched_path = generate_enriched_path(wp, pos_closest_to_door, other_side_of_door, cur_door_pos) + mcl_log("We have a path from outside door to target") + else + mcl_log("We cannot path from outside door to target") + end + break + else + mcl_log("This block next to door doesn't work.") + end + else + mcl_log("Block is not air, it is: ".. n.name) + end + + end + else + mcl_log("No door found") + end + else + mcl_log("We have a direct route") + end + + if wp and not enriched_path then + enriched_path = generate_enriched_path(wp) + end + return enriched_path +end + +local gopath_last = os.time() +function mob_class:gopath(target,callback_arrived) + if self.state == PATHFINDING then mcl_log("Already pathfinding, don't set another until done.") return end + + if self._pf_last_failed and (os.time() - self._pf_last_failed) < 30 then + mcl_log("We are not ready to path as last fail is less than threshold: " .. (os.time() - self._pf_last_failed)) + return + else + mcl_log("We are ready to pathfind, no previous fail or we are past threshold") + end + + --if os.time() - gopath_last < 5 then + -- mcl_log("Not ready to path yet") + -- return + --end + --gopath_last = os.time() + + self.order = nil + + local p = self.object:get_pos() + local t = vector.offset(target,0,1,0) + + local wp = calculate_path_through_door(p, t, target) + if not wp then + mcl_log("Could not calculate path") + self._pf_last_failed = os.time() + -- Cover for a flaw in pathfind where it chooses the wrong door and gets stuck. Take a break, allow others. + end + --output_table(wp) + + if wp and #wp > 0 then + 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") + end + self.current_target = current_location + self.waypoints = wp + self.state = PATHFINDING + return true + else + self.state = "walk" + self.waypoints = nil + self.current_target = nil + -- minetest.log("no path found") + end +end + +function mob_class:interact_with_door(action, target) + local p = self.object:get_pos() + --local t = minetest.get_timeofday() + --local dd = minetest.find_nodes_in_area(vector.offset(p,-1,-1,-1),vector.offset(p,1,1,1),{"group:door"}) + --for _,d in pairs(dd) do + if target then + mcl_log("Door target is: ".. minetest.pos_to_string(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 + else + mcl_log("Not door") + end + else + mcl_log("no target. cannot try and open or close door") + end + --end +end + +function mob_class:do_pathfind_action(action) + if action then + mcl_log("Action present") + local type = action["type"] + local action_val = action["action"] + local target = action["target"] + if target then + mcl_log("Target: ".. minetest.pos_to_string(target)) + end + if type and type == "door" then + mcl_log("Type is door") + self:interact_with_door(action_val, target) + end + end +end + +local gowp_etime = 0 + +function mob_class:check_gowp(dtime) + gowp_etime = gowp_etime + dtime + + -- 0.1 is optimal. + --less frequently = villager will get sent back after passing a point. + --more frequently = villager will fail points they shouldn't they just didn't get there yet + + --if gowp_etime < 0.05 then return end + --gowp_etime = 0 + local p = self.object:get_pos() + + -- no destination + if not p or not self._target then + mcl_log("p: ".. tostring(p)) + mcl_log("self._target: ".. tostring(self._target)) + return + end + + -- arrived at location, finish gowp + local distance_to_targ = vector.distance(p,self._target) + --mcl_log("Distance to targ: ".. tostring(distance_to_targ)) + if distance_to_targ < 2 then + mcl_log("Arrived at _target") + self.waypoints = nil + self._target = nil + 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}) + if self.callback_arrived then return self.callback_arrived(self) end + return true + end + + -- 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"]) + 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 + -- We have waypoints, and no current target, or we're at it. 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) + + self.current_target = table.remove(self.waypoints, 1) + self:go_to_pos(self.current_target["pos"]) + return + elseif self.current_target and self.current_target["pos"] then + -- No waypoints left, but have current target. 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 >= 50 then + mcl_log("Failed to reach position (" .. minetest.pos_to_string(self.current_target["pos"]) .. ") too many times. Abandon route. Times tried: " .. failed_attempts) + 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}) + 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) + self:go_to_pos(self.current_target["pos"]) + -- Do i just delete current_target, and return so we can find final path. + else + -- Not at target, no current waypoints or current_target. Through the door and should be able to path to target. + -- Is a little sensitive and could take 1 - 7 times. A 10 fail count might be a good exit condition. + + mcl_log("We don't have waypoints or a current target. Let's try to path to target") + local final_wp = minetest.find_path(p,self._target,150,1,4) + if final_wp then + mcl_log("We might be able to get to target here.") + -- self.waypoints = final_wp + self:go_to_pos(self._target) + else + -- Abandon route? + mcl_log("Cannot plot final route to target") + end + end + + -- I don't think we need the following anymore, but test first. + -- Maybe just need something to path to target if no waypoints left + 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"]) + + mcl_log("Distance to current target: ".. tostring(distance_to_cur_targ)) + 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 + mcl_log("not close to current target: ".. minetest.pos_to_string(self.current_target["pos"])) + self:go_to_pos(self._current_target) + else + mcl_log("close to current target: ".. minetest.pos_to_string(self.current_target["pos"])) + self.current_target = nil + end + + return + end +end diff --git a/mods/ENTITIES/mcl_mobs/physics.lua b/mods/ENTITIES/mcl_mobs/physics.lua new file mode 100644 index 000000000..a03e21543 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/physics.lua @@ -0,0 +1,976 @@ +local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs +local mob_class = mcl_mobs.mob_class + +local ENTITY_CRAMMING_MAX = 24 +local CRAMMING_DAMAGE = 3 +local DEATH_DELAY = 0.5 +local DEFAULT_FALL_SPEED = -9.81*1.5 +local FLOP_HEIGHT = 6 +local FLOP_HOR_SPEED = 1.5 +local PATHFINDING = "gowp" +local mobs_debug = minetest.settings:get_bool("mobs_debug", false) +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 + if mcl_vars then + if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then + wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max + end + end + if radius then + wmin = wmin - radius + wmax = wmax + radius + end + if not pos then return true end + for _,v in pairs(pos) do + if v < wmin or v > wmax then return false end + end + return true +end + +function mob_class:player_in_active_range() + for _,p in pairs(minetest.get_connected_players()) do + if vector.distance(self.object:get_pos(),p:get_pos()) <= mob_active_range then return true end + -- slightly larger than the mc 32 since mobs spawn on that circle and easily stand still immediately right after spawning. + end +end + +-- Return true if object is in view_range +function mob_class:object_in_range(object) + if not object then + return false + end + local factor + -- Apply view range reduction for special player armor + if object:is_player() then + local factors = mcl_armor.player_view_range_factors[object] + factor = factors and factors[self.name] + end + -- Distance check + local dist + if factor and factor == 0 then + return false + elseif factor then + dist = self.view_range * factor + else + dist = self.view_range + end + + local p1, p2 = self.object:get_pos(), object:get_pos() + return p1 and p2 and (vector.distance(p1, p2) <= dist) +end + +function mob_class:item_drop(cooked, looting_level) + + if not mobs_drop_items then return end + + looting_level = looting_level or 0 + + if (self.child and self.type ~= "monster") then + return + end + + local obj, item, num + local pos = self.object:get_pos() + + self.drops = self.drops or {} + + for n = 1, #self.drops do + local dropdef = self.drops[n] + local chance = 1 / dropdef.chance + local looting_type = dropdef.looting + + if looting_level > 0 then + local chance_function = dropdef.looting_chance_function + if chance_function then + chance = chance_function(looting_level) + elseif looting_type == "rare" then + chance = chance + (dropdef.looting_factor or 0.01) * looting_level + end + end + + 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) + 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) + end + + if num > 0 then + item = dropdef.name + + if cooked then + + 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 + end + + 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 + end + end + end + + self.drops = {} +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 + local vel = self.object:get_velocity() + local x = 0 + local z = 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) + end + end + + return({x,z}) +end + +-- move mob in facing direction +function mob_class:set_velocity(v) + local c_x, c_y = 0, 0 + + -- can mob be pushed, if so calculate direction + if self.pushable then + c_x, c_y = unpack(self:collision()) + end + + -- halt mob if it has been ordered to stay + if self.order == "stand" or self.order == "sit" then + self.acc=vector.new(0,0,0) + return + end + + local yaw = (self.object:get_yaw() or 0) + self.rotate + local vv = self.object:get_velocity() + if vv then + self.acc={ + x = ((math.sin(yaw) * -v) + c_x)*.27, + y = 0, + z = ((math.cos(yaw) * v) + c_y)*.27, + } + end +end + +-- calculate mob velocity +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 +end + +function mob_class:update_roll() + local is_Fleckenstein = self.nametag == "Fleckenstein" + local was_Fleckenstein = false + + local rot = self.object:get_rotation() + 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 + local pos = self.object:get_pos() + pos.y = pos.y + (acbox[2] + acbox[5]) + 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 + +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 + +end + + + +-- set and return valid yaw +function mob_class:set_yaw(yaw, delay, dtime) + if self.noyaw 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=yaw%360 + elseif math.deg(yaw) < 0 then + yaw=((360*5)-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) > 5 then + self.object:set_yaw(self.object:get_yaw()+(target_shortest_path*(3.6*ddtime))) + if self.acc then + self.acc=vector.rotate_around_axis(self.acc,vector.new(0,1,0), target_shortest_path*(3.6*ddtime)) + end + end + + delay = delay or 0 + + yaw = self.object:get_yaw() + + if delay == 0 then + if self.shaking and dtime then + yaw = yaw + (math.random() * 2 - 1) * 5 * dtime + end + self:update_roll() + return yaw + end + + self.target_yaw = yaw + self.delay = delay + + return self.target_yaw +end + +-- global function to set mob yaw +function mcl_mobs.yaw(self, yaw, delay, dtime) + return mob_class.set_yaw(self, yaw, delay, dtime) +end + +-- are we flying in what we are suppose to? (taikedz) +function mob_class:flight_check() + + local nod = self.standing_in + local def = minetest.registered_nodes[nod] + + if not def then return false end -- nil check + + local fly_in + if type(self.fly_in) == "string" then + fly_in = { self.fly_in } + elseif type(self.fly_in) == "table" then + fly_in = self.fly_in + else + return false + end + + for _,checknode in pairs(fly_in) do + if nod == checknode or nod == "ignore" then + return true + end + end + + return false +end + +-- check if mob is dead or only hurt +function mob_class:check_for_death(cause, cmi_cause) + + if self.state == "die" then + return true + end + + -- has health actually changed? + if self.health == self.old_health and self.health > 0 then + return false + end + + local damaged = self.health < self.old_health + self.old_health = self.health + + -- still got some health? + if self.health > 0 then + + -- make sure health isn't higher than max + if self.health > self.hp_max then + self.health = self.hp_max + end + + -- play damage sound if health was reduced and make mob flash red. + if damaged then + self:add_texture_mod("^[colorize:#d42222:175") + minetest.after(1, function(self) + if self and self.object then + self:remove_texture_mod("^[colorize:#d42222:175") + end + end, self) + self:mob_sound("damage") + end + + -- backup nametag so we can show health stats + if not self.nametag2 then + self.nametag2 = self.nametag or "" + end + + if show_health + and (cmi_cause and cmi_cause.type == "punch") then + + self.htimer = 2 + self.nametag = "♥ " .. self.health .. " / " .. self.hp_max + + self:update_tag() + end + + return false + end + + self:mob_sound("death") + + local function death_handle(self) + -- dropped cooked item if mob died in fire or lava + if cause == "lava" or cause == "fire" then + self:item_drop(true, 0) + else + local wielditem = ItemStack() + if cause == "hit" then + local puncher = cmi_cause.puncher + if puncher then + wielditem = puncher:get_wielded_item() + end + end + local cooked = mcl_burning.is_burning(self.object) or mcl_enchanting.has_enchantment(wielditem, "fire_aspect") + local looting = mcl_enchanting.get_enchantment(wielditem, "looting") + self:item_drop(cooked, looting) + + if ((not self.child) or self.type ~= "animal") and (minetest.get_us_time() - self.xp_timestamp <= math.huge) then + mcl_experience.throw_xp(self.object:get_pos(), math.random(self.xp_min, self.xp_max)) + end + end + end + + -- execute custom death function + if self.on_die then + + local pos = self.object:get_pos() + local on_die_exit = self.on_die(self, pos, cmi_cause) + if on_die_exit ~= true then + death_handle(self) + end + + if on_die_exit == true then + self.state = "die" + mcl_burning.extinguish(self.object) + self.object:remove() + return true + end + end + + local collisionbox + if self.collisionbox then + collisionbox = table.copy(self.collisionbox) + end + + self.state = "die" + self.attack = nil + self.v_start = false + self.fall_speed = DEFAULT_FALL_SPEED + self.timer = 0 + self.blinktimer = 0 + self:remove_texture_mod("^[colorize:#FF000040") + self:remove_texture_mod("^[brighten") + self.passive = true + + self.object:set_properties({ + pointable = false, + collide_with_objects = false, + }) + + self:set_velocity(0) + local acc = self.object:get_acceleration() + acc.x, acc.y, acc.z = 0, DEFAULT_FALL_SPEED, 0 + self.object:set_acceleration(acc) + + local length + -- default death function and die animation (if defined) + if self.instant_death then + length = 0 + 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 + self:set_animation( "die") + else + length = 1 + DEATH_DELAY + self:set_animation( "stand", true) + end + + + -- Remove body after a few seconds and drop stuff + local kill = function(self) + if not self.object:get_luaentity() then + return + end + + death_handle(self) + local dpos = self.object:get_pos() + local cbox = self.collisionbox + local yaw = self.object:get_rotation().y + mcl_burning.extinguish(self.object) + self.object:remove() + mcl_mobs.death_effect(dpos, yaw, cbox, not self.instant_death) + end + if length <= 0 then + kill(self) + else + minetest.after(length, kill, self) + end + + return true +end + +-- Deal light damage to mob, returns true if mob died +function mob_class:deal_light_damage(pos, damage) + if not ((mcl_weather.rain.raining or mcl_weather.state == "snow") and mcl_weather.is_outdoor(pos)) then + self.health = self.health - damage + + mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png") + + if self:check_for_death("light", {type = "light"}) then + return true + end + end +end + +-- environmental damage (water, lava, fire, light etc.) +function mob_class:do_env_damage() + -- feed/tame text timer (so mob 'full' messages dont spam chat) + if self.htimer > 0 then + self.htimer = self.htimer - 1 + end + + -- reset nametag after showing health stats + if self.htimer < 1 and self.nametag2 then + + self.nametag = self.nametag2 + self.nametag2 = nil + + self:update_tag() + end + + local pos = self.object:get_pos() + if not pos then return end + + self.time_of_day = minetest.get_timeofday() + + -- remove mob if beyond map limits + if not within_limits(pos, 0) then + mcl_burning.extinguish(self.object) + self.object:remove() + return true + end + + local sunlight = minetest.get_natural_light(pos, self.time_of_day) + + -- bright light harms mob + if self.light_damage ~= 0 and (sunlight or 0) > 12 then + if self:deal_light_damage(pos, self.light_damage) then + return true + end + end + local _, dim = mcl_worlds.y_to_layer(pos.y) + if (self.sunlight_damage ~= 0 or self.ignited_by_sunlight) and (sunlight or 0) >= minetest.LIGHT_MAX and dim == "overworld" then + if self.armor_list and not self.armor_list.helmet or not self.armor_list or self.armor_list and self.armor_list.helmet and self.armor_list.helmet == "" then + if self.ignited_by_sunlight then + mcl_burning.set_on_fire(self.object, 10) + else + self:deal_light_damage(pos, self.sunlight_damage) + return true + end + end + end + + local y_level = self.collisionbox[2] + + if self.child then + y_level = self.collisionbox[2] * 0.5 + end + + -- 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} + self.standing_in = node_ok(pos, "air").name + self.standing_on = node_ok(pos2, "air").name + + -- 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}) + end + + local nodef = minetest.registered_nodes[self.standing_in] + + -- rain + if self.rain_damage > 0 then + if mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then + + self.health = self.health - self.rain_damage + + if self:check_for_death("rain", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + end + + pos.y = pos.y + 1 -- for particle effect position + + -- water damage + if self.water_damage > 0 + and nodef.groups.water then + + if self.water_damage ~= 0 then + + self.health = self.health - self.water_damage + + mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png", nil, nil, 1, nil) + + if self:check_for_death("water", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + + -- lava damage + elseif self.lava_damage > 0 + and (nodef.groups.lava) then + + if self.lava_damage ~= 0 then + + self.health = self.health - self.lava_damage + + mcl_mobs.effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil) + mcl_burning.set_on_fire(self.object, 10) + + if self:check_for_death("lava", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + + -- fire damage + elseif self.fire_damage > 0 + and (nodef.groups.fire) then + + if self.fire_damage ~= 0 then + + self.health = self.health - self.fire_damage + + mcl_mobs.effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil) + mcl_burning.set_on_fire(self.object, 5) + + if self:check_for_death("fire", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + + -- damage_per_second node check + elseif nodef.damage_per_second ~= 0 and not nodef.groups.lava and not nodef.groups.fire then + + self.health = self.health - nodef.damage_per_second + + mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png") + + if self:check_for_death("dps", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + + -- Drowning damage + if self.breath_max ~= -1 then + local drowning = false + if self.breathes_in_water then + if minetest.get_item_group(self.standing_in, "water") == 0 then + drowning = true + end + elseif nodef.drowning > 0 then + drowning = true + end + if drowning then + + self.breath = math.max(0, self.breath - 1) + + mcl_mobs.effect(pos, 2, "bubble.png", nil, nil, 1, nil) + if self.breath <= 0 then + local dmg + if nodef.drowning > 0 then + dmg = nodef.drowning + else + dmg = 4 + end + self:damage_effect(dmg) + self.health = self.health - dmg + end + if self:check_for_death("drowning", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + else + self.breath = math.min(self.breath_max, self.breath + 1) + end + end + + --- suffocation inside solid node + -- FIXME: Redundant with mcl_playerplus + if (self.suffocation == true) + and (nodef.walkable == nil or nodef.walkable == true) + and (nodef.collision_box == nil or nodef.collision_box.type == "regular") + and (nodef.node_box == nil or nodef.node_box.type == "regular") + and (nodef.groups.disable_suffocation ~= 1) + and (nodef.groups.opaque == 1) then + + -- Short grace period before starting to take suffocation damage. + -- This is different from players, who take damage instantly. + -- This has been done because mobs might briefly be inside solid nodes + -- when e.g. climbing up stairs. + -- This is a bit hacky because it assumes that do_env_damage + -- is called roughly every second only. + self.suffocation_timer = self.suffocation_timer + 1 + if self.suffocation_timer >= 3 then + -- 2 damage per second + -- TODO: Deal this damage once every 1/2 second + self.health = self.health - 2 + + if self:check_for_death("suffocation", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + else + self.suffocation_timer = 0 + end + + return self:check_for_death("", {type = "unknown"}) +end + +function mob_class:damage_mob(reason,damage) + if not self.health then return end + damage = math.floor(damage) + if damage > 0 then + self.health = self.health - damage + + mcl_mobs.effect(self.object:get_pos(), 5, "mcl_particles_smoke.png", 1, 2, 2, nil) + + if self:check_for_death(reason, {type = reason}) then + return true + end + end +end + +function mob_class:check_entity_cramming() + local p = self.object:get_pos() + if not p then return end + local oo = minetest.get_objects_inside_radius(p,1) + local mobs = {} + for _,o in pairs(oo) do + local l = o:get_luaentity() + if l and l.is_mob and l.health > 0 then table.insert(mobs,l) end + end + local clear = #mobs < ENTITY_CRAMMING_MAX + local ncram = {} + for _,l in pairs(mobs) do + if l then + if clear then + l.cram = nil + elseif l.cram == nil and not self.child then + table.insert(ncram,l) + elseif l.cram then + l:damage_mob("cramming",CRAMMING_DAMAGE) + end + end + end + for i,l in pairs(ncram) do + if i > ENTITY_CRAMMING_MAX then + l.cram = true + else + l.cram = nil + end + end +end + +-- falling and fall damage +-- returns true if mob died +function mob_class:falling(pos) + + if self.fly and self.state ~= "die" then + return + end + + if not self.fall_speed then self.fall_speed = DEFAULT_FALL_SPEED 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.y > 0 then + + -- apply gravity when moving up + self.object:set_acceleration({ + x = 0, + y = DEFAULT_FALL_SPEED, + z = 0 + }) + + elseif v.y <= 0 and v.y > self.fall_speed then + + -- fall downwards at set speed + self.object:set_acceleration({ + x = 0, + y = self.fall_speed, + z = 0 + }) + else + -- stop accelerating once max fall speed hit + self.object:set_acceleration({x = 0, y = 0, z = 0}) + end + + if minetest.registered_nodes[node_ok(pos).name].groups.lava then + + if self.floats_on_lava == 1 then + + self.object:set_acceleration({ + x = 0, + y = -self.fall_speed / (math.max(1, v.y) ^ 2), + z = 0 + }) + end + end + + -- in water then float up + if minetest.registered_nodes[node_ok(pos).name].groups.water then + + if self.floats == 1 then + + self.object:set_acceleration({ + x = 0, + y = -self.fall_speed / (math.max(1, v.y) ^ 2), + z = 0 + }) + end + else + + -- fall damage onto solid ground + if self.fall_damage == 1 + and self.object:get_velocity().y == 0 then + local n = node_ok(vector.offset(pos,0,-1,0)).name + local d = (self.old_y or 0) - self.object:get_pos().y + + if d > 5 and n ~= "air" and n ~= "ignore" then + local add = minetest.get_item_group(self.standing_on, "fall_damage_add_percent") + local damage = d - 5 + if add ~= 0 then + damage = damage + damage * (add/100) + end + self:damage_mob("fall",damage) + end + + self.old_y = self.object:get_pos().y + end + end +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 + + -- Move item around on flowing liquids + if def and def.liquidtype == "flowing" then + + --[[ Get flowing direction (function call from flowlib), if there's a liquid. + NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7. + Luckily, this is exactly what we need if we only care about water, which has this flowing distance. ]] + local vec = flowlib.quick_flow(p, node) + -- Just to make sure we don't manipulate the speed for no reason + if vec.x ~= 0 or vec.y ~= 0 or vec.z ~= 0 then + -- Minecraft Wiki: Flowing speed is "about 1.39 meters per second" + 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.physical_state = true + self._flowing = true + self.object:set_properties({ + physical = true + }) + return + end + elseif self._flowing == true then + -- Disable flowing physics if not on/in flowing liquid + self._flowing = false + return + end +end + +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() + rot.z = ((math.pi/2-rot.z)*.2)+rot.z + self.object:set_rotation(rot) + return true + end +end + +function mob_class:check_suspend() + if not self:player_in_active_range() then + local pos = self.object:get_pos() + local node_under = node_ok(vector.offset(pos,0,-1,0)).name + local acc = self.object:get_acceleration() + self:set_animation( "stand", true) + if acc.y > 0 or node_under ~= "air" then + self.object:set_acceleration(vector.new(0,0,0)) + self.object:set_velocity(vector.new(0,0,0)) + end + if acc.y == 0 and node_under == "air" then + self:falling(pos) + end + return true + end +end diff --git a/mods/ENTITIES/mcl_mobs/spawning.lua b/mods/ENTITIES/mcl_mobs/spawning.lua index d7e3deb2b..a8250c19e 100644 --- a/mods/ENTITIES/mcl_mobs/spawning.lua +++ b/mods/ENTITIES/mcl_mobs/spawning.lua @@ -1,5 +1,7 @@ --lua locals -local minetest,vector,math,table = minetest,vector,math,table +local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs +local mob_class = mcl_mobs.mob_class + local get_node = minetest.get_node local get_item_group = minetest.get_item_group local get_node_light = minetest.get_node_light @@ -29,6 +31,7 @@ local dbg_spawn_succ = 0 local dbg_spawn_counts = {} -- range for mob count local aoc_range = 136 +local remove_far = true local mob_cap = { monster = tonumber(minetest.settings:get("mcl_mob_cap_monster")) or 70, @@ -661,34 +664,36 @@ if mobs_spawn then end local mob_def = mob_library_worker_table[mob_index] --minetest.log(mob_def.name.." "..step_chance.. " "..mob_chance) - local spawn_in_group = minetest.registered_entities[mob_def.name].spawn_in_group or 4 - local spawn_in_group_min = minetest.registered_entities[mob_def.name].spawn_in_group_min or 1 - local mob_type = minetest.registered_entities[mob_def.name].type - if spawn_check(spawning_position,mob_def) then - if mob_def.type_of_spawning == "water" then - spawning_position = get_water_spawn(spawning_position) - if not spawning_position then - minetest.log("warning","[mcl_mobs] no water spawn for mob "..mob_def.name.." found at "..minetest.pos_to_string(vector.round(pos))) + if mob_def and mob_def.name and minetest.registered_entities[mob_def.name] then + local spawn_in_group = minetest.registered_entities[mob_def.name].spawn_in_group or 4 + local spawn_in_group_min = minetest.registered_entities[mob_def.name].spawn_in_group_min or 1 + local mob_type = minetest.registered_entities[mob_def.name].type + if spawn_check(spawning_position,mob_def) then + if mob_def.type_of_spawning == "water" then + spawning_position = get_water_spawn(spawning_position) + if not spawning_position then + minetest.log("warning","[mcl_mobs] no water spawn for mob "..mob_def.name.." found at "..minetest.pos_to_string(vector.round(pos))) + return + end + end + if minetest.registered_entities[mob_def.name].can_spawn and not minetest.registered_entities[mob_def.name].can_spawn(spawning_position) then + minetest.log("warning","[mcl_mobs] mob "..mob_def.name.." refused to spawn at "..minetest.pos_to_string(vector.round(spawning_position))) return end - end - if minetest.registered_entities[mob_def.name].can_spawn and not minetest.registered_entities[mob_def.name].can_spawn(spawning_position) then - minetest.log("warning","[mcl_mobs] mob "..mob_def.name.." refused to spawn at "..minetest.pos_to_string(vector.round(spawning_position))) - return - end - --everything is correct, spawn mob - local object - if spawn_in_group and ( mob_type ~= "monster" or math.random(5) == 1 ) then - if logging then - minetest.log("action", "[mcl_mobs] A group of mob " .. mob_def.name .. " spawns on " ..minetest.get_node(vector.offset(spawning_position,0,-1,0)).name .." at " .. minetest.pos_to_string(spawning_position, 1)) - end - object = spawn_group(spawning_position,mob_def,{minetest.get_node(vector.offset(spawning_position,0,-1,0)).name},spawn_in_group,spawn_in_group_min) + --everything is correct, spawn mob + local object + if spawn_in_group and ( mob_type ~= "monster" or math.random(5) == 1 ) then + if logging then + minetest.log("action", "[mcl_mobs] A group of mob " .. mob_def.name .. " spawns on " ..minetest.get_node(vector.offset(spawning_position,0,-1,0)).name .." at " .. minetest.pos_to_string(spawning_position, 1)) + end + object = spawn_group(spawning_position,mob_def,{minetest.get_node(vector.offset(spawning_position,0,-1,0)).name},spawn_in_group,spawn_in_group_min) - else - if logging then - minetest.log("action", "[mcl_mobs] Mob " .. mob_def.name .. " spawns on " ..minetest.get_node(vector.offset(spawning_position,0,-1,0)).name .." at ".. minetest.pos_to_string(spawning_position, 1)) + else + if logging then + minetest.log("action", "[mcl_mobs] Mob " .. mob_def.name .. " spawns on " ..minetest.get_node(vector.offset(spawning_position,0,-1,0)).name .." at ".. minetest.pos_to_string(spawning_position, 1)) + end + object = mcl_mobs.spawn(spawning_position, mob_def.name) end - object = mcl_mobs.spawn(spawning_position, mob_def.name) end end current_summary_chance = current_summary_chance - mob_chance @@ -722,6 +727,30 @@ if mobs_spawn then end) end +function mob_class:check_despawn(pos) + -- Despawning: when lifetimer expires, remove mob + if remove_far + and self.can_despawn == true + and ((not self.nametag) or (self.nametag == "")) + and self.state ~= "attack" + and self.following == nil then + if self.despawn_immediately or self.lifetimer <= 0 then + if logging then + minetest.log("action", "[mcl_mobs] Mob "..self.name.." despawns at "..minetest.pos_to_string(pos, 1) .. " lifetimer ran out") + end + mcl_burning.extinguish(self.object) + self.object:remove() + return true + elseif self.lifetimer <= 10 then + if math.random(10) < 4 then + self.despawn_immediately = true + else + self.lifetimer = 20 + end + end + end +end + minetest.register_chatcommand("mobstats",{ privs = { debug = true }, func = function(n,param) diff --git a/mods/ENTITIES/mobs_mc/axolotl.lua b/mods/ENTITIES/mobs_mc/axolotl.lua index ead5ae453..1197983a3 100644 --- a/mods/ENTITIES/mobs_mc/axolotl.lua +++ b/mods/ENTITIES/mobs_mc/axolotl.lua @@ -87,7 +87,7 @@ local axolotl = { runaway = true, } -mcl_mobs:register_mob("mobs_mc:axolotl", axolotl) +mcl_mobs.register_mob("mobs_mc:axolotl", axolotl) local water = 0 @@ -178,4 +178,4 @@ water-16, water+1) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:axolotl", S("Axolotl"), "#e890bf", "#b83D7e", 0) +mcl_mobs.register_egg("mobs_mc:axolotl", S("Axolotl"), "#e890bf", "#b83D7e", 0) diff --git a/mods/ENTITIES/mobs_mc/bat.lua b/mods/ENTITIES/mobs_mc/bat.lua index 924ebbf87..b5532e2ee 100644 --- a/mods/ENTITIES/mobs_mc/bat.lua +++ b/mods/ENTITIES/mobs_mc/bat.lua @@ -2,7 +2,7 @@ local S = minetest.get_translator("mobs_mc") -mcl_mobs:register_mob("mobs_mc:bat", { +mcl_mobs.register_mob("mobs_mc:bat", { description = S("Bat"), type = "animal", spawn_class = "ambient", @@ -144,4 +144,4 @@ mobs_mc.water_level-1) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:bat", S("Bat"), "#4c3e30", "#0f0f0f", 0) +mcl_mobs.register_egg("mobs_mc:bat", S("Bat"), "#4c3e30", "#0f0f0f", 0) diff --git a/mods/ENTITIES/mobs_mc/blaze.lua b/mods/ENTITIES/mobs_mc/blaze.lua index b94bc656e..18b7ea676 100644 --- a/mods/ENTITIES/mobs_mc/blaze.lua +++ b/mods/ENTITIES/mobs_mc/blaze.lua @@ -12,7 +12,7 @@ local mod_target = minetest.get_modpath("mcl_target") --################### -mcl_mobs:register_mob("mobs_mc:blaze", { +mcl_mobs.register_mob("mobs_mc:blaze", { description = S("Blaze"), type = "monster", spawn_class = "hostile", @@ -153,7 +153,7 @@ mcl_vars.mg_nether_min, mcl_vars.mg_nether_max) -- Blaze fireball -mcl_mobs:register_arrow("mobs_mc:blaze_fireball", { +mcl_mobs.register_arrow("mobs_mc:blaze_fireball", { visual = "sprite", visual_size = {x = 0.3, y = 0.3}, textures = {"mcl_fire_fire_charge.png"}, @@ -208,4 +208,4 @@ mcl_mobs:register_arrow("mobs_mc:blaze_fireball", { }) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:blaze", S("Blaze"), "#f6b201", "#fff87e", 0) +mcl_mobs.register_egg("mobs_mc:blaze", S("Blaze"), "#f6b201", "#fff87e", 0) diff --git a/mods/ENTITIES/mobs_mc/chicken.lua b/mods/ENTITIES/mobs_mc/chicken.lua index 034cb89db..399294390 100644 --- a/mods/ENTITIES/mobs_mc/chicken.lua +++ b/mods/ENTITIES/mobs_mc/chicken.lua @@ -8,7 +8,7 @@ local S = minetest.get_translator("mobs_mc") -mcl_mobs:register_mob("mobs_mc:chicken", { +mcl_mobs.register_mob("mobs_mc:chicken", { description = S("Chicken"), type = "animal", spawn_class = "passive", @@ -83,7 +83,7 @@ mcl_mobs:register_mob("mobs_mc:chicken", { fear_height = 4, on_rightclick = function(self, clicker) - if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end + if self:feed_tame(clicker, 1, true, false) then return end if mcl_mobs:protect(self, clicker) then return end if mcl_mobs:capture_mob(self, clicker, 0, 60, 5, false, nil) then return end end, @@ -163,4 +163,4 @@ mobs_mc.water_level, mcl_vars.mg_overworld_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:chicken", S("Chicken"), "#a1a1a1", "#ff0000", 0) +mcl_mobs.register_egg("mobs_mc:chicken", S("Chicken"), "#a1a1a1", "#ff0000", 0) diff --git a/mods/ENTITIES/mobs_mc/cod.lua b/mods/ENTITIES/mobs_mc/cod.lua index e3529b3ee..24f3131b3 100644 --- a/mods/ENTITIES/mobs_mc/cod.lua +++ b/mods/ENTITIES/mobs_mc/cod.lua @@ -111,7 +111,7 @@ local cod = { end } -mcl_mobs:register_mob("mobs_mc:cod", cod) +mcl_mobs.register_mob("mobs_mc:cod", cod) --spawning TODO: in schools @@ -272,4 +272,4 @@ water-16, water+1) --spawn egg -mcl_mobs:register_egg("mobs_mc:cod", S("Cod"), "#c1a76a", "#e5c48b", 0) +mcl_mobs.register_egg("mobs_mc:cod", S("Cod"), "#c1a76a", "#e5c48b", 0) diff --git a/mods/ENTITIES/mobs_mc/cow+mooshroom.lua b/mods/ENTITIES/mobs_mc/cow+mooshroom.lua index 4a91e9196..5ac5912a4 100644 --- a/mods/ENTITIES/mobs_mc/cow+mooshroom.lua +++ b/mods/ENTITIES/mobs_mc/cow+mooshroom.lua @@ -59,7 +59,7 @@ local cow_def = { run_start = 41, run_end = 81, run_speed = 60, }, on_rightclick = function(self, clicker) - if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end + if self:feed_tame(clicker, 1, true, false) then return end if mcl_mobs:protect(self, clicker) then return end if self.child then @@ -88,7 +88,7 @@ local cow_def = { fear_height = 4, } -mcl_mobs:register_mob("mobs_mc:cow", cow_def) +mcl_mobs.register_mob("mobs_mc:cow", cow_def) -- Mooshroom local mooshroom_def = table.copy(cow_def) @@ -97,7 +97,7 @@ mooshroom_def.spawn_in_group_min = 4 mooshroom_def.spawn_in_group = 8 mooshroom_def.textures = { {"mobs_mc_mooshroom.png", "mobs_mc_mushroom_red.png"}, {"mobs_mc_mooshroom_brown.png", "mobs_mc_mushroom_brown.png" } } mooshroom_def.on_rightclick = function(self, clicker) - if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end + if self:feed_tame(clicker, 1, true, false) then return end if mcl_mobs:protect(self, clicker) then return end if self.child then @@ -163,7 +163,7 @@ mooshroom_def.on_lightning_strike = function(self, pos, pos2, objects) self.object:set_properties({ textures = self.base_texture }) return true end -mcl_mobs:register_mob("mobs_mc:mooshroom", mooshroom_def) +mcl_mobs.register_mob("mobs_mc:mooshroom", mooshroom_def) -- Spawning @@ -231,5 +231,5 @@ mcl_vars.mg_overworld_min, mcl_vars.mg_overworld_max) -- spawn egg -mcl_mobs:register_egg("mobs_mc:cow", S("Cow"), "#443626", "#a1a1a1", 0) -mcl_mobs:register_egg("mobs_mc:mooshroom", S("Mooshroom"), "#a00f10", "#b7b7b7", 0) +mcl_mobs.register_egg("mobs_mc:cow", S("Cow"), "#443626", "#a1a1a1", 0) +mcl_mobs.register_egg("mobs_mc:mooshroom", S("Mooshroom"), "#a00f10", "#b7b7b7", 0) diff --git a/mods/ENTITIES/mobs_mc/creeper.lua b/mods/ENTITIES/mobs_mc/creeper.lua index f50eb5326..6bbb37d5a 100644 --- a/mods/ENTITIES/mobs_mc/creeper.lua +++ b/mods/ENTITIES/mobs_mc/creeper.lua @@ -9,7 +9,7 @@ local S = minetest.get_translator("mobs_mc") -mcl_mobs:register_mob("mobs_mc:creeper", { +mcl_mobs.register_mob("mobs_mc:creeper", { type = "monster", spawn_class = "hostile", spawn_in_group = 1, @@ -81,7 +81,7 @@ mcl_mobs:register_mob("mobs_mc:creeper", { if self._forced_explosion_countdown_timer ~= nil then self._forced_explosion_countdown_timer = self._forced_explosion_countdown_timer - dtime if self._forced_explosion_countdown_timer <= 0 then - mcl_mobs:boom(self, mcl_util.get_object_center(self.object), self.explosion_strength) + self:boom(mcl_util.get_object_center(self.object), self.explosion_strength) end end end, @@ -133,7 +133,7 @@ mcl_mobs:register_mob("mobs_mc:creeper", { view_range = 16, }) -mcl_mobs:register_mob("mobs_mc:creeper_charged", { +mcl_mobs.register_mob("mobs_mc:creeper_charged", { description = S("Creeper"), type = "monster", spawn_class = "hostile", @@ -203,7 +203,7 @@ mcl_mobs:register_mob("mobs_mc:creeper_charged", { if self._forced_explosion_countdown_timer ~= nil then self._forced_explosion_countdown_timer = self._forced_explosion_countdown_timer - dtime if self._forced_explosion_countdown_timer <= 0 then - mcl_mobs:boom(self, mcl_util.get_object_center(self.object), self.explosion_strength) + self:boom(mcl_util.get_object_center(self.object), self.explosion_strength) end end end, @@ -411,4 +411,4 @@ mcl_vars.mg_overworld_min, mcl_vars.mg_overworld_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:creeper", S("Creeper"), "#0da70a", "#000000", 0) +mcl_mobs.register_egg("mobs_mc:creeper", S("Creeper"), "#0da70a", "#000000", 0) diff --git a/mods/ENTITIES/mobs_mc/dolphin.lua b/mods/ENTITIES/mobs_mc/dolphin.lua index 5e5910ca3..0e5c8e7ee 100644 --- a/mods/ENTITIES/mobs_mc/dolphin.lua +++ b/mods/ENTITIES/mobs_mc/dolphin.lua @@ -92,7 +92,7 @@ local dolphin = { end, } -mcl_mobs:register_mob("mobs_mc:dolphin", dolphin) +mcl_mobs.register_mob("mobs_mc:dolphin", dolphin) --spawning TO DO: in schools @@ -250,4 +250,4 @@ water-16, water+1) --spawn egg -mcl_mobs:register_egg("mobs_mc:dolphin", S("Dolphin"), "#223b4d", "#f9f9f9", 0) +mcl_mobs.register_egg("mobs_mc:dolphin", S("Dolphin"), "#223b4d", "#f9f9f9", 0) diff --git a/mods/ENTITIES/mobs_mc/ender_dragon.lua b/mods/ENTITIES/mobs_mc/ender_dragon.lua index f781dd477..ef4c8f811 100644 --- a/mods/ENTITIES/mobs_mc/ender_dragon.lua +++ b/mods/ENTITIES/mobs_mc/ender_dragon.lua @@ -48,7 +48,7 @@ local function check_pos(self) end end -mcl_mobs:register_mob("mobs_mc:enderdragon", { +mcl_mobs.register_mob("mobs_mc:enderdragon", { description = S("Ender Dragon"), type = "monster", spawn_class = "hostile", @@ -142,7 +142,7 @@ mcl_mobs:register_mob("mobs_mc:enderdragon", { local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false -- dragon fireball (projectile) -mcl_mobs:register_arrow("mobs_mc:dragon_fireball", { +mcl_mobs.register_arrow("mobs_mc:dragon_fireball", { visual = "sprite", visual_size = {x = 1.25, y = 1.25}, textures = {"mobs_mc_dragon_fireball.png"}, @@ -166,11 +166,11 @@ mcl_mobs:register_arrow("mobs_mc:dragon_fireball", { -- node hit, explode hit_node = function(self, pos, node) - mcl_mobs:boom(self, pos, 2) + mcl_mobs.mob_class.boom(self,pos, 2) end }) -mcl_mobs:register_egg("mobs_mc:enderdragon", S("Ender Dragon"), "#252525", "#b313c9", 0, true) +mcl_mobs.register_egg("mobs_mc:enderdragon", S("Ender Dragon"), "#252525", "#b313c9", 0, true) mcl_wip.register_wip_item("mobs_mc:enderdragon") diff --git a/mods/ENTITIES/mobs_mc/enderman.lua b/mods/ENTITIES/mobs_mc/enderman.lua index 2849b62b6..2688977fe 100644 --- a/mods/ENTITIES/mobs_mc/enderman.lua +++ b/mods/ENTITIES/mobs_mc/enderman.lua @@ -255,7 +255,7 @@ local psdefs = {{ texture = "mcl_portals_particle"..math.random(1, 5)..".png", }} -mcl_mobs:register_mob("mobs_mc:enderman", { +mcl_mobs.register_mob("mobs_mc:enderman", { description = S("Enderman"), type = "monster", spawn_class = "passive", @@ -497,7 +497,7 @@ mcl_mobs:register_mob("mobs_mc:enderman", { self.base_texture = create_enderman_textures(block_type, self._taken_node) self.object:set_properties({ textures = self.base_texture }) self.animation = select_enderman_animation("block") - mcl_mobs:set_animation(self, self.animation.current) + self:set_animation(self.animation.current) if def.sounds and def.sounds.dug then minetest.sound_play(def.sounds.dug, {pos = take_pos, max_hear_distance = 16}, true) end @@ -520,7 +520,7 @@ mcl_mobs:register_mob("mobs_mc:enderman", { local def = minetest.registered_nodes[self._taken_node] -- Update animation accordingly (removes visible block) self.animation = select_enderman_animation("normal") - mcl_mobs:set_animation(self, self.animation.current) + self:set_animation(self.animation.current) if def.sounds and def.sounds.place then minetest.sound_play(def.sounds.place, {pos = place_pos, max_hear_distance = 16}, true) end @@ -832,4 +832,4 @@ mcl_vars.mg_nether_min, mcl_vars.mg_nether_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:enderman", S("Enderman"), "#252525", "#151515", 0) +mcl_mobs.register_egg("mobs_mc:enderman", S("Enderman"), "#252525", "#151515", 0) diff --git a/mods/ENTITIES/mobs_mc/endermite.lua b/mods/ENTITIES/mobs_mc/endermite.lua index 035d57fd9..612054be8 100644 --- a/mods/ENTITIES/mobs_mc/endermite.lua +++ b/mods/ENTITIES/mobs_mc/endermite.lua @@ -4,7 +4,7 @@ local S = minetest.get_translator("mobs_mc") -mcl_mobs:register_mob("mobs_mc:endermite", { +mcl_mobs.register_mob("mobs_mc:endermite", { description = S("Endermite"), type = "monster", spawn_class = "hostile", @@ -38,4 +38,4 @@ mcl_mobs:register_mob("mobs_mc:endermite", { reach = 1, }) -mcl_mobs:register_egg("mobs_mc:endermite", S("Endermite"), "#161616", "#6d6d6d", 0) +mcl_mobs.register_egg("mobs_mc:endermite", S("Endermite"), "#161616", "#6d6d6d", 0) diff --git a/mods/ENTITIES/mobs_mc/ghast.lua b/mods/ENTITIES/mobs_mc/ghast.lua index 6805bbad4..0f7e73344 100644 --- a/mods/ENTITIES/mobs_mc/ghast.lua +++ b/mods/ENTITIES/mobs_mc/ghast.lua @@ -10,7 +10,7 @@ local S = minetest.get_translator("mobs_mc") --################### -mcl_mobs:register_mob("mobs_mc:ghast", { +mcl_mobs.register_mob("mobs_mc:ghast", { description = S("Ghast"), type = "monster", spawn_class = "hostile", @@ -103,7 +103,7 @@ mcl_vars.mg_nether_min, mcl_vars.mg_nether_max) -- fireball (projectile) -mcl_mobs:register_arrow("mobs_mc:fireball", { +mcl_mobs.register_arrow("mobs_mc:fireball", { visual = "sprite", visual_size = {x = 1, y = 1}, textures = {"mcl_fire_fire_charge.png"}, @@ -118,9 +118,9 @@ mcl_mobs:register_arrow("mobs_mc:fireball", { }, nil) local p = self.object:get_pos() if p then - mcl_mobs:boom(self, p, 1, true) + mcl_mobs.mob_class.boom(self,p, 1, true) else - mcl_mobs:boom(self, player:get_pos(), 1, true) + mcl_mobs.mob_class.boom(self,player:get_pos(), 1, true) end end, @@ -129,11 +129,11 @@ mcl_mobs:register_arrow("mobs_mc:fireball", { full_punch_interval = 1.0, damage_groups = {fleshy = 6}, }, nil) - mcl_mobs:boom(self, self.object:get_pos(), 1, true) + mcl_mobs.mob_class.boom(self,self.object:get_pos(), 1, true) end, hit_node = function(self, pos, node) - mcl_mobs:boom(self, pos, 1, true) + mcl_mobs.mob_class.boom(self,pos, 1, true) end }) @@ -141,4 +141,4 @@ mcl_mobs:register_arrow("mobs_mc:fireball", { -- spawn eggs -mcl_mobs:register_egg("mobs_mc:ghast", S("Ghast"), "#f9f9f9", "#bcbcbc", 0) +mcl_mobs.register_egg("mobs_mc:ghast", S("Ghast"), "#f9f9f9", "#bcbcbc", 0) diff --git a/mods/ENTITIES/mobs_mc/glow_squid.lua b/mods/ENTITIES/mobs_mc/glow_squid.lua index 092206998..9f3a45e34 100644 --- a/mods/ENTITIES/mobs_mc/glow_squid.lua +++ b/mods/ENTITIES/mobs_mc/glow_squid.lua @@ -29,7 +29,7 @@ for i=1,4 do table.insert(psdefs,p) end -mcl_mobs:register_mob("mobs_mc:glow_squid", { +mcl_mobs.register_mob("mobs_mc:glow_squid", { type = "animal", spawn_class = "water", can_despawn = true, @@ -243,4 +243,4 @@ mcl_mobs:spawn_specific( water) -- spawn egg -mcl_mobs:register_egg("mobs_mc:glow_squid", S("Glow Squid"), "#095757", "#87f6c0", 0) +mcl_mobs.register_egg("mobs_mc:glow_squid", S("Glow Squid"), "#095757", "#87f6c0", 0) diff --git a/mods/ENTITIES/mobs_mc/guardian.lua b/mods/ENTITIES/mobs_mc/guardian.lua index f0b13d7a4..e9a15264b 100644 --- a/mods/ENTITIES/mobs_mc/guardian.lua +++ b/mods/ENTITIES/mobs_mc/guardian.lua @@ -4,7 +4,7 @@ local S = minetest.get_translator("mobs_mc") -mcl_mobs:register_mob("mobs_mc:guardian", { +mcl_mobs.register_mob("mobs_mc:guardian", { description = S("Guardian"), type = "monster", spawn_class = "hostile", @@ -104,4 +104,4 @@ mcl_mobs:register_mob("mobs_mc:guardian", { --mcl_mobs:spawn_specific("mobs_mc:guardian", { "mcl_core:water_source", "mclx_core:river_water_source" }, { "mcl_core:water_source", "mclx_core:river_water_source" }, 0, minetest.LIGHT_MAX+1, 30, 25000, 2, mcl_vars.mg_overworld_min, mobs_mc.water_level - 10) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:guardian", S("Guardian"), "#5a8272", "#f17d31", 0) +mcl_mobs.register_egg("mobs_mc:guardian", S("Guardian"), "#5a8272", "#f17d31", 0) diff --git a/mods/ENTITIES/mobs_mc/guardian_elder.lua b/mods/ENTITIES/mobs_mc/guardian_elder.lua index eb07157e1..d08cc1846 100644 --- a/mods/ENTITIES/mobs_mc/guardian_elder.lua +++ b/mods/ENTITIES/mobs_mc/guardian_elder.lua @@ -6,7 +6,7 @@ local S = minetest.get_translator("mobs_mc") -mcl_mobs:register_mob("mobs_mc:guardian_elder", { +mcl_mobs.register_mob("mobs_mc:guardian_elder", { description = S("Elder Guardian"), type = "monster", spawn_class = "hostile", @@ -112,6 +112,6 @@ mcl_mobs:register_mob("mobs_mc:guardian_elder", { -- mcl_mobs:spawn_specific("mobs_mc:guardian_elder", { "mcl_core:water_source", "mclx_core:river_water_source" }, { "mcl_core:water_source", "mclx_core:river_water_source" }, 0, minetest.LIGHT_MAX+1, 30, 40000, 2, mcl_vars.mg_overworld_min, mobs_mc.water_level-18) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:guardian_elder", S("Elder Guardian"), "#ceccba", "#747693", 0) +mcl_mobs.register_egg("mobs_mc:guardian_elder", S("Elder Guardian"), "#ceccba", "#747693", 0) diff --git a/mods/ENTITIES/mobs_mc/hoglin+zoglin.lua b/mods/ENTITIES/mobs_mc/hoglin+zoglin.lua index f44ce1c08..e5cdcc8a8 100644 --- a/mods/ENTITIES/mobs_mc/hoglin+zoglin.lua +++ b/mods/ENTITIES/mobs_mc/hoglin+zoglin.lua @@ -84,7 +84,7 @@ local hoglin = { attack_animals = true, } -mcl_mobs:register_mob("mobs_mc:hoglin", hoglin) +mcl_mobs.register_mob("mobs_mc:hoglin", hoglin) local zoglin = table.copy(hoglin) zoglin.fire_resistant = 1 @@ -95,7 +95,7 @@ end zoglin.attacks_monsters = true zoglin.lava_damage = 0 zoglin.fire_damage = 0 -mcl_mobs:register_mob("mobs_mc:zoglin", zoglin) +mcl_mobs.register_mob("mobs_mc:zoglin", zoglin) -- Baby hoglin. @@ -112,7 +112,7 @@ baby_hoglin.walk_velocity = 1.2 baby_hoglin.run_velocity = 2.4 baby_hoglin.child = 1 -mcl_mobs:register_mob("mobs_mc:baby_hoglin", baby_hoglin) +mcl_mobs.register_mob("mobs_mc:baby_hoglin", baby_hoglin) -- Regular spawning in the Nether mcl_mobs:spawn_specific( @@ -132,4 +132,4 @@ mcl_vars.mg_nether_min, mcl_vars.mg_nether_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:hoglin", S("Hoglin"), "#85682e", "#2b2140", 0) +mcl_mobs.register_egg("mobs_mc:hoglin", S("Hoglin"), "#85682e", "#2b2140", 0) diff --git a/mods/ENTITIES/mobs_mc/horse.lua b/mods/ENTITIES/mobs_mc/horse.lua index d1c865968..a69234b44 100644 --- a/mods/ENTITIES/mobs_mc/horse.lua +++ b/mods/ENTITIES/mobs_mc/horse.lua @@ -337,7 +337,7 @@ local horse = { elseif (iname == "mcl_farming:carrot_item_gold") then heal = 4 end - if heal > 0 and mcl_mobs:feed_tame(self, clicker, heal, true, false) then + if heal > 0 and self:feed_tame(clicker, heal, true, false) then return end end @@ -352,7 +352,7 @@ local horse = { elseif (iname == "mcl_farming:hay_block") then heal = 20 end - if heal > 0 and mcl_mobs:feed_tame(self, clicker, heal, false, false) then + if heal > 0 and self:feed_tame(clicker, heal, false, false) then return end @@ -490,7 +490,7 @@ local horse = { end, } -mcl_mobs:register_mob("mobs_mc:horse", horse) +mcl_mobs.register_mob("mobs_mc:horse", horse) -- Skeleton horse local skeleton_horse = table.copy(horse) @@ -513,7 +513,7 @@ skeleton_horse.sounds = { distance = 16, } skeleton_horse.harmed_by_heal = true -mcl_mobs:register_mob("mobs_mc:skeleton_horse", skeleton_horse) +mcl_mobs.register_mob("mobs_mc:skeleton_horse", skeleton_horse) -- Zombie horse local zombie_horse = table.copy(horse) @@ -537,7 +537,7 @@ zombie_horse.sounds = { distance = 16, } zombie_horse.harmed_by_heal = true -mcl_mobs:register_mob("mobs_mc:zombie_horse", zombie_horse) +mcl_mobs.register_mob("mobs_mc:zombie_horse", zombie_horse) -- Donkey local d = 0.86 -- donkey scale @@ -571,7 +571,7 @@ donkey.jump = true donkey.jump_height = 3.75 -- can clear 1 block height -mcl_mobs:register_mob("mobs_mc:donkey", donkey) +mcl_mobs.register_mob("mobs_mc:donkey", donkey) mcl_entity_invs.register_inv("mobs_mc:donkey","Donkey",15,true) -- Mule local m = 0.94 @@ -589,7 +589,7 @@ mule.collisionbox = { horse.collisionbox[5] * m, horse.collisionbox[6] * m, } -mcl_mobs:register_mob("mobs_mc:mule", mule) +mcl_mobs.register_mob("mobs_mc:mule", mule) mcl_entity_invs.register_inv("mobs_mc:mule","Mule",15,true) --=========================== @@ -641,8 +641,8 @@ mobs_mc.water_level+3, mcl_vars.mg_overworld_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:horse", S("Horse"), "#c09e7d", "#eee500", 0) -mcl_mobs:register_egg("mobs_mc:skeleton_horse", S("Skeleton Horse"), "#68684f", "#e5e5d8", 0) +mcl_mobs.register_egg("mobs_mc:horse", S("Horse"), "#c09e7d", "#eee500", 0) +mcl_mobs.register_egg("mobs_mc:skeleton_horse", S("Skeleton Horse"), "#68684f", "#e5e5d8", 0) --mobs:register_egg("mobs_mc:zombie_horse", S("Zombie Horse"), "#2a5a37", "#84d080", 0) -mcl_mobs:register_egg("mobs_mc:donkey", S("Donkey"), "#534539", "#867566", 0) -mcl_mobs:register_egg("mobs_mc:mule", S("Mule"), "#1b0200", "#51331d", 0) +mcl_mobs.register_egg("mobs_mc:donkey", S("Donkey"), "#534539", "#867566", 0) +mcl_mobs.register_egg("mobs_mc:mule", S("Mule"), "#1b0200", "#51331d", 0) diff --git a/mods/ENTITIES/mobs_mc/iron_golem.lua b/mods/ENTITIES/mobs_mc/iron_golem.lua index a55bba81d..7ca4dd88a 100644 --- a/mods/ENTITIES/mobs_mc/iron_golem.lua +++ b/mods/ENTITIES/mobs_mc/iron_golem.lua @@ -11,7 +11,7 @@ local S = minetest.get_translator("mobs_mc") local etime = 0 -mcl_mobs:register_mob("mobs_mc:iron_golem", { +mcl_mobs.register_mob("mobs_mc:iron_golem", { description = S("Iron Golem"), type = "npc", spawn_class = "passive", @@ -89,7 +89,7 @@ mcl_mobs:register_mob("mobs_mc:iron_golem", { etime = etime + dtime if etime > 10 then if self._home and vector.distance(self._home,self.object:get_pos()) > 50 then - mcl_mobs:gopath(self,self._home) + self:gopath(self._home) end end end, @@ -97,7 +97,7 @@ mcl_mobs:register_mob("mobs_mc:iron_golem", { -- spawn eggs -mcl_mobs:register_egg("mobs_mc:iron_golem", S("Iron Golem"), "#3b3b3b", "#f57223", 0) +mcl_mobs.register_egg("mobs_mc:iron_golem", S("Iron Golem"), "#3b3b3b", "#f57223", 0) --[[ This is to be called when a pumpkin or jack'o lantern has been placed. Recommended: In the on_construct function of the node. This summons an iron golen if placing the pumpkin created an iron golem summon pattern: diff --git a/mods/ENTITIES/mobs_mc/llama.lua b/mods/ENTITIES/mobs_mc/llama.lua index dc3d4aca0..1f458ec89 100644 --- a/mods/ENTITIES/mobs_mc/llama.lua +++ b/mods/ENTITIES/mobs_mc/llama.lua @@ -47,7 +47,7 @@ local function get_drops(self) end end -mcl_mobs:register_mob("mobs_mc:llama", { +mcl_mobs.register_mob("mobs_mc:llama", { description = S("Llama"), type = "animal", spawn_class = "passive", @@ -156,7 +156,7 @@ mcl_mobs:register_mob("mobs_mc:llama", { local item = clicker:get_wielded_item() if item:get_name() == "mcl_farming:hay_block" then -- Breed with hay bale - if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end + if self:feed_tame(clicker, 1, true, false) then return end elseif not self._has_chest and item:get_name() == "mcl_chests:chest" then item:take_item() clicker:set_wielded_item(item) @@ -173,7 +173,7 @@ mcl_mobs:register_mob("mobs_mc:llama", { return else -- Feed with anything else - if mcl_mobs:feed_tame(self, clicker, 1, false, true) then return end + if self:feed_tame(clicker, 1, false, true) then return end end if mcl_mobs:protect(self, clicker) then return end @@ -254,7 +254,7 @@ mcl_mobs:register_mob("mobs_mc:llama", { mcl_entity_invs.register_inv("mobs_mc:llama","Llama",nil,true) -- spit arrow (weapon) -mcl_mobs:register_arrow("mobs_mc:llamaspit", { +mcl_mobs.register_arrow("mobs_mc:llamaspit", { visual = "sprite", visual_size = {x = 0.10, y = 0.10}, textures = {"mobs_mc_llama_spit.png"}, @@ -297,4 +297,4 @@ mobs_mc.water_level+15, mcl_vars.mg_overworld_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:llama", S("Llama"), "#c09e7d", "#995f40", 0) +mcl_mobs.register_egg("mobs_mc:llama", S("Llama"), "#c09e7d", "#995f40", 0) diff --git a/mods/ENTITIES/mobs_mc/ocelot.lua b/mods/ENTITIES/mobs_mc/ocelot.lua index c50067f5f..8a7ea7545 100644 --- a/mods/ENTITIES/mobs_mc/ocelot.lua +++ b/mods/ENTITIES/mobs_mc/ocelot.lua @@ -107,7 +107,7 @@ local ocelot = { end, } -mcl_mobs:register_mob("mobs_mc:ocelot", ocelot) +mcl_mobs.register_mob("mobs_mc:ocelot", ocelot) -- Cat local cat = table.copy(ocelot) @@ -130,7 +130,7 @@ cat.sounds = { distance = 16, } cat.on_rightclick = function(self, clicker) - if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end + if self:feed_tame(clicker, 1, true, false) then return end if mcl_mobs:capture_mob(self, clicker, 0, 60, 5, false, nil) then return end if mcl_mobs:protect(self, clicker) then return end @@ -167,7 +167,7 @@ cat.on_spawn = function(self) self.object:set_properties({textures = {self._texture}}) end -mcl_mobs:register_mob("mobs_mc:cat", cat) +mcl_mobs.register_mob("mobs_mc:cat", cat) local base_spawn_chance = 5000 @@ -249,4 +249,4 @@ mobs:spawn({ ]]-- -- spawn eggs -mcl_mobs:register_egg("mobs_mc:ocelot", S("Ocelot"), "#efde7d", "#564434", 0) +mcl_mobs.register_egg("mobs_mc:ocelot", S("Ocelot"), "#efde7d", "#564434", 0) diff --git a/mods/ENTITIES/mobs_mc/parrot.lua b/mods/ENTITIES/mobs_mc/parrot.lua index 52bb77280..ed2892aca 100644 --- a/mods/ENTITIES/mobs_mc/parrot.lua +++ b/mods/ENTITIES/mobs_mc/parrot.lua @@ -87,7 +87,7 @@ local function perch(self,player) local shoulder = get_shoulder(player) if not shoulder then return true end self.object:set_attach(player,"",shoulder,vector.new(0,0,0),true) - mcl_mobs:set_animation(self, "stand") + self:set_animation("stand") end end @@ -125,7 +125,7 @@ local function check_perch(self,dtime) end end -mcl_mobs:register_mob("mobs_mc:parrot", { +mcl_mobs.register_mob("mobs_mc:parrot", { description = S("Parrot"), type = "animal", spawn_class = "passive", @@ -207,7 +207,7 @@ mcl_mobs:register_mob("mobs_mc:parrot", { return end -- Feed to tame, but not breed - if mcl_mobs:feed_tame(self, clicker, 1, false, true) then return end + if self:feed_tame(clicker, 1, false, true) then return end perch(self,clicker) end, do_custom = function(self,dtime) @@ -241,4 +241,4 @@ mobs_mc.water_level+7, mcl_vars.mg_overworld_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:parrot", S("Parrot"), "#0da70a", "#ff0000", 0) +mcl_mobs.register_egg("mobs_mc:parrot", S("Parrot"), "#0da70a", "#ff0000", 0) diff --git a/mods/ENTITIES/mobs_mc/pig.lua b/mods/ENTITIES/mobs_mc/pig.lua index f8ce4afbe..3fb2d2edb 100644 --- a/mods/ENTITIES/mobs_mc/pig.lua +++ b/mods/ENTITIES/mobs_mc/pig.lua @@ -2,7 +2,7 @@ local S = minetest.get_translator("mobs_mc") -mcl_mobs:register_mob("mobs_mc:pig", { +mcl_mobs.register_mob("mobs_mc:pig", { description = S("Pig"), type = "animal", spawn_class = "passive", @@ -108,7 +108,7 @@ mcl_mobs:register_mob("mobs_mc:pig", { local wielditem = clicker:get_wielded_item() -- Feed pig if wielditem:get_name() ~= "mcl_mobitems:carrot_on_a_stick" then - if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end + if self:feed_tame(clicker, 1, true, false) then return end end if mcl_mobs:protect(self, clicker) then return end @@ -242,4 +242,4 @@ mcl_vars.mg_overworld_min, mcl_vars.mg_overworld_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:pig", S("Pig"), "#f0a5a2", "#db635f", 0) +mcl_mobs.register_egg("mobs_mc:pig", S("Pig"), "#f0a5a2", "#db635f", 0) diff --git a/mods/ENTITIES/mobs_mc/piglin.lua b/mods/ENTITIES/mobs_mc/piglin.lua index aac97092b..90e19ef02 100644 --- a/mods/ENTITIES/mobs_mc/piglin.lua +++ b/mods/ENTITIES/mobs_mc/piglin.lua @@ -174,7 +174,7 @@ local piglin = { specific_attack = { "player", "mobs_mc:hoglin" }, } -mcl_mobs:register_mob("mobs_mc:piglin", piglin) +mcl_mobs.register_mob("mobs_mc:piglin", piglin) local sword_piglin = table.copy(piglin) @@ -206,7 +206,7 @@ sword_piglin.animation = { punch_start = 189, punch_end = 198, } -mcl_mobs:register_mob("mobs_mc:sword_piglin", sword_piglin) +mcl_mobs.register_mob("mobs_mc:sword_piglin", sword_piglin) local zombified_piglin = table.copy(piglin) zombified_piglin.fire_resistant = 1 @@ -239,7 +239,7 @@ zombified_piglin.animation = { punch_start = 189, punch_end = 198, } -mcl_mobs:register_mob("mobs_mc:zombified_piglin", zombified_piglin) +mcl_mobs.register_mob("mobs_mc:zombified_piglin", zombified_piglin) local piglin_brute = table.copy(piglin) @@ -280,7 +280,7 @@ piglin_brute.animation = { } piglin_brute.can_despawn = false piglin_brute.group_attack = { "mobs_mc:piglin", "mobs_mc:piglin_brute" } -mcl_mobs:register_mob("mobs_mc:piglin_brute", piglin_brute) +mcl_mobs.register_mob("mobs_mc:piglin_brute", piglin_brute) -- Regular spawning in the Nether @@ -316,5 +316,5 @@ minetest.LIGHT_MAX+1, mcl_vars.mg_lava_nether_max, mcl_vars.mg_nether_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:piglin", S("Piglin"), "#7b4a17","#d5c381", 0) -mcl_mobs:register_egg("mobs_mc:piglin_brute", S("Piglin Brute"), "#562b0c","#ddc89d", 0) +mcl_mobs.register_egg("mobs_mc:piglin", S("Piglin"), "#7b4a17","#d5c381", 0) +mcl_mobs.register_egg("mobs_mc:piglin_brute", S("Piglin Brute"), "#562b0c","#ddc89d", 0) diff --git a/mods/ENTITIES/mobs_mc/pillager.lua b/mods/ENTITIES/mobs_mc/pillager.lua index 1321058b7..0b6b036f7 100644 --- a/mods/ENTITIES/mobs_mc/pillager.lua +++ b/mods/ENTITIES/mobs_mc/pillager.lua @@ -12,7 +12,7 @@ end local function reset_animation(self, animation) if not self.object:get_pos() or self._current_animation ~= animation then return end self._current_animation = "stand_reload" -- Mobs Redo won't set the animation unless we do this - mcl_mobs:set_animation(self, animation) + self:set_animation(animation) end pillager = { @@ -96,29 +96,29 @@ pillager = { self.object:set_properties(props) local old_anim = self._current_animation if old_anim == "run" or old_anim == "walk" then - mcl_mobs:set_animation(self, "reload_run") + self:set_animation("reload_run") end if old_anim == "stand" then - mcl_mobs:set_animation(self, "reload_stand") + self:set_animation("reload_stand") end self._current_animation = old_anim -- Mobs Redo will imediately reset the animation otherwise minetest.after(1, reload, self) minetest.after(2, reset_animation, self, old_anim) - + -- 2-4 damage per arrow local dmg = math.max(4, math.random(2, 8)) mcl_bows_s.shoot_arrow_crossbow("mcl_bows:arrow", pos, dir, self.object:get_yaw(), self.object, nil, dmg) - + -- While we are at it, change the sounds since there is no way to do this in Mobs Redo if self.sounds and self.sounds.random then self.sounds = table.copy(self.sounds) self.sounds.random = "mobs_mc_pillager_grunt" .. math.random(2) end - + -- Randomize reload time self.shoot_interval = math.random(3, 4) end, } -mcl_mobs:register_mob("mobs_mc:pillager", pillager) -mcl_mobs:register_egg("mobs_mc:pillager", S("Pillager"), "#532f36", "#959b9b", 0) +mcl_mobs.register_mob("mobs_mc:pillager", pillager) +mcl_mobs.register_egg("mobs_mc:pillager", S("Pillager"), "#532f36", "#959b9b", 0) diff --git a/mods/ENTITIES/mobs_mc/polar_bear.lua b/mods/ENTITIES/mobs_mc/polar_bear.lua index c81525f09..d6667a256 100644 --- a/mods/ENTITIES/mobs_mc/polar_bear.lua +++ b/mods/ENTITIES/mobs_mc/polar_bear.lua @@ -7,7 +7,7 @@ local S = minetest.get_translator("mobs_mc") --################### -mcl_mobs:register_mob("mobs_mc:polar_bear", { +mcl_mobs.register_mob("mobs_mc:polar_bear", { description = S("Polar Bear"), type = "animal", spawn_class = "passive", @@ -92,4 +92,4 @@ mcl_vars.mg_overworld_min, mcl_vars.mg_overworld_max) -- spawn egg -mcl_mobs:register_egg("mobs_mc:polar_bear", S("Polar Bear"), "#f2f2f2", "#959590", 0) +mcl_mobs.register_egg("mobs_mc:polar_bear", S("Polar Bear"), "#f2f2f2", "#959590", 0) diff --git a/mods/ENTITIES/mobs_mc/rabbit.lua b/mods/ENTITIES/mobs_mc/rabbit.lua index a28b34359..6d13fbe22 100644 --- a/mods/ENTITIES/mobs_mc/rabbit.lua +++ b/mods/ENTITIES/mobs_mc/rabbit.lua @@ -83,7 +83,7 @@ local rabbit = { }, on_rightclick = function(self, clicker) -- Feed, tame protect or capture - if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end + if self:feed_tame(clicker, 1, true, false) then return end if mcl_mobs:protect(self, clicker) then return end if mcl_mobs:capture_mob(self, clicker, 0, 50, 80, false, nil) then return end end, @@ -102,7 +102,7 @@ local rabbit = { end, } -mcl_mobs:register_mob("mobs_mc:rabbit", rabbit) +mcl_mobs.register_mob("mobs_mc:rabbit", rabbit) -- The killer bunny (Only with spawn egg) local killer_bunny = table.copy(rabbit) @@ -128,7 +128,7 @@ killer_bunny.do_custom = function(self) end end -mcl_mobs:register_mob("mobs_mc:killer_bunny", killer_bunny) +mcl_mobs.register_mob("mobs_mc:killer_bunny", killer_bunny) -- Mob spawning rules. -- Different skins depending on spawn location <- we'll get to this when the spawning algorithm is fleshed out @@ -213,7 +213,7 @@ mcl_mobs:spawn(spawn_grass) ]]-- -- Spawn egg -mcl_mobs:register_egg("mobs_mc:rabbit", S("Rabbit"), "#995f40", "#734831", 0) +mcl_mobs.register_egg("mobs_mc:rabbit", S("Rabbit"), "#995f40", "#734831", 0) -- Note: This spawn egg does not exist in Minecraft -mcl_mobs:register_egg("mobs_mc:killer_bunny", S("Killer Bunny"), "#f2f2f2", "#ff0000", 0) +mcl_mobs.register_egg("mobs_mc:killer_bunny", S("Killer Bunny"), "#f2f2f2", "#ff0000", 0) diff --git a/mods/ENTITIES/mobs_mc/salmon.lua b/mods/ENTITIES/mobs_mc/salmon.lua index d187e72ae..f53648244 100644 --- a/mods/ENTITIES/mobs_mc/salmon.lua +++ b/mods/ENTITIES/mobs_mc/salmon.lua @@ -67,7 +67,7 @@ local salmon = { end } -mcl_mobs:register_mob("mobs_mc:salmon", salmon) +mcl_mobs.register_mob("mobs_mc:salmon", salmon) --spawning TODO: in schools @@ -226,4 +226,4 @@ water-16, water+1) --spawn egg -mcl_mobs:register_egg("mobs_mc:salmon", S("Salmon"), "#a00f10", "#0e8474", 0) +mcl_mobs.register_egg("mobs_mc:salmon", S("Salmon"), "#a00f10", "#0e8474", 0) diff --git a/mods/ENTITIES/mobs_mc/sheep.lua b/mods/ENTITIES/mobs_mc/sheep.lua index b27ddfd34..4f3d3a048 100644 --- a/mods/ENTITIES/mobs_mc/sheep.lua +++ b/mods/ENTITIES/mobs_mc/sheep.lua @@ -52,7 +52,7 @@ end local gotten_texture = { "blank.png", "mobs_mc_sheep.png" } --mcsheep -mcl_mobs:register_mob("mobs_mc:sheep", { +mcl_mobs.register_mob("mobs_mc:sheep", { description = S("Sheep"), type = "animal", spawn_class = "passive", @@ -205,7 +205,7 @@ mcl_mobs:register_mob("mobs_mc:sheep", { on_rightclick = function(self, clicker) local item = clicker:get_wielded_item() - if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end + if self:feed_tame(clicker, 1, true, false) then return end if mcl_mobs:protect(self, clicker) then return end if item:get_name() == "mcl_tools:shears" and not self.gotten and not self.child then @@ -365,4 +365,4 @@ mcl_vars.mg_overworld_min, mcl_vars.mg_overworld_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:sheep", S("Sheep"), "#e7e7e7", "#ffb5b5", 0) +mcl_mobs.register_egg("mobs_mc:sheep", S("Sheep"), "#e7e7e7", "#ffb5b5", 0) diff --git a/mods/ENTITIES/mobs_mc/shulker.lua b/mods/ENTITIES/mobs_mc/shulker.lua index b197fb3ac..46737e90f 100644 --- a/mods/ENTITIES/mobs_mc/shulker.lua +++ b/mods/ENTITIES/mobs_mc/shulker.lua @@ -30,7 +30,7 @@ local function check_spot(pos) end local pr = PseudoRandom(os.time()*(-334)) -- animation 45-80 is transition between passive and attack stance -mcl_mobs:register_mob("mobs_mc:shulker", { +mcl_mobs.register_mob("mobs_mc:shulker", { description = S("Shulker"), type = "monster", spawn_class = "hostile", @@ -83,10 +83,10 @@ mcl_mobs:register_mob("mobs_mc:shulker", { end if self.state == "walk" or self.state == "stand" then self.state = "stand" - mcl_mobs:set_animation(self, "stand") + self:set_animation("stand") end if self.state == "attack" then - mcl_mobs:set_animation(self, "punch") + self:set_animation("punch") end self.path.way = false self.look_at_players = false @@ -134,7 +134,7 @@ mcl_mobs:register_mob("mobs_mc:shulker", { for n=1, math.min(8, #nodes) do local r = pr:next(1, #nodes) local nodepos = nodes[r] - local tg = vector.offset(nodepos,0,1,0) + local tg = vector.offset(nodepos,0,0.5,0) if check_spot(tg) then self.object:set_pos(tg) node_ok = true @@ -152,7 +152,7 @@ mcl_mobs:register_mob("mobs_mc:shulker", { }) -- bullet arrow (weapon) -mcl_mobs:register_arrow("mobs_mc:shulkerbullet", { +mcl_mobs.register_arrow("mobs_mc:shulkerbullet", { visual = "sprite", visual_size = {x = 0.25, y = 0.25}, textures = {"mobs_mc_shulkerbullet.png"}, @@ -177,7 +177,7 @@ mcl_mobs:register_arrow("mobs_mc:shulkerbullet", { }) -mcl_mobs:register_egg("mobs_mc:shulker", S("Shulker"), "#946694", "#4d3852", 0) +mcl_mobs.register_egg("mobs_mc:shulker", S("Shulker"), "#946694", "#4d3852", 0) --[[ mcl_mobs:spawn_specific( diff --git a/mods/ENTITIES/mobs_mc/silverfish.lua b/mods/ENTITIES/mobs_mc/silverfish.lua index f524581bd..5b1325f19 100644 --- a/mods/ENTITIES/mobs_mc/silverfish.lua +++ b/mods/ENTITIES/mobs_mc/silverfish.lua @@ -4,7 +4,7 @@ local S = minetest.get_translator("mobs_mc") -mcl_mobs:register_mob("mobs_mc:silverfish", { +mcl_mobs.register_mob("mobs_mc:silverfish", { description = S("Silverfish"), type = "monster", spawn_class = "hostile", @@ -56,7 +56,7 @@ mcl_mobs:register_mob("mobs_mc:silverfish", { reach = 1, }) -mcl_mobs:register_egg("mobs_mc:silverfish", S("Silverfish"), "#6d6d6d", "#313131", 0) +mcl_mobs.register_egg("mobs_mc:silverfish", S("Silverfish"), "#6d6d6d", "#313131", 0) -- Monster egg blocks (Minetest Game) if minetest.get_modpath("default") and mobs_mc.create_monster_egg_nodes then diff --git a/mods/ENTITIES/mobs_mc/skeleton+stray.lua b/mods/ENTITIES/mobs_mc/skeleton+stray.lua index 18e1d99fe..ca2fe25fa 100644 --- a/mods/ENTITIES/mobs_mc/skeleton+stray.lua +++ b/mods/ENTITIES/mobs_mc/skeleton+stray.lua @@ -129,7 +129,7 @@ local skeleton = { harmed_by_heal = true, } -mcl_mobs:register_mob("mobs_mc:skeleton", skeleton) +mcl_mobs.register_mob("mobs_mc:skeleton", skeleton) --################### @@ -166,7 +166,7 @@ table.insert(stray.drops, { end, }) -mcl_mobs:register_mob("mobs_mc:stray", stray) +mcl_mobs.register_mob("mobs_mc:stray", stray) -- Overworld spawn mcl_mobs:spawn_specific( @@ -356,6 +356,6 @@ mcl_vars.mg_overworld_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:skeleton", S("Skeleton"), "#c1c1c1", "#494949", 0) +mcl_mobs.register_egg("mobs_mc:skeleton", S("Skeleton"), "#c1c1c1", "#494949", 0) -mcl_mobs:register_egg("mobs_mc:stray", S("Stray"), "#5f7476", "#dae8e7", 0) +mcl_mobs.register_egg("mobs_mc:stray", S("Stray"), "#5f7476", "#dae8e7", 0) diff --git a/mods/ENTITIES/mobs_mc/skeleton_wither.lua b/mods/ENTITIES/mobs_mc/skeleton_wither.lua index f3fd3aab5..94fa78120 100644 --- a/mods/ENTITIES/mobs_mc/skeleton_wither.lua +++ b/mods/ENTITIES/mobs_mc/skeleton_wither.lua @@ -9,7 +9,7 @@ local S = minetest.get_translator("mobs_mc") --################### WITHER SKELETON --################### -mcl_mobs:register_mob("mobs_mc:witherskeleton", { +mcl_mobs.register_mob("mobs_mc:witherskeleton", { description = S("Wither Skeleton"), type = "monster", spawn_class = "hostile", @@ -117,4 +117,4 @@ mcl_vars.mg_nether_min, mcl_vars.mg_nether_max) --]] -- spawn eggs -mcl_mobs:register_egg("mobs_mc:witherskeleton", S("Wither Skeleton"), "#141414", "#474d4d", 0) +mcl_mobs.register_egg("mobs_mc:witherskeleton", S("Wither Skeleton"), "#141414", "#474d4d", 0) diff --git a/mods/ENTITIES/mobs_mc/slime+magma_cube.lua b/mods/ENTITIES/mobs_mc/slime+magma_cube.lua index 58966e05e..c6de471e3 100644 --- a/mods/ENTITIES/mobs_mc/slime+magma_cube.lua +++ b/mods/ENTITIES/mobs_mc/slime+magma_cube.lua @@ -109,7 +109,7 @@ local slime_big = { on_die = spawn_children_on_die("mobs_mc:slime_small", 4, 1.0, 1.5), use_texture_alpha = true, } -mcl_mobs:register_mob("mobs_mc:slime_big", slime_big) +mcl_mobs.register_mob("mobs_mc:slime_big", slime_big) local slime_small = table.copy(slime_big) slime_small.sounds.base_pitch = 1.15 @@ -126,7 +126,7 @@ slime_small.run_velocity = 1.3 slime_small.jump_height = 4.3 slime_small.spawn_small_alternative = "mobs_mc:slime_tiny" slime_small.on_die = spawn_children_on_die("mobs_mc:slime_tiny", 4, 0.6, 1.0) -mcl_mobs:register_mob("mobs_mc:slime_small", slime_small) +mcl_mobs.register_mob("mobs_mc:slime_small", slime_small) local slime_tiny = table.copy(slime_big) slime_tiny.sounds.base_pitch = 1.3 @@ -151,7 +151,7 @@ slime_tiny.jump_height = 3 slime_tiny.spawn_small_alternative = nil slime_tiny.on_die = nil -mcl_mobs:register_mob("mobs_mc:slime_tiny", slime_tiny) +mcl_mobs.register_mob("mobs_mc:slime_tiny", slime_tiny) local smin = mcl_vars.mg_overworld_min local smax = mobs_mc.water_level - 23 @@ -348,7 +348,7 @@ local magma_cube_big = { on_die = spawn_children_on_die("mobs_mc:magma_cube_small", 3, 0.8, 1.5), fire_resistant = true, } -mcl_mobs:register_mob("mobs_mc:magma_cube_big", magma_cube_big) +mcl_mobs.register_mob("mobs_mc:magma_cube_big", magma_cube_big) local magma_cube_small = table.copy(magma_cube_big) magma_cube_small.sounds.jump = "mobs_mc_magma_cube_small" @@ -369,7 +369,7 @@ magma_cube_small.reach = 2.75 magma_cube_small.armor = 66 magma_cube_small.spawn_small_alternative = "mobs_mc:magma_cube_tiny" magma_cube_small.on_die = spawn_children_on_die("mobs_mc:magma_cube_tiny", 4, 0.6, 1.0) -mcl_mobs:register_mob("mobs_mc:magma_cube_small", magma_cube_small) +mcl_mobs.register_mob("mobs_mc:magma_cube_small", magma_cube_small) local magma_cube_tiny = table.copy(magma_cube_big) magma_cube_tiny.sounds.jump = "mobs_mc_magma_cube_small" @@ -391,7 +391,7 @@ magma_cube_tiny.drops = {} magma_cube_tiny.spawn_small_alternative = nil magma_cube_tiny.on_die = nil -mcl_mobs:register_mob("mobs_mc:magma_cube_tiny", magma_cube_tiny) +mcl_mobs.register_mob("mobs_mc:magma_cube_tiny", magma_cube_tiny) local mmin = mcl_vars.mg_nether_min @@ -447,6 +447,6 @@ mmin, mmax) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:magma_cube_big", S("Magma Cube"), "#350000", "#fcfc00") +mcl_mobs.register_egg("mobs_mc:magma_cube_big", S("Magma Cube"), "#350000", "#fcfc00") -mcl_mobs:register_egg("mobs_mc:slime_big", S("Slime"), "#52a03e", "#7ebf6d") +mcl_mobs.register_egg("mobs_mc:slime_big", S("Slime"), "#52a03e", "#7ebf6d") diff --git a/mods/ENTITIES/mobs_mc/snowman.lua b/mods/ENTITIES/mobs_mc/snowman.lua index f282c45b8..f9f0043c2 100644 --- a/mods/ENTITIES/mobs_mc/snowman.lua +++ b/mods/ENTITIES/mobs_mc/snowman.lua @@ -20,7 +20,7 @@ local gotten_texture = { "blank.png", } -mcl_mobs:register_mob("mobs_mc:snowman", { +mcl_mobs.register_mob("mobs_mc:snowman", { description = S("Snow Golem"), type = "npc", spawn_class = "passive", @@ -196,4 +196,4 @@ function mobs_mc.check_snow_golem_summon(pos) end -- Spawn egg -mcl_mobs:register_egg("mobs_mc:snowman", S("Snow Golem"), "#f2f2f2", "#fd8f47", 0) +mcl_mobs.register_egg("mobs_mc:snowman", S("Snow Golem"), "#f2f2f2", "#fd8f47", 0) diff --git a/mods/ENTITIES/mobs_mc/spider.lua b/mods/ENTITIES/mobs_mc/spider.lua index 1d68256b3..403edba6d 100644 --- a/mods/ENTITIES/mobs_mc/spider.lua +++ b/mods/ENTITIES/mobs_mc/spider.lua @@ -111,7 +111,7 @@ local spider = { run_end = 20, }, } -mcl_mobs:register_mob("mobs_mc:spider", spider) +mcl_mobs.register_mob("mobs_mc:spider", spider) -- Cave spider local cave_spider = table.copy(spider) @@ -141,7 +141,7 @@ cave_spider.walk_velocity = 1.3 cave_spider.run_velocity = 3.2 cave_spider.sounds = table.copy(spider.sounds) cave_spider.sounds.base_pitch = 1.25 -mcl_mobs:register_mob("mobs_mc:cave_spider", cave_spider) +mcl_mobs.register_mob("mobs_mc:cave_spider", cave_spider) mcl_mobs:spawn_specific( @@ -293,5 +293,5 @@ mcl_vars.mg_overworld_min, mcl_vars.mg_overworld_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:spider", S("Spider"), "#342d26", "#a80e0e", 0) -mcl_mobs:register_egg("mobs_mc:cave_spider", S("Cave Spider"), "#0c424e", "#a80e0e", 0) +mcl_mobs.register_egg("mobs_mc:spider", S("Spider"), "#342d26", "#a80e0e", 0) +mcl_mobs.register_egg("mobs_mc:cave_spider", S("Cave Spider"), "#0c424e", "#a80e0e", 0) diff --git a/mods/ENTITIES/mobs_mc/squid.lua b/mods/ENTITIES/mobs_mc/squid.lua index 678f3ad50..72f6702a5 100644 --- a/mods/ENTITIES/mobs_mc/squid.lua +++ b/mods/ENTITIES/mobs_mc/squid.lua @@ -6,7 +6,7 @@ local S = minetest.get_translator("mobs_mc") -mcl_mobs:register_mob("mobs_mc:squid", { +mcl_mobs.register_mob("mobs_mc:squid", { description = S("Squid"), type = "animal", spawn_class = "water", @@ -217,4 +217,4 @@ water-16, water+1) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:squid", S("Squid"), "#223b4d", "#708999", 0) +mcl_mobs.register_egg("mobs_mc:squid", S("Squid"), "#223b4d", "#708999", 0) diff --git a/mods/ENTITIES/mobs_mc/strider.lua b/mods/ENTITIES/mobs_mc/strider.lua index 42b2506e4..a49845f7e 100644 --- a/mods/ENTITIES/mobs_mc/strider.lua +++ b/mods/ENTITIES/mobs_mc/strider.lua @@ -123,7 +123,7 @@ local strider = { local wielditem = clicker:get_wielded_item() if wielditem:get_name() ~= "mcl_crimson:warped_fungus" then - if mcl_mobs:feed_tame(self, clicker, 1, true, true) then return end + if self:feed_tame(clicker, 1, true, true) then return end end if self.child then @@ -192,7 +192,7 @@ local strider = { end, } -mcl_mobs:register_mob("mobs_mc:strider", strider) +mcl_mobs.register_mob("mobs_mc:strider", strider) -- Baby strider. @@ -208,7 +208,7 @@ baby_strider.walk_velocity = 1.2 baby_strider.run_velocity = 2.4 baby_strider.child = 1 -mcl_mobs:register_mob("mobs_mc:baby_strider", baby_strider) +mcl_mobs.register_mob("mobs_mc:baby_strider", baby_strider) -- Regular spawning in the Nether @@ -245,4 +245,4 @@ mcl_mobs:spawn_setup({ }) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:strider", S("Strider"), "#000000", "#FF0000", 0) +mcl_mobs.register_egg("mobs_mc:strider", S("Strider"), "#000000", "#FF0000", 0) diff --git a/mods/ENTITIES/mobs_mc/tropical_fish.lua b/mods/ENTITIES/mobs_mc/tropical_fish.lua index 113f2d401..47f59a92e 100644 --- a/mods/ENTITIES/mobs_mc/tropical_fish.lua +++ b/mods/ENTITIES/mobs_mc/tropical_fish.lua @@ -118,7 +118,7 @@ local tropical_fish = { on_spawn = set_textures, } -mcl_mobs:register_mob("mobs_mc:tropical_fish", tropical_fish) +mcl_mobs.register_mob("mobs_mc:tropical_fish", tropical_fish) local water = 0 mcl_mobs:spawn_specific( @@ -189,4 +189,4 @@ water-16, water+1) --spawn egg -mcl_mobs:register_egg("mobs_mc:tropical_fish", S("Tropical fish"), "#ef6915", "#fff9ef", 0) +mcl_mobs.register_egg("mobs_mc:tropical_fish", S("Tropical fish"), "#ef6915", "#fff9ef", 0) diff --git a/mods/ENTITIES/mobs_mc/vex.lua b/mods/ENTITIES/mobs_mc/vex.lua index 4cfa4ed48..f2cd6f14b 100644 --- a/mods/ENTITIES/mobs_mc/vex.lua +++ b/mods/ENTITIES/mobs_mc/vex.lua @@ -9,7 +9,7 @@ local S = minetest.get_translator("mobs_mc") --################### VEX --################### -mcl_mobs:register_mob("mobs_mc:vex", { +mcl_mobs.register_mob("mobs_mc:vex", { description = S("Vex"), type = "monster", spawn_class = "hostile", @@ -94,4 +94,4 @@ mcl_mobs:register_mob("mobs_mc:vex", { -- spawn eggs -mcl_mobs:register_egg("mobs_mc:vex", S("Vex"), "#7a90a4", "#e8edf1", 0) +mcl_mobs.register_egg("mobs_mc:vex", S("Vex"), "#7a90a4", "#e8edf1", 0) diff --git a/mods/ENTITIES/mobs_mc/villager.lua b/mods/ENTITIES/mobs_mc/villager.lua index 9c10c703d..bf2a07d51 100644 --- a/mods/ENTITIES/mobs_mc/villager.lua +++ b/mods/ENTITIES/mobs_mc/villager.lua @@ -809,7 +809,7 @@ local function go_home(entity, sleep) return end - mcl_mobs:gopath(entity,b,function(entity,b) + entity:gopath(b,function(entity,b) local b = entity._bed if not b then @@ -845,7 +845,6 @@ local function take_bed (entity) mcl_log("Can we path to bed: "..minetest.pos_to_string(closest_block) ) local distance_to_block = vector.distance(p, closest_block) mcl_log("Distance: " .. distance_to_block) - if distance_to_block < 2 then local m = minetest.get_meta(closest_block) local owner = m:get_string("villager") @@ -866,7 +865,7 @@ local function take_bed (entity) mcl_log("Set as sleep already..." ) end else - local gp = mcl_mobs:gopath(entity, closest_block,function(self) end) + local gp = entity:gopath(closest_block,function(self) end) if gp then mcl_log("Nice bed. I'll defintely take it as I can path") else @@ -1035,7 +1034,7 @@ local function look_for_job(self, requested_jobsites) if closest_block then mcl_log("It's a free job for me (" .. minetest.pos_to_string(p) .. ")! I might be interested: ".. minetest.pos_to_string(closest_block) ) - local gp = mcl_mobs:gopath(self, closest_block,function(self) + local gp = self:gopath(closest_block,function(self) mcl_log("Arrived at block callback") if self and self.state == "stand" then self.order = WORK @@ -1168,7 +1167,7 @@ local function do_work (self) self.order = nil return end - mcl_mobs:gopath(self, jobsite, function(self,jobsite) + self:gopath(jobsite, function(self,jobsite) if not self then --mcl_log("missing self. not good") return false @@ -1210,7 +1209,7 @@ local function go_to_town_bell(self) for _,n in pairs(nn) do mcl_log("Found bell") - local gp = mcl_mobs:gopath(self,n,function(self) + local gp = self:gopath(n,function(self) if self then self.order = GATHERING mcl_log("Callback has a self") @@ -1898,7 +1897,7 @@ end) local pick_up = { "mcl_farming:bread", "mcl_farming:carrot_item", "mcl_farming:beetroot_item" , "mcl_farming:potato_item" } -mcl_mobs:register_mob("mobs_mc:villager", { +mcl_mobs.register_mob("mobs_mc:villager", { description = S("Villager"), type = "npc", spawn_class = "passive", @@ -1963,7 +1962,7 @@ mcl_mobs:register_mob("mobs_mc:villager", { end end if clicker and not self.horny then - mcl_mobs:feed_tame(self, clicker, 1, true, false, true) + self:feed_tame(clicker, 1, true, false, true) it:take_item(1) end return it @@ -1986,7 +1985,7 @@ mcl_mobs:register_mob("mobs_mc:villager", { end -- Don't do at night. Go to bed? Maybe do_activity needs it's own method if validate_jobsite(self) and not self.order == WORK then - --mcl_mobs:gopath(self,self._jobsite,function() + --self:gopath(self._jobsite,function() -- minetest.log("sent to jobsite") --end) else @@ -2157,4 +2156,4 @@ mobs_mc.water_level+1, mcl_vars.mg_overworld_max) --]] -- spawn eggs -mcl_mobs:register_egg("mobs_mc:villager", S("Villager"), "#563d33", "#bc8b72", 0) +mcl_mobs.register_egg("mobs_mc:villager", S("Villager"), "#563d33", "#bc8b72", 0) diff --git a/mods/ENTITIES/mobs_mc/villager_evoker.lua b/mods/ENTITIES/mobs_mc/villager_evoker.lua index 242fa802a..4d46a7260 100644 --- a/mods/ENTITIES/mobs_mc/villager_evoker.lua +++ b/mods/ENTITIES/mobs_mc/villager_evoker.lua @@ -13,7 +13,7 @@ local pr = PseudoRandom(os.time()*666) local spawned_vexes = {} --this is stored locally so the mobs engine doesn't try to store it in staticdata -mcl_mobs:register_mob("mobs_mc:evoker", { +mcl_mobs.register_mob("mobs_mc:evoker", { description = S("Evoker"), type = "monster", spawn_class = "hostile", @@ -89,4 +89,4 @@ mcl_mobs:register_mob("mobs_mc:evoker", { }) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:evoker", S("Evoker"), "#959b9b", "#1e1c1a", 0) +mcl_mobs.register_egg("mobs_mc:evoker", S("Evoker"), "#959b9b", "#1e1c1a", 0) diff --git a/mods/ENTITIES/mobs_mc/villager_illusioner.lua b/mods/ENTITIES/mobs_mc/villager_illusioner.lua index fd4a7e183..f7c034eb0 100644 --- a/mods/ENTITIES/mobs_mc/villager_illusioner.lua +++ b/mods/ENTITIES/mobs_mc/villager_illusioner.lua @@ -6,7 +6,7 @@ local S = minetest.get_translator("mobs_mc") local mod_bows = minetest.get_modpath("mcl_bows") ~= nil -mcl_mobs:register_mob("mobs_mc:illusioner", { +mcl_mobs.register_mob("mobs_mc:illusioner", { description = S("Illusioner"), type = "monster", spawn_class = "hostile", @@ -65,4 +65,4 @@ mcl_mobs:register_mob("mobs_mc:illusioner", { fear_height = 4, }) -mcl_mobs:register_egg("mobs_mc:illusioner", S("Illusioner"), "#3f5cbb", "#8a8686", 0) +mcl_mobs.register_egg("mobs_mc:illusioner", S("Illusioner"), "#3f5cbb", "#8a8686", 0) diff --git a/mods/ENTITIES/mobs_mc/villager_vindicator.lua b/mods/ENTITIES/mobs_mc/villager_vindicator.lua index 67db51680..b2dcb827a 100644 --- a/mods/ENTITIES/mobs_mc/villager_vindicator.lua +++ b/mods/ENTITIES/mobs_mc/villager_vindicator.lua @@ -10,7 +10,7 @@ local S = minetest.get_translator("mobs_mc") --################### -mcl_mobs:register_mob("mobs_mc:vindicator", { +mcl_mobs.register_mob("mobs_mc:vindicator", { description = S("Vindicator"), type = "monster", spawn_class = "hostile", @@ -77,4 +77,4 @@ mcl_mobs:register_mob("mobs_mc:vindicator", { }) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:vindicator", S("Vindicator"), "#959b9b", "#275e61", 0) +mcl_mobs.register_egg("mobs_mc:vindicator", S("Vindicator"), "#959b9b", "#275e61", 0) diff --git a/mods/ENTITIES/mobs_mc/villager_zombie.lua b/mods/ENTITIES/mobs_mc/villager_zombie.lua index d0f4acd14..9a1e30e70 100644 --- a/mods/ENTITIES/mobs_mc/villager_zombie.lua +++ b/mods/ENTITIES/mobs_mc/villager_zombie.lua @@ -25,7 +25,7 @@ local professions = { nitwit = "mobs_mc_villager.png", } -mcl_mobs:register_mob("mobs_mc:villager_zombie", { +mcl_mobs.register_mob("mobs_mc:villager_zombie", { description = S("Zombie Villager"), type = "monster", spawn_class = "hostile", @@ -231,4 +231,4 @@ mcl_vars.mg_overworld_max) --mcl_mobs:spawn_specific("mobs_mc:villager_zombie", "overworld", "ground", 0, 7, 30, 60000, 4, mcl_vars.mg_overworld_min, mcl_vars.mg_overworld_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:villager_zombie", S("Zombie Villager"), "#563d33", "#799c66", 0) +mcl_mobs.register_egg("mobs_mc:villager_zombie", S("Zombie Villager"), "#563d33", "#799c66", 0) diff --git a/mods/ENTITIES/mobs_mc/witch.lua b/mods/ENTITIES/mobs_mc/witch.lua index 5d835eab5..266d5b0c3 100644 --- a/mods/ENTITIES/mobs_mc/witch.lua +++ b/mods/ENTITIES/mobs_mc/witch.lua @@ -12,7 +12,7 @@ local S = minetest.get_translator("mobs_mc") -mcl_mobs:register_mob("mobs_mc:witch", { +mcl_mobs.register_mob("mobs_mc:witch", { description = S("Witch"), type = "monster", spawn_class = "hostile", @@ -73,7 +73,7 @@ mcl_mobs:register_mob("mobs_mc:witch", { }) -- potion projectile (EXPERIMENTAL) -mcl_mobs:register_arrow("mobs_mc:potion_arrow", { +mcl_mobs.register_arrow("mobs_mc:potion_arrow", { visual = "sprite", visual_size = {x = 0.5, y = 0.5}, --textures = {"vessels_glass_bottle.png"}, --TODO fix to else if default @@ -105,6 +105,6 @@ mcl_mobs:register_arrow("mobs_mc:potion_arrow", { --mcl_mobs:spawn_specific("mobs_mc:witch", { "mcl_core:jungletree", "mcl_core:jungleleaves", "mcl_flowers:fern", "mcl_core:vine" }, {"air"}, 0, minetest.LIGHT_MAX-6, 12, 20000, 2, mobs_mc.water_level-6, mcl_vars.mg_overworld_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:witch", S("Witch"), "#340000", "#51a03e", 0, true) +mcl_mobs.register_egg("mobs_mc:witch", S("Witch"), "#340000", "#51a03e", 0, true) mcl_wip.register_wip_item("mobs_mc:witch") diff --git a/mods/ENTITIES/mobs_mc/wither.lua b/mods/ENTITIES/mobs_mc/wither.lua index 26f90be5a..dda2beafa 100644 --- a/mods/ENTITIES/mobs_mc/wither.lua +++ b/mods/ENTITIES/mobs_mc/wither.lua @@ -9,7 +9,7 @@ local S = minetest.get_translator("mobs_mc") --################### WITHER --################### -mcl_mobs:register_mob("mobs_mc:wither", { +mcl_mobs.register_mob("mobs_mc:wither", { description = S("Wither"), type = "monster", spawn_class = "hostile", @@ -85,7 +85,7 @@ mcl_mobs:register_mob("mobs_mc:wither", { local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false local wither_rose_soil = { "group:grass_block", "mcl_core:dirt", "mcl_core:coarse_dirt", "mcl_nether:netherrack", "group:soul_block", "mcl_mud:mud", "mcl_moss:moss" } -mcl_mobs:register_arrow("mobs_mc:wither_skull", { +mcl_mobs.register_arrow("mobs_mc:wither_skull", { visual = "sprite", visual_size = {x = 0.75, y = 0.75}, -- TODO: 3D projectile, replace tetxture @@ -98,7 +98,7 @@ mcl_mobs:register_arrow("mobs_mc:wither_skull", { full_punch_interval = 0.5, damage_groups = {fleshy = 8}, }, nil) - mcl_mobs:boom(self, self.object:get_pos(), 1) + mcl_mobs.mob_class.boom(self,self.object:get_pos(), 1) end, hit_mob = function(self, mob) @@ -106,7 +106,7 @@ mcl_mobs:register_arrow("mobs_mc:wither_skull", { full_punch_interval = 0.5, damage_groups = {fleshy = 8}, }, nil) - mcl_mobs:boom(self, self.object:get_pos(), 1) + mcl_mobs.mob_class.boom(self,self.object:get_pos(), 1) local l = mob:get_luaentity() if l and l.health - 8 <= 0 then local n = minetest.find_node_near(mob:get_pos(),2,wither_rose_soil) @@ -123,12 +123,12 @@ mcl_mobs:register_arrow("mobs_mc:wither_skull", { -- node hit, explode hit_node = function(self, pos, node) - mcl_mobs:boom(self, pos, 1) + mcl_mobs.mob_class.boom(self,pos, 1) end }) -- TODO: Add blue wither skull --Spawn egg -mcl_mobs:register_egg("mobs_mc:wither", S("Wither"), "#4f4f4f", "#4f4f4f", 0, true) +mcl_mobs.register_egg("mobs_mc:wither", S("Wither"), "#4f4f4f", "#4f4f4f", 0, true) mcl_wip.register_wip_item("mobs_mc:wither") diff --git a/mods/ENTITIES/mobs_mc/wolf.lua b/mods/ENTITIES/mobs_mc/wolf.lua index 711a46799..922069508 100644 --- a/mods/ENTITIES/mobs_mc/wolf.lua +++ b/mods/ENTITIES/mobs_mc/wolf.lua @@ -71,7 +71,7 @@ local wolf = { ent = dog:get_luaentity() ent.owner = clicker:get_player_name() ent.tamed = true - mcl_mobs:set_animation(ent, "sit") + ent:set_animation("sit") ent.walk_chance = 0 ent.jump = false ent.health = self.health @@ -100,7 +100,7 @@ local wolf = { specific_attack = { "player", "mobs_mc:sheep" }, } -mcl_mobs:register_mob("mobs_mc:wolf", wolf) +mcl_mobs.register_mob("mobs_mc:wolf", wolf) -- Tamed wolf @@ -167,7 +167,7 @@ end dog.on_rightclick = function(self, clicker) local item = clicker:get_wielded_item() - if mcl_mobs:feed_tame(self, clicker, 1, true, false) then + if self:feed_tame(clicker, 1, true, false) then return elseif mcl_mobs:protect(self, clicker) then return @@ -194,45 +194,17 @@ dog.on_rightclick = function(self, clicker) end end else - -- Toggle sitting order - if not self.owner or self.owner == "" then - -- Huh? This wolf has no owner? Let's fix this! This should never happen. + -- Huh? This dog has no owner? Let's fix this! This should never happen. self.owner = clicker:get_player_name() end - - local pos = self.object:get_pos() - local particle - if not self.order or self.order == "" or self.order == "sit" then - particle = "mobs_mc_wolf_icon_roam.png" - self.order = "roam" - self.state = "stand" - self.walk_chance = default_walk_chance - self.jump = true - mcl_mobs:set_animation(self, "stand") - -- TODO: Add sitting model - else - particle = "mobs_mc_wolf_icon_sit.png" - self.order = "sit" - self.state = "stand" - self.walk_chance = 0 - self.jump = false - mcl_mobs:set_animation(self, "sit") + if not minetest.settings:get_bool("mcl_extended_pet_control",true) then + self:toggle_sit(clicker,-0.4) end - -- Display icon to show current order (sit or roam) - minetest.add_particle({ - pos = vector.add(pos, {x=0,y=1,z=0}), - velocity = {x=0,y=0.2,z=0}, - expirationtime = 1, - size = 4, - texture = particle, - playername = self.owner, - glow = minetest.LIGHT_MAX, - }) end end -mcl_mobs:register_mob("mobs_mc:dog", dog) +mcl_mobs.register_mob("mobs_mc:dog", dog) -- Spawn mcl_mobs:spawn_specific( "mobs_mc:wolf", @@ -258,4 +230,4 @@ minetest.LIGHT_MAX+1, mobs_mc.water_level+3, mcl_vars.mg_overworld_max) -mcl_mobs:register_egg("mobs_mc:wolf", S("Wolf"), "#d7d3d3", "#ceaf96", 0) +mcl_mobs.register_egg("mobs_mc:wolf", S("Wolf"), "#d7d3d3", "#ceaf96", 0) diff --git a/mods/ENTITIES/mobs_mc/zombie.lua b/mods/ENTITIES/mobs_mc/zombie.lua index eaba127a8..358579dbd 100644 --- a/mods/ENTITIES/mobs_mc/zombie.lua +++ b/mods/ENTITIES/mobs_mc/zombie.lua @@ -102,7 +102,7 @@ local zombie = { attack_npcs = true, } -mcl_mobs:register_mob("mobs_mc:zombie", zombie) +mcl_mobs.register_mob("mobs_mc:zombie", zombie) -- Baby zombie. -- A smaller and more dangerous variant of the zombie @@ -123,7 +123,7 @@ baby_zombie.animation = { punch_start = 109, punch_end = 119 } -mcl_mobs:register_mob("mobs_mc:baby_zombie", baby_zombie) +mcl_mobs.register_mob("mobs_mc:baby_zombie", baby_zombie) -- Husk. -- Desert variant of the zombie @@ -140,7 +140,7 @@ husk.sunlight_damage = 0 husk.drops = drops_common -- TODO: Husks avoid water -mcl_mobs:register_mob("mobs_mc:husk", husk) +mcl_mobs.register_mob("mobs_mc:husk", husk) -- Baby husk. -- A smaller and more dangerous variant of the husk @@ -154,7 +154,7 @@ baby_husk.ignited_by_sunlight = false baby_husk.sunlight_damage = 0 baby_husk.drops = drops_common -mcl_mobs:register_mob("mobs_mc:baby_husk", baby_husk) +mcl_mobs.register_mob("mobs_mc:baby_husk", baby_husk) -- Spawning @@ -368,5 +368,5 @@ mcl_vars.mg_overworld_min, mcl_vars.mg_overworld_max) -- Spawn eggs -mcl_mobs:register_egg("mobs_mc:husk", S("Husk"), "#777361", "#ded88f", 0) -mcl_mobs:register_egg("mobs_mc:zombie", S("Zombie"), "#00afaf", "#799c66", 0) +mcl_mobs.register_egg("mobs_mc:husk", S("Husk"), "#777361", "#ded88f", 0) +mcl_mobs.register_egg("mobs_mc:zombie", S("Zombie"), "#00afaf", "#799c66", 0) diff --git a/mods/ENTITIES/mobs_mc/zombiepig.lua b/mods/ENTITIES/mobs_mc/zombiepig.lua index 9545e4022..4e89db4f6 100644 --- a/mods/ENTITIES/mobs_mc/zombiepig.lua +++ b/mods/ENTITIES/mobs_mc/zombiepig.lua @@ -93,7 +93,7 @@ local pigman = { fire_damage_resistant = true, } -mcl_mobs:register_mob("mobs_mc:pigman", pigman) +mcl_mobs.register_mob("mobs_mc:pigman", pigman) -- Baby pigman. -- A smaller and more dangerous variant of the pigman @@ -113,7 +113,7 @@ baby_pigman.run_velocity = 2.4 baby_pigman.light_damage = 0 baby_pigman.child = 1 -mcl_mobs:register_mob("mobs_mc:baby_pigman", baby_pigman) +mcl_mobs.register_mob("mobs_mc:baby_pigman", baby_pigman) -- Regular spawning in the Nether mcl_mobs:spawn_specific( @@ -152,4 +152,4 @@ mcl_vars.mg_nether_max) --mobs:spawn_specific("mobs_mc:pigman", {"mcl_portals:portal"}, {"air"}, 0, minetest.LIGHT_MAX+1, 30, 500, 4, mcl_vars.mg_overworld_min, mcl_vars.mg_overworld_max) -- spawn eggs -mcl_mobs:register_egg("mobs_mc:pigman", S("Zombie Pigman"), "#ea9393", "#4c7129", 0) +mcl_mobs.register_egg("mobs_mc:pigman", S("Zombie Pigman"), "#ea9393", "#4c7129", 0) diff --git a/mods/ITEMS/mcl_bells/init.lua b/mods/ITEMS/mcl_bells/init.lua index f1fce85e9..32bdfe3d7 100644 --- a/mods/ITEMS/mcl_bells/init.lua +++ b/mods/ITEMS/mcl_bells/init.lua @@ -9,7 +9,7 @@ function mcl_bells.ring_once(pos) local vv=minetest.get_objects_inside_radius(pos,150) for _,o in pairs(vv) do if o.type == "npc" then - mcl_mobs:gopath(o:get_luaentity(),pos,function() end) + o:get_luaentity():gopath(pos,function() end) end end end diff --git a/settingtypes.txt b/settingtypes.txt index 78aa9d44b..0d517499a 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -222,6 +222,9 @@ mcl_bookshelf_inventories (Enable bookshelf inventories) bool true # Enable swiftness on enchanted golden apples mcl_enable_fapples (Enable swiftness on enchanted golden apples) bool true +# All tameable mobs listen to the "sit" righclick like dogs +mcl_extended_pet_control (Extended pet control) bool true + [Debugging] # If enabled, this will show the itemstring of an item in the description. mcl_item_id_debug (Item ID Debug) bool false