diff --git a/mods/mobs/api.lua b/mods/mobs/api.lua new file mode 100644 index 0000000..faf2228 --- /dev/null +++ b/mods/mobs/api.lua @@ -0,0 +1,4971 @@ +local MP = minetest.get_modpath(minetest.get_current_modname()) +-- Check for translation method +local S + +if minetest.get_translator ~= nil then + S = minetest.get_translator("mobs") +else + if minetest.get_modpath("intllib") then + dofile(minetest.get_modpath("intllib").."/init.lua") + if intllib.make_gettext_pair then + S = intllib.make_gettext_pair() -- new gettext method + else + S = intllib.Getter() -- old text file method + end + else -- boilerplate function + S = function(str, ...) + local args = {...} + return str:gsub("@%d+", function(match) + return args[tonumber(match:sub(2))] + end) + end + end +end + +-- CMI support check +local use_cmi = minetest.global_exists("cmi") + +mobs = { + mod = "redo", + version = "20230805", + intllib = S, + invis = minetest.global_exists("invisibility") and invisibility or {}, + node_ice = "default:ice", + node_snow = minetest.registered_aliases["mapgen_snow"] or "default:snow" or "mcl_core:snow", + node_dirt = minetest.registered_aliases["mapgen_dirt"] or "default:dirt" or "mcl_core:dirt" +} +mobs.fallback_node = mobs.node_dirt + + +-- localize common functions +local pi = math.pi +local square = math.sqrt +local sin = math.sin +local cos = math.cos +local abs = math.abs +local min = math.min +local max = math.max +local random = math.random +local floor = math.floor +local ceil = math.ceil +local rad = math.rad +local deg = math.deg +local atann = math.atan +local atan = function(x) + if not x or x ~= x then + return 0 -- NaN + else + return atann(x) + end +end +local table_copy = table.copy +local table_remove = table.remove +local vadd = vector.add +local vdirection = vector.direction +local vmultiply = vector.multiply +local vsubtract = vector.subtract +local settings = minetest.settings + +-- creative check +local creative_cache = minetest.settings:get_bool("creative_mode") +function mobs.is_creative(name) + return creative_cache or minetest.check_player_privs(name, {creative = true}) +end + +-- Load settings +local damage_enabled = settings:get_bool("enable_damage") +local mobs_spawn = settings:get_bool("mobs_spawn") ~= false +local peaceful_only = settings:get_bool("only_peaceful_mobs") +local disable_blood = settings:get_bool("mobs_disable_blood") +local mob_hit_effect = settings:get_bool("mob_hit_effect") +local mobs_drop_items = settings:get_bool("mobs_drop_items") ~= false +local mobs_griefing = settings:get_bool("mobs_griefing") ~= false +local spawn_protected = settings:get_bool("mobs_spawn_protected") ~= false +local spawn_monster_protected = settings:get_bool("mobs_spawn_monster_protected") ~= false +local remove_far = settings:get_bool("remove_far_mobs") ~= false +local mob_area_spawn = settings:get_bool("mob_area_spawn") +local difficulty = tonumber(settings:get("mob_difficulty")) or 1.0 +local max_per_block = tonumber(settings:get("max_objects_per_block") or 99) +local mob_nospawn_range = tonumber(settings:get("mob_nospawn_range") or 12) +local active_limit = tonumber(settings:get("mob_active_limit") or 0) +local mob_chance_multiplier = tonumber(settings:get("mob_chance_multiplier") or 1) +local peaceful_player_enabled = settings:get_bool("enable_peaceful_player") +local mob_smooth_rotate = settings:get_bool("mob_smooth_rotate") ~= false +local mob_height_fix = settings:get_bool("mob_height_fix") ~= false +local mob_log_spawn = settings:get_bool("mob_log_spawn") == true +local active_mobs = 0 + +-- get loop timers for node and main functions +local node_timer_interval = tonumber(settings:get("mob_node_timer_interval") or 0.25) +local main_timer_interval = tonumber(settings:get("mob_main_timer_interval") or 1.0) + +-- pathfinding settings +local pathfinding_enable = settings:get_bool("mob_pathfinding_enable") or true +-- Use pathfinder mod if available +local pathfinder_enable = settings:get_bool("mob_pathfinder_enable") or true +-- how long before stuck mobs start searching +local pathfinding_stuck_timeout = tonumber( + settings:get("mob_pathfinding_stuck_timeout")) or 3.0 +-- how long will mob follow path before giving up +local pathfinding_stuck_path_timeout = tonumber(settings:get("mob_pathfinding_stuck_path_timeout")) or 5.0 +-- which algorithm to use, Dijkstra(default) or A*_noprefetch or A* +-- fix settings not allowing "*" +local pathfinding_algorithm = settings:get("mob_pathfinding_algorithm") or "Dijkstra" + +if pathfinding_algorithm == "AStar_noprefetch" then + pathfinding_algorithm = "A*_noprefetch" +elseif pathfinding_algorithm == "AStar" then + pathfinding_algorithm = "A*" +end + +-- max search distance from search positions (default 16) +local pathfinding_searchdistance = tonumber( + settings:get("mob_pathfinding_searchdistance") or 16) +-- max jump height (default 4) +local pathfinding_max_jump = tonumber(settings:get("mob_pathfinding_max_jump") or 4) +-- max drop height (default 6) +local pathfinding_max_drop = tonumber(settings:get("mob_pathfinding_max_drop") or 6) + +-- Peaceful mode message so players will know there are no monsters +if peaceful_only then + minetest.register_on_joinplayer(function(player) + minetest.chat_send_player(player:get_player_name(), + S("** Peaceful Mode Active - No Monsters Will Spawn")) + end) +end + +-- calculate aoc range for mob count +local aoc_range = tonumber(settings:get("active_block_range")) * 16 + +-- can we attack Creatura mobs ? +local creatura = minetest.get_modpath("creatura") and + settings:get_bool("mobs_attack_creatura") == true + + +mobs.mob_class = { + stepheight = 1.1, + fly_in = "air", + owner = "", + order = "", + jump_height = 4, + lifetimer = 180, -- 3 minutes + physical = true, + collisionbox = {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25}, + visual_size = {x = 1, y = 1}, + texture_mods = "", + makes_footstep_sound = false, + view_range = 5, + walk_velocity = 1, + run_velocity = 2, + light_damage = 0, + light_damage_min = 14, + light_damage_max = 15, + water_damage = 0, + lava_damage = 4, + fire_damage = 4, + air_damage = 0, + suffocation = 2, + fall_damage = 1, + fall_speed = -10, -- must be lower than -2 (default: -10) + drops = {}, + armor = 100, + sounds = {}, + jump = true, + knock_back = true, + walk_chance = 50, + stand_chance = 30, + attack_chance = 5, + passive = false, + blood_amount = 5, + blood_texture = "mobs_blood.png", + shoot_offset = 0, + floats = 1, -- floats in water by default + replace_offset = 0, + timer = 0, + env_damage_timer = 0, -- only used when state = "attack" + tamed = false, + pause_timer = 0, + horny = false, + hornytimer = 0, + child = false, + gotten = false, + health = 0, + reach = 3, + docile_by_day = false, + time_of_day = 0.5, + fear_height = 0, + runaway_timer = 0, + immune_to = {}, + explosion_timer = 3, + allow_fuse_reset = true, + stop_to_explode = true, + dogshoot_count = 0, + dogshoot_count_max = 5, + dogshoot_count2_max = 5, + group_attack = false, + attack_monsters = false, + attack_animals = false, + attack_players = true, + attack_npcs = true, + friendly_fire = true, + facing_fence = false, + _breed_countdown = nil, + _cmi_is_mob = true +} + +local mob_class = mobs.mob_class -- Compatibility +local mob_class_meta = {__index = mob_class} + + +-- play sound +function mob_class:mob_sound(sound) + + if sound then + + -- higher pitch for a child + local pitch = self.child and 1.5 or 1.0 + + -- a little random pitch to be different + pitch = pitch + random(-10, 10) * 0.005 + + minetest.sound_play(sound, { + object = self.object, + gain = 1.0, + max_hear_distance = self.sounds.distance, + pitch = pitch + }, true) + end +end + + +-- attack player/mob +function mob_class:do_attack(player) + + if self.state == "attack" then + return + end + + self.attack = player + self.state = "attack" + + if random(0, 100) < 90 then + self:mob_sound(self.sounds.war_cry) + end +end + + +-- calculate distance +local get_distance = function(a, b) + + if not a or not b then return 50 end -- nil check and default distance + + local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z + + return square(x * x + y * y + z * z) +end + + +-- are we a real player ? +local function is_player(player) + + if player and type(player) == "userdata" then + return true + end +end + + +-- collision function based on jordan4ibanez' open_ai mod +function mob_class:collision() + + local pos = self.object:get_pos() ; if not pos then return {0, 0} end + local x, z = 0, 0 + local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5 + + for _,object in ipairs(minetest.get_objects_inside_radius(pos, width)) do + + if is_player(object) then + + 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 + + +-- check if string exists in another string or table +local function check_for(look_for, look_inside) + + if type(look_inside) == "string" and look_inside == look_for then + + return true + + elseif type(look_inside) == "table" then + + for _, str in pairs(look_inside) do + + if not str then goto continue end + if not str or not str.find then print("Every node list (fly_in, follow, specific_attack or runaway_from) should contain strings, but entry '" .. _ .. "' is a " .. type(str) .. ".") end + if not str.find then goto continue end + + if str then + if str:find("group:") then + local group = str:split(":")[2] or "" + if minetest.get_item_group(look_for, group) ~= 0 then + return true + end + end + end + + if str == look_for then + return true + end + + ::continue:: + end + end + + return false +end + + +-- move mob in facing direction +function mob_class:set_velocity(v) + + -- halt mob if it has been ordered to stay + if self.order == "stand" then + + local vel = self.object:get_velocity() or {y = 0} + + self.object:set_velocity({x = 0, y = vel.y, z = 0}) + + return + end + + 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 + + local yaw = (self.object:get_yaw() or 0) + (self.rotate or 0) + + -- nil check for velocity + v = v or 0.01 + + -- check if standing in liquid with max viscosity of 7 + local visc = min(minetest.registered_nodes[self.standing_in].liquid_viscosity, 7) + + -- only slow mob trying to move while inside a viscous fluid that + -- they aren't meant to be in (fish in water, spiders in cobweb etc) + if v > 0 and visc and visc > 0 and not check_for(self.standing_in, self.fly_in) then + v = v / (visc + 1) + end + + -- set velocity + local vel = self.object:get_velocity() or {x = 0, y = 0, z = 0} + + local new_vel = { + x = (sin(yaw) * -v) + c_x, + y = vel.y, + z = (cos(yaw) * v) + c_y} + + self.object:set_velocity(new_vel) +end + +-- global version of above function +function mobs:set_velocity(entity, v) + mob_class.set_velocity(entity, v) +end + + +-- calculate mob velocity +function mob_class:get_velocity() + + local v = self.object:get_velocity() + + if not v then return 0 end + + return (v.x * v.x + v.z * v.z) ^ 0.5 +end + + +-- set and return valid yaw +function mob_class:set_yaw(yaw, delay) + + if not yaw or yaw ~= yaw then + yaw = 0 + end + + delay = mob_smooth_rotate and delay or 0 + + -- simplified yaw clamp + if yaw > 6.283185 then + yaw = yaw - 6.283185 + elseif yaw < 0 then + yaw = 6.283185 + yaw + end + + if delay == 0 then + + self.object:set_yaw(yaw) + + return yaw + end + + self.target_yaw = yaw + self.delay = delay + + return self.target_yaw +end + +-- global function to set mob yaw +function mobs:yaw(entity, yaw, delay) + mob_class.set_yaw(entity, yaw, delay) +end + + +-- set defined animation +function mob_class:set_animation(anim, force) + + if not self.animation or not anim then return end + + self.animation.current = self.animation.current or "" + + -- only use different animation for attacks when using same set + if force ~= true and anim ~= "punch" and anim ~= "shoot" + and string.find(self.animation.current, anim) then + return + end + + local num = 0 + + -- check for more than one animation (max 4) + for n = 1, 4 do + + if self.animation[anim .. n .. "_start"] + and self.animation[anim .. n .. "_end"] then + num = n + end + end + + -- choose random animation from set + if num > 0 then + num = random(0, num) + anim = anim .. (num ~= 0 and num or "") + end + + if (anim == self.animation.current and force ~= true) + or not self.animation[anim .. "_start"] + or not self.animation[anim .. "_end"] then + return + end + + self.animation.current = anim + + self.object:set_animation({ + x = self.animation[anim .. "_start"], + y = self.animation[anim .. "_end"]}, + self.animation[anim .. "_speed"] or self.animation.speed_normal or 15, + 0, self.animation[anim .. "_loop"] ~= false) +end + +function mobs:set_animation(entity, anim) + entity.set_animation(entity, anim) +end + + +-- check line of sight (BrunoMine) +local function line_of_sight(self, pos1, pos2, stepsize) + + stepsize = stepsize or 1 + + local s = 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 = get_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 'walkable' 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 = get_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 + + +-- check line of sight (by BrunoMine, tweaked by Astrobe) +local function new_line_of_sight(self, pos1, pos2, stepsize) + + if not pos1 or not pos2 then return end + + stepsize = stepsize or 1 + + local stepv = vmultiply(vdirection(pos1, pos2), stepsize) + + 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 + + -- It continues to advance in the line of sight in search of a real + -- obstruction which counts as 'walkable' nodebox. + while minetest.registered_nodes[nn] + and (minetest.registered_nodes[nn].walkable == false) do + + npos1 = vadd(npos1, stepv) + + if get_distance(npos1, pos2) < stepsize then return true end + + -- 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 + + +-- check line of sight using raycasting (thanks Astrobe) +local function ray_line_of_sight(self, pos1, pos2) + + local ray = minetest.raycast(pos1, pos2, true, false) + local thing = ray:next() + + while thing do -- thing.type, thing.ref + + if thing.type == "node" then + + local name = minetest.get_node(thing.under).name + + if minetest.registered_items[name] + and minetest.registered_items[name].walkable then + return false + end + end + + thing = ray:next() + end + + return true +end + + +function mob_class:line_of_sight(pos1, pos2, stepsize) + + if minetest.raycast then -- only use if minetest 5.0 is detected + return ray_line_of_sight(self, pos1, pos2) + end + + return line_of_sight(self, pos1, pos2, stepsize) +end + +-- global function +function mobs:line_of_sight(entity, pos1, pos2, stepsize) + return entity:line_of_sight(pos1, pos2, stepsize) +end + + +function mob_class:attempt_flight_correction(override) + + if self:flight_check() and override ~= true then return true end + + -- We are not flying in what we are supposed to. + -- See if we can find intended flight medium and return to it + local pos = self.object:get_pos() ; if not pos then return true end + local searchnodes = self.fly_in + + if type(searchnodes) == "string" then + searchnodes = {self.fly_in} + end + + local flyable_nodes = minetest.find_nodes_in_area( + {x = pos.x - 1, y = pos.y - 1, z = pos.z - 1}, + {x = pos.x + 1, y = pos.y + 2, z = pos.z + 1}, searchnodes) + + if #flyable_nodes < 1 then + return false + end + + local escape_target = flyable_nodes[random(#flyable_nodes)] + local escape_direction = vdirection(pos, escape_target) + + self.object:set_velocity(vmultiply(escape_direction, 1)) + + return true +end + + +-- are we flying in what we are suppose to? (taikedz) +function mob_class:flight_check() + + local def = minetest.registered_nodes[self.standing_in] + + if not def then return false end + + -- are we standing inside what we should be to fly/swim ? + if check_for(self.standing_in, self.fly_in) then + return true + end + + -- stops mobs getting stuck inside stairs and plantlike nodes + if def.drawtype ~= "airlike" + and def.drawtype ~= "liquid" + and def.drawtype ~= "flowingliquid" then + return true + end + + return false +end + + +-- turn mob to face position +local function yaw_to_pos(self, target, rot) + + rot = rot or 0 + + local pos = self.object:get_pos() + local vec = {x = target.x - pos.x, z = target.z - pos.z} + local yaw = (atan(vec.z / vec.x) + rot + pi / 2) - self.rotate + + if target.x > pos.x then + yaw = yaw + pi + end + + yaw = self:set_yaw(yaw, rot) + + return yaw +end + +function mobs:yaw_to_pos(self, target, rot) + return yaw_to_pos(self, target, rot) +end + + +-- if stay near set then periodically check for nodes and turn towards them +function mob_class:do_stay_near() + + if not self.stay_near then return false end + + local pos = self.object:get_pos() + local searchnodes = self.stay_near[1] + local chance = self.stay_near[2] or 10 + + if not pos or random(chance) > 1 then + return false + end + + if type(searchnodes) == "string" then + searchnodes = {self.stay_near[1]} + end + + local r = self.view_range + local nearby_nodes = minetest.find_nodes_in_area( + {x = pos.x - r, y = pos.y - 1, z = pos.z - r}, + {x = pos.x + r, y = pos.y + 1, z = pos.z + r}, searchnodes) + + if #nearby_nodes < 1 then + return false + end + + yaw_to_pos(self, nearby_nodes[random(#nearby_nodes)]) + + self:set_animation("walk") + + self:set_velocity(self.walk_velocity) + + return true +end + + +-- custom particle effects +local function effect( + pos, amount, texture, min_size, max_size, radius, gravity, glow, fall) + + radius = radius or 2 + min_size = min_size or 0.5 + max_size = max_size or 1 + gravity = gravity or -10 + glow = glow or 0 + + if fall == true then + fall = 0 + elseif fall == false then + fall = radius + else + fall = -radius + end + + minetest.add_particlespawner({ + amount = amount, + time = 0.25, + minpos = pos, + maxpos = pos, + minvel = {x = -radius, y = fall, 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 mobs:effect( + pos, amount, texture, min_size, max_size, radius, gravity, glow, fall) + + effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, fall) +end + + +-- Thanks Wuzzy for the following editable settings + +local HORNY_TIME = 30 +local HORNY_AGAIN_TIME = 60 * 5 -- 5 minutes +local CHILD_GROW_TIME = 60 * 20 -- 20 minutes + + +-- update nametag and infotext +function mob_class:update_tag() + + local col + local qua = self.hp_max / 6 + + if self.health <= qua then + col = "#FF0000" + elseif self.health <= (qua * 2) then + col = "#FF7A00" + elseif self.health <= (qua * 3) then + col = "#FFB500" + elseif self.health <= (qua * 4) then + col = "#FFFF00" + elseif self.health <= (qua * 5) then + col = "#B4FF00" + elseif self.health > (qua * 5) then + col = "#00FF00" + end + + local text = "" + + if self.horny == true then + text = "\nLoving: " .. (self.hornytimer - (HORNY_TIME + HORNY_AGAIN_TIME)) + elseif self.child == true then + text = "\nGrowing: " .. (self.hornytimer - CHILD_GROW_TIME) + elseif self._breed_countdown then + text = "\nBreeding: " .. self._breed_countdown + end + + if self.protected then + if self.protected == 2 then + text = text .. "\nProtection: Level 2" + else + text = text .. "\nProtection: Level 1" + end + end + + self.infotext = "Health: " .. self.health .. " / " .. self.hp_max + .. (self.owner == "" and "" or "\nOwner: " .. self.owner) + .. text + + -- set changes + self.object:set_properties({ + nametag = self.nametag, nametag_color = col, infotext = self.infotext}) +end + + +-- drop items +function mob_class:item_drop() + + -- no drops if disabled by setting or mob is child + if not mobs_drop_items or self.child then return end + + local pos = self.object:get_pos() + + -- check for drops function + self.drops = type(self.drops) == "function" and self.drops(pos) or self.drops + + -- check for nil or no drops + if not self.drops or #self.drops == 0 then + return + end + + -- was mob killed by player? + local death_by_player = self.cause_of_death + and self.cause_of_death.puncher + and is_player(self.cause_of_death.puncher) + + -- check for tool 'looting_level' under tool_capabilities as default, or use + -- meta string 'looting_level' if found (max looting level is 3). + local looting = 0 + + if death_by_player then + + local wield_stack = self.cause_of_death.puncher:get_wielded_item() + local wield_name = wield_stack:get_name() + local wield_stack_meta = wield_stack:get_meta() + local item_def = minetest.registered_items[wield_name] + local item_looting = item_def and item_def.tool_capabilities and + item_def.tool_capabilities.looting_level or 0 + + looting = tonumber(wield_stack_meta:get_string("looting_level")) or item_looting + looting = min(looting, 3) + end + +--print("--- looting level", looting) + + local obj, item, num + + for n = 1, #self.drops do + + if random(self.drops[n].chance) == 1 then + + num = random(self.drops[n].min or 0, self.drops[n].max or 1) + item = self.drops[n].name + + -- cook items on a hot death + if self.cause_of_death.hot 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 + + -- only drop rare items (drops.min = 0) if killed by player + if death_by_player or self.drops[n].min ~= 0 then + obj = minetest.add_item(pos, ItemStack(item .. " " .. (num + looting))) + 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 + + +-- remove mob and descrease counter +local function remove_mob(self, decrease) + + self.object:remove() + + if decrease and active_limit > 1 then + active_mobs = active_mobs - 1 + + if active_mobs < 0 then + active_mobs = 0 + end + end +--print("-- active mobs: " .. active_mobs .. " / " .. active_limit) +end + +-- global function for removing mobs +function mobs:remove(self, decrease) + remove_mob(self, decrease) +end + + +-- check if mob is dead or only hurt +function mob_class:check_for_death(cmi_cause) + + -- We dead already + 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? play hurt sound + if self.health > 0 then + + -- only play hurt sound if damaged + if damaged then + self:mob_sound(self.sounds.damage) + end + + -- make sure health isn't higher than max + if self.health > self.hp_max then + self.health = self.hp_max + end + + self:update_tag() + + return false + end + + self.cause_of_death = cmi_cause + + -- drop items and play death sound + self:item_drop() + self:mob_sound(self.sounds.death) + + local pos = self.object:get_pos() + + -- execute custom death function + if pos and self.on_die then + + self:on_die(pos) + + if use_cmi then + cmi.notify_die(self.object, cmi_cause) + end + + remove_mob(self, true) + + return true + end + + -- reset vars and set state + self.attack = nil + self.following = nil + self.v_start = false + self.timer = 0 + self.blinktimer = 0 + self.passive = true + self.state = "die" + self.fly = false + + -- check for custom death function and die animation + if 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 + local length = max((frames / speed), 0) + local rot = self.animation.die_rotate and 5 + + self.object:set_properties({ + pointable = false, collide_with_objects = false, + automatic_rotate = rot, static_save = false + }) + self:set_velocity(0) + self:set_animation("die") + + minetest.after(length, function(self) + + if self.object:get_luaentity() then + + if use_cmi then + cmi.notify_die(self.object, cmi_cause) + end + + remove_mob(self, true) + end + end, self) + + return true + + elseif pos then -- otherwise remove mob and show particle effect + + if use_cmi then + cmi.notify_die(self.object, cmi_cause) + end + + remove_mob(self, true) + + effect(pos, 20, "tnt_smoke.png") + end + + return true +end + + +-- get node but use fallback for nil or unknown +local function node_ok(pos, fallback) + + 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 or mobs.fallback_node)] +end + + +-- Returns true is node can deal damage to self +function mobs:is_node_dangerous(mob_object, nodename) + + if mob_object.water_damage > 0 + and minetest.get_item_group(nodename, "water") ~= 0 then + return true + end + + if mob_object.lava_damage > 0 + and minetest.get_item_group(nodename, "lava") ~= 0 then + return true + end + + if mob_object.fire_damage > 0 + and minetest.get_item_group(nodename, "fire") ~= 0 then + return true + end + + if minetest.registered_nodes[nodename].damage_per_second > 0 then + return true + end + + return false +end + +local function is_node_dangerous(mob_object, nodename) + return mobs:is_node_dangerous(mob_object, nodename) +end + + +-- is mob facing a cliff +function mob_class:is_at_cliff() + + if self.driver or self.fear_height == 0 then -- 0 for no falling protection! + return false + end + + -- get yaw but if nil returned object no longer exists + local yaw = self.object:get_yaw() + + if not yaw then return false end + + 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}) + + -- check for straight drop + if free_fall then + return true + end + + local bnode = node_ok(blocker, "air") + + -- will we drop onto dangerous node? + if is_node_dangerous(self, bnode.name) then + return true + end + + local def = minetest.registered_nodes[bnode.name] + + return (not def and def.walkable) +end + + +-- environmental damage (water, lava, fire, light etc.) +function mob_class:do_env_damage() + + self:update_tag() + + local pos = self.object:get_pos() ; if not pos then return end + + self.time_of_day = minetest.get_timeofday() + + -- halt mob if standing inside ignore node + if self.standing_in == "ignore" then + + self.object:set_velocity({x = 0, y = 0, z = 0}) + + return true + end + + -- particle appears at random mob height + local py = { + x = pos.x, + y = pos.y + random(self.collisionbox[2], self.collisionbox[5]), + z = pos.z + } + + local nodef = minetest.registered_nodes[self.standing_in] + + -- water + if self.water_damage ~= 0 and nodef.groups.water then + + self.health = self.health - self.water_damage + + effect(py, 5, "bubble.png", nil, nil, 1, nil) + + if self:check_for_death({type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + + -- lava damage + elseif self.lava_damage ~= 0 and nodef.groups.lava then + + self.health = self.health - self.lava_damage + + effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true) + + if self:check_for_death({type = "environment", pos = pos, + node = self.standing_in, hot = true}) then + return true + end + + -- fire damage + elseif self.fire_damage ~= 0 and nodef.groups.fire then + + self.health = self.health - self.fire_damage + + effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true) + + if self:check_for_death({type = "environment", pos = pos, + node = self.standing_in, hot = true}) then + return true + end + + -- damage_per_second node check (not fire and lava) + elseif nodef.damage_per_second and nodef.damage_per_second ~= 0 + and nodef.groups.lava == nil and nodef.groups.fire == nil then + + self.health = self.health - nodef.damage_per_second + + effect(py, 5, "tnt_smoke.png") + + if self:check_for_death({type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + + -- air damage + if self.air_damage ~= 0 and self.standing_in == "air" then + + self.health = self.health - self.air_damage + + effect(py, 3, "bubble.png", 1, 1, 1, 0.2) + + if self:check_for_death({type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + + -- is mob light sensitive, or scared of the dark :P + if self.light_damage ~= 0 then + + local light + + -- if max set to 16 then only kill mob with natural sunlight + if self.light_damage_max == 16 then + light = minetest.get_natural_light(pos) or 0 + else + light = minetest.get_node_light(pos) or 0 + end + + if light >= self.light_damage_min + and light <= self.light_damage_max then + + self.health = self.health - self.light_damage + + effect(py, 5, "tnt_smoke.png") + + if self:check_for_death({type = "light"}) then + return true + end + end + end + + --- suffocation inside solid node + if (self.suffocation and self.suffocation ~= 0) + 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) then + + local damage + + if self.suffocation == true then + damage = 2 + else + damage = (self.suffocation or 2) + end + + self.health = self.health - damage + + if self:check_for_death({type = "suffocation", + pos = pos, node = self.standing_in}) then + return true + end + + -- try to jump out of block + self.object:set_velocity({x = 0, y = self.jump_height, z = 0}) + end + + return self:check_for_death({type = "unknown"}) +end + + +-- jump if facing a solid node (not fences or gates) +function mob_class:do_jump() + + local vel = self.object:get_velocity() ; if not vel then return false end + + -- don't jump if ordered to stand or already in mid-air or moving forwards + if self.state == "stand" or vel.y ~= 0 or self:get_velocity() > 0.2 then + return false + end + + -- we can only jump if standing on solid node + if minetest.registered_nodes[self.standing_on].walkable == false then + return false + end + + -- is there anything stopping us from jumping up onto a block? + local blocked = minetest.registered_nodes[self.looking_above].walkable + + -- if mob can leap then remove blockages and let them try + if self.can_leap == true then + blocked = false + self.facing_fence = false + end + + -- jump if possible + if self.jump and self.jump_height > 0 and not self.fly and not self.child + and self.order ~= "stand" + and (self.walk_chance == 0 or minetest.registered_items[self.looking_at].walkable) + and not blocked + and not self.facing_fence + and self.looking_at ~= mobs.node_snow then + + vel.y = self.jump_height + + self:set_animation("jump") -- only if defined + + self.object:set_velocity(vel) + + -- when in air move forward + minetest.after(0.3, function(self, vel) + + if self.object:get_luaentity() then + + self.object:set_acceleration({ + x = vel.x * 2, + y = 0, + z = vel.z * 2 + }) + end + end, self, vel) + + if self:get_velocity() > 0 then + self:mob_sound(self.sounds.jump) + end + + self.jump_count = 0 + + return true + end + + -- if blocked for 3 counts then turn + if not self.following and (self.facing_fence or blocked) then + + self.jump_count = (self.jump_count or 0) + 1 + + if self.jump_count > 2 then + + local yaw = self.object:get_yaw() or 0 + local turn = random(0, 2) + 1.35 + + self:set_yaw(yaw + turn, 12) + + self.jump_count = 0 + end + end + + return false +end + + +-- blast damage to entities nearby (modified from TNT mod) +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 = get_distance(pos, obj_pos) + + if dist < 1 then dist = 1 end + + local damage = floor((4 / dist) * radius) + + -- 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 + + +-- can mob see player +local function is_invisible(self, player_name) + + if mobs.invis[player_name] and not self.ignore_invisibility then + return true + end +end + + +-- should mob follow what I'm holding ? +function mob_class:follow_holding(clicker) + + if is_invisible(self, clicker:get_player_name()) then + return false + end + + local item = clicker:get_wielded_item() + + -- are we holding an item mob can follow ? + if check_for(item:get_name(), self.follow) then + return true + end + + return false +end + + +-- find two animals of same type and breed if nearby and horny +function mob_class:breed() + + -- child takes a long time before growing into adult + if self.child == true then + + self.hornytimer = self.hornytimer + 1 + + if self.hornytimer > CHILD_GROW_TIME then + + self.child = false + self.hornytimer = 0 + + -- replace child texture with adult one + if self.mommy_tex then + self.base_texture = self.mommy_tex + self.mommy_tex = nil + end + + 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 + local pos = self.object:get_pos() ; if not pos then return end + local ent = self.object:get_luaentity() + + pos.y = pos.y + (ent.collisionbox[2] * -1) + + self.object:set_pos(pos) + + -- jump slightly when fully grown so as not to fall into ground + self.object:set_velocity({x = 0, y = 2, z = 0 }) + end + 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 + + self:update_tag() + end + + -- find another same animal who is also horny and mate if nearby + if self.horny == true + and self.hornytimer <= HORNY_TIME then + + 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 ent + + 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 = ent.name:split(":") + local selfname = self.name:split(":") + + if entname[1] == selfname[1] then + + entname = entname[2]:split("_") + selfname = selfname[2]:split("_") + + if entname[1] == selfname[1] then + canmate = true + end + end + end + end + + -- found another similar horny animal that isn't self? + if ent and ent.object ~= self.object + and canmate == true + and ent.horny == true + and ent.hornytimer <= HORNY_TIME then + + local pos2 = ent.object:get_pos() + + -- Have mobs face one another + yaw_to_pos(self, pos2) + yaw_to_pos(ent, self.object:get_pos()) + + self.hornytimer = HORNY_TIME + 1 + ent.hornytimer = HORNY_TIME + 1 + + self:update_tag() + + -- have we reached active mob limit + if active_limit > 0 and active_mobs >= active_limit then + minetest.chat_send_player(self.owner, + S("Active Mob Limit Reached!") + .. " (" .. active_mobs + .. " / " .. active_limit .. ")") + return + end + + -- spawn baby + minetest.after(5, function(self, ent) + + if not self.object:get_luaentity() then + return + end + + -- reset parent movement + self.follow_stop = false + ent.follow_stop = false + + -- custom breed function + if self.on_breed then + + -- when false skip going any further + if self:on_breed(ent) == false then + return + end + else + effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5) + end + + pos.y = pos.y + 0.5 -- spawn child a little higher + + local mob = minetest.add_entity(pos, self.name) + local ent2 = mob:get_luaentity() + local textures = self.base_texture + + -- make sure baby is actually there + if ent2 then + + -- using specific child texture (if found) + if self.child_texture then + textures = self.child_texture[1] + ent2.mommy_tex = self.base_texture + end + + -- and resize to half height + mob:set_properties({ + textures = textures, + visual_size = { + x = self.base_size.x * .5, + y = self.base_size.y * .5 + }, + collisionbox = { + self.base_colbox[1] * .5, + self.base_colbox[2] * .5, + self.base_colbox[3] * .5, + self.base_colbox[4] * .5, + self.base_colbox[5] * .5, + self.base_colbox[6] * .5 + }, + selectionbox = { + self.base_selbox[1] * .5, + self.base_selbox[2] * .5, + self.base_selbox[3] * .5, + self.base_selbox[4] * .5, + self.base_selbox[5] * .5, + self.base_selbox[6] * .5 + } + }) + + -- tamed and owned by parents' owner + ent2.child = true + ent2.tamed = true + ent2.owner = self.owner + ent2.base_texture = textures + end + end, self, ent) + + break + end + end + end +end + + +-- find and replace what mob is looking for (grass, wheat etc.) +function mob_class:replace(pos) + + local vel = self.object:get_velocity() + if not vel then return end + + if not mobs_griefing + or not self.replace_rate + or not self.replace_what + or self.child == true + or vel.y ~= 0 + or random(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 + + if #minetest.find_nodes_in_area(pos, pos, what) > 0 then + +-- print("replace node = ".. minetest.get_node(pos).name, pos.y) + + if self.on_replace then + + local oldnode = what or "" + local newnode = with + + -- pass actual node name when using table or groups + if type(oldnode) == "table" + or oldnode:find("group:") then + oldnode = minetest.get_node(pos).name + end + + if self:on_replace(pos, oldnode, newnode) == false then + return + end + end + + minetest.set_node(pos, {name = with}) + end +end + + +-- 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 + + +local los_switcher = false +local height_switcher = false + +-- are we able to dig this node and add drops? +local function can_dig_drop(pos) + + if minetest.is_protected(pos, "") then + return false + end + + local node = node_ok(pos, "air").name + local ndef = minetest.registered_nodes[node] + + if node ~= "ignore" + and ndef + and ndef.drawtype ~= "airlike" + and not ndef.groups.level + and not ndef.groups.unbreakable + and not ndef.groups.liquid then + + local drops = minetest.get_node_drops(node) + + for _, item in ipairs(drops) do + + minetest.add_item({ + x = pos.x - 0.5 + random(), + y = pos.y - 0.5 + random(), + z = pos.z - 0.5 + random() + }, item) + end + + minetest.remove_node(pos) + + return true + end + + return false +end + + +local pathfinder_mod = minetest.get_modpath("pathfinder") + +-- 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 = p + + + -- 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 self.object:get_luaentity() then + + if has_lineofsight then + self.path.following = false + end + end + end, self) + end -- can see target! + end + + if self.path.stuck_timer > pathfinding_stuck_timeout and not self.path.following then + + use_pathfind = true + self.path.stuck_timer = 0 + + minetest.after(1, function(self) + + if self.object:get_luaentity() then + + if has_lineofsight then + self.path.following = false + end + end + end, self) + end + + if self.path.stuck_timer > pathfinding_stuck_path_timeout and self.path.following then + + use_pathfind = true + self.path.stuck_timer = 0 + + minetest.after(1, function(self) + + if self.object:get_luaentity() then + + if has_lineofsight then + self.path.following = false + end + end + end, self) + end + + if abs(vsubtract(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 + + -- lets try find a path, first take care of positions + -- since pathfinder is very sensitive + if use_pathfind then + + -- 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 = pathfinding_max_drop + + if self.fear_height ~= 0 then dropheight = self.fear_height end + + local jumpheight = 0 + + if self.jump and self.jump_height >= pathfinding_max_jump then + jumpheight = min(ceil( + self.jump_height / pathfinding_max_jump), pathfinding_max_jump) + + elseif self.stepheight > 0.5 then + jumpheight = 1 + end + + if pathfinder_mod and pathfinder_enable then + self.path.way = pathfinder.find_path(s, p1, self, dtime) + else + self.path.way = minetest.find_path(s, p1, pathfinding_searchdistance, + jumpheight, dropheight, pathfinding_algorithm) + end +--[[ + -- show path using particles + if self.path.way and #self.path.way > 0 then + + print("-- path length:" .. tonumber(#self.path.way)) + + for _,pos in pairs(self.path.way) do + + minetest.add_particle({ + pos = pos, + velocity = {x=0, y=0, z=0}, + acceleration = {x=0, y=0, z=0}, + expirationtime = 1, + size = 4, + collisiondetection = false, + vertical = false, + texture = "heart.png", + }) + end + end +]] + + self.state = "" + + if self.attack then + self:do_attack(self.attack) + end + + -- 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 more than 1 block higher than mob? + if p1.y > (s.y + 1) 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 = 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 + can_dig_drop(s) + + s.y = s.y - sheight + self.object:set_pos({x = s.x, y = s.y + 2, z = s.z}) + + -- is player more than 1 block lower than mob + elseif p1.y < (s.y - 1) then + + -- dig down + s.y = s.y - self.collisionbox[4] - 0.2 + + can_dig_drop(s) + + 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) + } + + -- dig bottom node first incase of door + can_dig_drop(p1) + + p1.y = p1.y + 1 + + can_dig_drop(p1) + end + end + + -- will try again in 2 second + self.path.stuck_timer = pathfinding_stuck_timeout - 2 + + elseif s.y < p1.y and (not self.fly) then + self:do_jump() --add jump to pathfinding + self.path.following = true + else + -- yay i found path + if self.attack then + self:mob_sound(self.sounds.war_cry) + else + self:mob_sound(self.sounds.random) + end + + self:set_velocity(self.walk_velocity) + + -- follow path now that it has it + self.path.following = true + end + end +end + + +-- peaceful player privilege support +local function is_peaceful_player(player) + + -- main setting enabled + if peaceful_player_enabled and player:is_player() then + + local player_name = player:get_player_name() + + -- player priv enabled + if player_name + and minetest.check_player_privs(player_name, "peaceful_player") then + return true + end + end + + return false +end + + +-- general attack function for all mobs +function mob_class:general_attack() + + -- return if already attacking, passive or docile during day + if self.passive + or self.state == "runaway" + or self.state == "attack" + or self:day_docile() then + return + end + + local s = self.object:get_pos() ; if not s then return end + local objs = minetest.get_objects_inside_radius(s, self.view_range) + + -- remove entities we aren't interested in + for n = 1, #objs do + + local ent = objs[n]:get_luaentity() + + -- are we a player? + if is_player(objs[n]) then + + -- if player invisible or mob cannot attack then remove from list + if not damage_enabled + or self.attack_players == false + or (self.owner and self.type ~= "monster") + or is_invisible(self, objs[n]:get_player_name()) + or (self.specific_attack + and not check_for("player", self.specific_attack)) then + objs[n] = nil +--print("- pla", n) + end + + -- are we a creatura mob? + elseif creatura and ent and ent._cmi_is_mob ~= true + and ent.hitbox and ent.stand_node then + + -- monsters attack all creatura mobs, npc and animals will only attack + -- if the animal owner is currently being attacked by creatura mob + if self.name == ent.name + or (self.type ~= "monster" + and self.owner ~= (ent._target and ent._target:get_player_name() or ".")) + or (self.specific_attack + and not check_for(ent.name, self.specific_attack)) then + + objs[n] = nil +--print("-- creatura", ent.name) + end + + -- or are we a mob? + elseif ent and ent._cmi_is_mob then + + -- remove mobs not to attack + if self.name == ent.name + or (not self.attack_animals and ent.type == "animal") + or (not self.attack_monsters and ent.type == "monster") + or (not self.attack_npcs and ent.type == "npc") + or (self.specific_attack + and not check_for(ent.name, self.specific_attack)) then + objs[n] = nil +--print("- mob", n, self.name, ent.name) + end + + -- remove all other entities + else +--print(" -obj", n) + objs[n] = nil + end + end + + local p, sp, dist, min_player + local min_dist = self.view_range + 1 + + -- go through remaining entities and select closest + for _,player in pairs(objs) do + + p = player:get_pos() + sp = s + + dist = get_distance(p, s) + + -- aim higher to make looking up hills more realistic + p.y = p.y + 1 + sp.y = sp.y + 1 + + -- choose closest player to attack that isnt self + if dist ~= 0 + and dist < min_dist + and self:line_of_sight(sp, p, 2) == true + and not is_peaceful_player(player) then + min_dist = dist + min_player = player + end + end + + -- attack closest player or mob + if min_player and random(100) > self.attack_chance then + self:do_attack(min_player) + end +end + + +-- find someone to runaway from +function mob_class:do_runaway_from() + + if not self.runaway_from then + return + end + + local s = self.object:get_pos() ; if not s then return end + local p, sp, dist, pname + local player, obj, min_player, name + local min_dist = self.view_range + 1 + local objs = minetest.get_objects_inside_radius(s, self.view_range) + + -- loop through entities surrounding mob + for n = 1, #objs do + + if is_player(objs[n]) then + + pname = objs[n]:get_player_name() + + if is_invisible(self, pname) + or self.owner == pname then + + name = "" + else + player = objs[n] + name = "player" + end + else + obj = objs[n]:get_luaentity() + + if obj then + player = obj.object + name = obj.name or "" + end + end + + -- find specific mob to runaway from + if name ~= "" and name ~= self.name + and (self.runaway_from and check_for(name, self.runaway_from)) then + + sp = s + p = player and player:get_pos() or s + + -- aim higher to make looking up hills more realistic + p.y = p.y + 1 + sp.y = sp.y + 1 + + dist = get_distance(p, s) + + -- choose closest player/mob 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 + + yaw_to_pos(self, min_player:get_pos(), 3) + + self.state = "runaway" + self.runaway_timer = 3 + self.following = nil + + return + end + + -- check for nodes to runaway from + objs = minetest.find_node_near(s, self.view_range, self.runaway_from, true) + + if objs then + + yaw_to_pos(self, objs, 3) + + 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.state ~= "runaway" then + + local s = self.object:get_pos() ; if not s then return end + local players = minetest.get_connected_players() + + for n = 1, #players do + + if players[n] then + if players[n]:is_player() then + if not is_invisible(self, players[n]:get_player_name()) + and get_distance(players[n]:get_pos(), s) < self.view_range then + self.following = players[n] + break + end + end + end + end + end + + if self.type == "npc" + and self.order == "follow" + and self.state ~= "attack" + 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 or mob is horny + if self.following and is_player(self.following) + and (self:follow_holding(self.following) == false or self.horny) then + self.following = nil + end + + end + + -- follow that thing + if self.following then + + local s = self.object:get_pos() + local p + + if is_player(self.following) then + p = self.following:get_pos() + elseif self.following.object then + p = self.following.object:get_pos() + end + + if p then + + local dist = get_distance(p, s) + + -- dont follow if out of range + if dist > self.view_range then + self.following = nil + else + yaw_to_pos(self, p) + + -- anyone but standing npc's can move along + if dist >= self.reach + and self.order ~= "stand" then + + self:set_velocity(self.walk_velocity) + self.follow_stop = nil + + if self.walk_chance ~= 0 then + self:set_animation("walk") + end + else + self:set_velocity(0) + self:set_animation("stand") + self.follow_stop = true + end + + return + end + end + end + + -- swimmers flop when out of their element, and swim again when back in + if self.fly then + + if not self:attempt_flight_correction() then + + self.state = "flop" + + -- do we have a custom on_flop function? + if self.on_flop then + + if self:on_flop(self) then + return + end + end + + self.object:set_velocity({x = 0, y = -5, z = 0}) + + self:set_animation("stand") + + return + + elseif self.state == "flop" then + self.state = "stand" + end + 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 + + +-- execute current state (stand, walk, run, attacks) +function mob_class:do_states(dtime) + + local yaw = self.object:get_yaw() ; if not yaw then return end + + -- are we standing in something that hurts ? Try to get out + if is_node_dangerous(self, self.standing_in) then + + local s = self.object:get_pos() + local grps = {} + + if self.water_damage > 0 then table.insert(grps, "group:water") end + if self.fire_damage > 0 then table.insert(grps, "group:fire") end + if self.lava_damage > 0 then table.insert(grps, "group:lava") end + + local lp = minetest.find_node_near(s, 1, grps) + + if lp then + + if self.pause_timer <= 0 then + + lp = minetest.find_nodes_in_area_under_air( + {x = s.x - 5, y = s.y , z = s.z - 5}, + {x = s.x + 5, y = s.y + 2, z = s.z + 5}, + {"group:cracky", "group:crumbly", "group:choppy", "group:solid", "group:stone", "group:sand", mobs.node_ice, mobs.node_snow}) + + -- did we find land? + if lp and #lp > 0 then + + -- select position of random block to climb onto + lp = lp[random(#lp)] + + yaw = yaw_to_pos(self, lp) + end + + self.pause_timer = 3 + self.following = nil + + self:set_velocity(self.run_velocity) + self:set_animation("walk") + + return + end + end + end + + if self.state == "stand" and not self.follow_stop then + + if self.randomly_turn and random(4) == 1 then + + local lp + local s = self.object:get_pos() + local objs = minetest.get_objects_inside_radius(s, 3) + + for n = 1, #objs do + + if is_player(objs[n]) then + lp = objs[n]:get_pos() + break + end + end + + -- look at any players nearby, otherwise turn randomly + if lp then + yaw = yaw_to_pos(self, lp) + else + yaw = yaw + random(-0.5, 0.5) + end + + self:set_yaw(yaw, 8) + end + + self:set_velocity(0) + self:set_animation("stand") + + -- mobs ordered to stand stay standing + if self.order ~= "stand" + and self.walk_chance ~= 0 + and self.facing_fence ~= true + and random(100) <= self.walk_chance + and self.at_cliff == false then + + self:set_velocity(self.walk_velocity) + self.state = "walk" + self:set_animation("walk") + end + + elseif self.state == "walk" then + + if self.randomly_turn and random(100) <= 30 then + + yaw = yaw + random(-0.5, 0.5) + + self:set_yaw(yaw, 8) + + -- for flying/swimming mobs randomly move up and down also + if self.fly_in and not self.following then + self:attempt_flight_correction(true) + end + end + + -- stand for great fall in front + if self.facing_fence == true + or self.at_cliff + or random(100) <= self.stand_chance then + + -- don't stand if mob flies and keep_flying set + if (self.fly and not self.keep_flying) or not self.fly then + + self:set_velocity(0) + self.state = "stand" + self:set_animation("stand", true) + end + else + self:set_velocity(self.walk_velocity) + + -- figure out which animation to use while in motion + if self:flight_check() + and self.animation + and self.animation.fly_start + and self.animation.fly_end then + + local on_ground = minetest.registered_nodes[self.standing_on].walkable + local in_water = minetest.registered_nodes[self.standing_in].groups.water + + if on_ground and in_water then + self:set_animation("fly") + elseif on_ground then + self:set_animation("walk") + else + self:set_animation("fly") + end + 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.at_cliff + or self.order == "stand" then + self.runaway_timer = 0 + self:set_velocity(0) + self.state = "stand" + self:set_animation("stand") + else + self:set_velocity(self.run_velocity) + self:set_animation("walk") + end + + -- attack routines (explode, dogfight, shoot, dogshoot) + elseif self.state == "attack" then + + -- get mob and enemy positions and distance between + local s = self.object:get_pos() + local p = self.attack and self.attack:get_pos() + local dist = p and get_distance(p, s) or 500 + + -- stop attacking if player out of range or invisible + if dist > self.view_range + or not self.attack + or not self.attack:get_pos() + or self.attack:get_hp() <= 0 + or (is_player(self.attack) + and is_invisible(self, self.attack:get_player_name())) then + +--print(" ** stop attacking **", self.name, self.health, dist, self.view_range) + + self.attack = nil + self.following = nil + self.v_start = false + self.timer = 0 + self.blinktimer = 0 + self.path.way = nil + self:set_velocity(0) + self.state = "stand" + self:set_animation("stand", true) + + return + end + + if self.attack_type == "explode" then + + yaw_to_pos(self, p) + + local node_break_radius = self.explosion_radius or 1 + local entity_damage_radius = self.explosion_damage_radius + or (node_break_radius * 2) + + -- look a little higher to fix raycast + s.y = s.y + 0.5 ; p.y = p.y + 0.5 + + -- 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(self.sounds.fuse) + +--print("=== explosion timer started", self.explosion_timer) + + -- stop timer if out of reach or direct line of sight + elseif self.allow_fuse_reset + and self.v_start + and (dist > self.reach or not self:line_of_sight(s, p, 2)) then + +--print("=== explosion timer stopped") + + self.v_start = false + self.timer = 0 + self.blinktimer = 0 + self.blinkstatus = false + self.object:set_texture_mod("") + end + + -- walk right up to player unless the timer is active + if self.v_start and (self.stop_to_explode or dist < 1.5) 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.object:set_texture_mod(self.texture_mods) + else + + self.object:set_texture_mod(self.texture_mods .. "^[brighten") + end + + self.blinkstatus = not self.blinkstatus + end + +--print("=== explosion timer", self.timer) + + if self.timer > self.explosion_timer then + + local pos = self.object:get_pos() + + -- dont damage anything if area protected or next to waterpathfinding_max_jump + if minetest.find_node_near(pos, 1, {"group:water"}) + or minetest.is_protected(pos, "") then + + node_break_radius = 1 + end + + remove_mob(self, true) + + mobs:boom(self, pos, entity_damage_radius, node_break_radius) + + return true + end + end + + elseif self.attack_type == "dogfight" + or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2) + 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 = floor(p1.y) + local p2 = p + local p_y = floor(p2.y + 1) + local v = self.object:get_velocity() + + if self:flight_check() 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 + + yaw_to_pos(self, p) + + -- move towards enemy if beyond mob reach + if dist > (self.reach + (self.reach_ext or 0)) then + + -- path finding by rnd (only when enabled in setting and mob) + if self.pathfinding and pathfinding_enable then + self:smart_mobs(s, p, dist, dtime) + end + + -- distance padding to stop spinning mob + local pad = abs(p.x - s.x) + abs(p.z - s.z) + + self.reach_ext = 0 -- extended ready off by default + + if self.at_cliff or pad < 0.2 then + + -- when on top of player extend reach slightly so player can + -- still be attacked. + self.reach_ext = 0.8 + self:set_velocity(0) + self:set_animation("stand") + 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 self.timer > 1 then + + -- no custom attack or custom attack returns true to continue + if not self.custom_attack + or self:custom_attack(self, p) == true then + + self.timer = 0 + self:set_animation("punch") + + 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(self.sounds.attack) + + -- punch player (or what player is attached to) + local attached = self.attack:get_attach() + + if attached then + self.attack = attached + end + + local dgroup = self.damage_group or "fleshy" + + self.attack:punch(self.object, 1.0, { + full_punch_interval = 1.0, + damage_groups = {[dgroup] = self.damage} + }, nil) + end + 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 and + self:dogswitch() == 0) then + + p.y = p.y - .5 + s.y = s.y + .5 + + local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} + + yaw_to_pos(self, p) + + self:set_velocity(0) + + if self.shoot_interval and self.timer > self.shoot_interval + and random(100) <= 60 then + + self.timer = 0 + self:set_animation("shoot") + + -- play shoot attack sound + self:mob_sound(self.sounds.shoot_attack) + + local p = self.object:get_pos() + + p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2 + + if minetest.registered_entities[self.arrow] then + + local obj = minetest.add_entity(p, self.arrow) + local ent = obj:get_luaentity() + local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5 + + -- check for custom override for arrow + if self.arrow_override then + self.arrow_override(ent) + end + + local v = ent.velocity or 1 -- or set to default + + ent.switch = 1 + ent.owner_id = tostring(self.object) -- add unique owner id to arrow + + -- 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) + + obj:set_velocity(vec) + end + end + end + end +end + + +-- falling and fall damage +function mob_class:falling(pos) + + if self.fly or self.disable_falling then + return + end + + -- floating in water (or falling) + local v = self.object:get_velocity() + + -- sanity check + if not v then return end + + local fall_speed = self.fall_speed + + -- in water then use liquid viscosity for float/sink speed + if self.floats == 1 and self.standing_in + and minetest.registered_nodes[self.standing_in].groups.liquid then + + local visc = min( + minetest.registered_nodes[self.standing_in].liquid_viscosity, 7) + 1 + + self.object:set_velocity({x = v.x, y = 0.6, z = v.z}) + fall_speed = -1.2 / visc + else + + -- fall damage onto solid ground + if self.fall_damage == 1 + and self.object:get_velocity().y == 0 then + + local d = (self.old_y or 0) - self.object:get_pos().y + + if d > 5 then + + self.health = self.health - floor(d - 5) + + effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil) + + if self:check_for_death({type = "fall"}) then + return true + end + end + + self.old_y = self.object:get_pos().y + end + end + + -- fall at set speed + self.object:set_acceleration({x = 0, y = fall_speed, z = 0}) +end + + +-- is Took Ranks mod active? +local tr = minetest.get_modpath("toolranks") + +-- deal damage and effects when mob punched +function mob_class:on_punch(hitter, tflp, tool_capabilities, dir, damage) + + -- mob health check + if self.health <= 0 then + return true + end + + -- custom punch function (if false returned, do not continue and return true) + if self.do_punch and self:do_punch(hitter, tflp, tool_capabilities, dir) == false then + return true + end + + -- error checking when mod profiling is enabled + if not tool_capabilities then + minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled") + return true + end + + -- is mob protected + if self.protected then + + -- did player hit mob and if so is it in protected area + if is_player(hitter) then + + local player_name = hitter:get_player_name() + + if player_name ~= self.owner + and minetest.is_protected(self.object:get_pos(), player_name) then + + minetest.chat_send_player(hitter:get_player_name(), + S("Mob has been protected!")) + + return true + end + + -- if protection is on level 2 then dont let arrows harm mobs + elseif self.protected == 2 then + + local ent = hitter and hitter:get_luaentity() + + if ent and ent._is_arrow then + return true -- arrow entity + elseif not ent then + return true -- non entity + end + end + end + + local weapon = hitter:get_wielded_item() + local weapon_def = weapon:get_definition() or {} + + -- 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 + + if use_cmi then + damage = cmi.calculate_damage(self.object, hitter, tflp, tool_capabilities, dir) + else + + 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 + end + + -- check if hit by player item or entity + local hit_item = weapon_def.name + + if not is_player(hitter) then + hit_item = hitter:get_luaentity().name + end + + -- check for tool immunity or special damage + for n = 1, #self.immune_to do + + if self.immune_to[n][1] == hit_item then + + damage = self.immune_to[n][2] or 0 + + break + + -- if "all" then no tools deal damage unless it's specified in list + elseif self.immune_to[n][1] == "all" then + damage = self.immune_to[n][2] or 0 + end + end + +--print("Mob Damage is", damage) + + -- healing + if damage <= -1 then + + self.health = self.health - floor(damage) + + return true + end + + if use_cmi + and cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) then + return true + end + + -- add weapon wear + local punch_interval = tool_capabilities.full_punch_interval or 1.4 + + -- toolrank support + local wear = floor((punch_interval / 75) * 9000) + + if mobs.is_creative(hitter:get_player_name()) then + wear = tr and 1 or 0 + end + + if tr and weapon_def.original_description then + toolranks.new_afteruse(weapon, hitter, nil, {wear = wear}) + else + weapon:add_wear(wear) + end + + hitter:set_wielded_item(weapon) + + -- only play hit sound and show blood effects if damage is 1 or over + if damage >= 1 then + + -- select tool use sound if found, or fallback to default + local snd = weapon_def.sound and weapon_def.sound.use or "mobs_punch" + + minetest.sound_play(snd, {object = self.object, max_hear_distance = 8}, true) + + -- blood_particles + if not disable_blood and self.blood_amount > 0 then + + local pos = self.object:get_pos() + local blood = self.blood_texture + local amount = self.blood_amount + + pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5 + + -- lots of damage = more blood :) + if damage > 10 then + amount = self.blood_amount * 2 + end + + -- do we have a single blood texture or multiple? + if type(self.blood_texture) == "table" then + blood = self.blood_texture[random(#self.blood_texture)] + end + + effect(pos, amount, blood, 1, 2, 1.75, nil, nil, true) + end + + -- add healthy afterglow when hit (can cause lag with larger textures) + if mob_hit_effect then + + self.old_texture_mods = self.texture_mods + + self.object:set_texture_mod(self.texture_mods .. self.damage_texture_modifier) + + minetest.after(0.3, function() + + if self and self.object and self.object:get_pos() then + + self.texture_mods = self.old_texture_mods + self.old_texture_mods = nil + self.object:set_texture_mod(self.texture_mods) + end + end) + end + + -- check for friendly fire (arrows from same mob) + if self.friendly_fire then + self.health = self.health - floor(damage) -- do damage regardless + else + local entity = hitter and hitter:get_luaentity() + + -- check if arrow from same mob, if so then do no damage + if (entity and entity.name ~= self.arrow) or is_player(hitter) then + self.health = self.health - floor(damage) + end + end + + -- exit here if dead, check for tools with fire damage + local hot = tool_capabilities and tool_capabilities.damage_groups + and tool_capabilities.damage_groups.fire + + if self:check_for_death({type = "punch", puncher = hitter, hot = hot}) then + return true + end + end + + -- knock back effect (only on full punch) + if self.knock_back and tflp >= punch_interval then + + local v = self.object:get_velocity() + + -- sanity check + if not v then return true end + + local kb = damage or 1 + local up = 2 + + -- if already in air then dont go up anymore when hit + if v.y > 0 or self.fly then + up = 0 + end + + -- direction error check + dir = dir or {x = 0, y = 0, z = 0} + + -- use tool knockback value or default + kb = tool_capabilities.damage_groups["knockback"] or kb + + self.object:set_velocity({x = dir.x * kb, y = up, z = dir.z * kb}) + + -- turn mob on knockback and play run/walk animation + self:set_yaw((random(0, 360) - 180) / 180 * pi, 12) + + if self.animation and self.animation.injured_end and damage >= 1 then + self:set_animation("injured") + else + self:set_animation("walk") + end + + self.pause_timer = 0.25 + end + + -- if skittish then run away + if self.runaway == true and self.order ~= "stand" then + + local lp = hitter:get_pos() + + yaw_to_pos(self, lp, 3) + + 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 + and self.attack_players == true + and hitter:get_player_name() ~= self.owner + and not is_invisible(self, name) + and self.object ~= hitter then + + -- attack whoever punched mob + self.state = "" + self:do_attack(hitter) + + -- alert others to the attack + local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range) + local obj + + for n = 1, #objs do + + obj = objs[n]:get_luaentity() + + if obj and obj._cmi_is_mob then + + -- only alert members of same mob and assigned helper + if obj.group_attack == true + and obj.state ~= "attack" + and obj.owner ~= name + and (obj.name == self.name or obj.name == self.group_helper) then + obj:do_attack(hitter) + 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 + + return true +end + + +-- helper function to clean mob staticdata +local function clean_staticdata(self) + + local tmp, t = {} + + for _,stat in pairs(self) do + + t = type(stat) + + if t ~= "function" + and t ~= "nil" + and t ~= "userdata" + and _ ~= "object" + and _ ~= "_cmi_components" then + tmp[_] = self[_] + end + end + + return tmp +end + + +-- get entity staticdata +function mob_class:mob_staticdata() + + -- this handles mob count for mobs activated, unloaded, reloaded + if active_limit > 0 and self.active_toggle then + active_mobs = active_mobs + self.active_toggle + self.active_toggle = -self.active_toggle +--print("-- staticdata", active_mobs, active_limit, self.active_toggle) + end + + -- remove mob when out of range unless tamed + if remove_far + and self.remove_ok + and self.type ~= "npc" + and self.state ~= "attack" + and not self.tamed + and self.lifetimer < 20000 then + +--print("REMOVED " .. self.name) + + remove_mob(self, true) + + return minetest.serialize({remove_ok = true, static_save = true}) + end + + self.remove_ok = true + self.attack = nil + self.following = nil + self.state = "stand" + + -- used to rotate older mobs + if self.drawtype and self.drawtype == "side" then + self.rotate = rad(90) + end + + if use_cmi then + self.serialized_cmi_components = cmi.serialize_components(self._cmi_components) + end + + return minetest.serialize(clean_staticdata(self)) +end + + +-- activate mob and reload settings +function mob_class:mob_activate(staticdata, def, dtime) + + -- if dtime == 0 then entity has just been created + -- anything higher means it is respawning (thanks SorceryKid) + if dtime == 0 and active_limit > 0 then + self.active_toggle = 1 + end + + -- remove mob if not tamed and mob total reached + if active_limit > 0 and active_mobs >= active_limit and not self.tamed then + + remove_mob(self) +--print("-- mob limit reached, removing " .. self.name) + return + end + + -- remove monsters in peaceful mode + if self.type == "monster" and peaceful_only then + + remove_mob(self, true) + + return + end + + -- load entity variables from staticdata into self. + local tmp = minetest.deserialize(staticdata) + + if tmp then + + local t + + for _,stat in pairs(tmp) do + + t = type(stat) + + if t ~= "function" and t ~= "nil" and t ~= "userdata" then + self[_] = stat + end + end + end + + -- force current model into mob + self.mesh = def.mesh + self.base_mesh = def.mesh + self.collisionbox = def.collisionbox + self.selectionbox = def.selectionbox + + -- select random texture, set model and size + if not self.base_texture then + + -- compatiblity with old simple mobs textures + if def.textures and type(def.textures[1]) == "string" then + def.textures = {def.textures} + end + + self.base_texture = def.textures and def.textures[random(#def.textures)] + 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 = {x = self.base_size.x * .5, y = self.base_size.y * .5} + + if def.child_texture then + textures = def.child_texture[1] + end + + colbox = { + self.base_colbox[1] * .5, self.base_colbox[2] * .5, + self.base_colbox[3] * .5, self.base_colbox[4] * .5, + self.base_colbox[5] * .5, self.base_colbox[6] * .5} + + selbox = { + self.base_selbox[1] * .5, self.base_selbox[2] * .5, + self.base_selbox[3] * .5, self.base_selbox[4] * .5, + self.base_selbox[5] * .5, self.base_selbox[6] * .5} + end + + if self.health == 0 then + self.health = random(self.hp_min, self.hp_max) + end + + -- pathfinding init + self.path = {} + self.path.way = {} -- path to follow, table of positions + self.path.lastpos = {x = 0, y = 0, z = 0} + self.path.stuck = false + self.path.following = false -- currently following path? + self.path.stuck_timer = 0 -- if stuck for too long search for path + + -- Armor groups (immortal = 1 for custom damage handling) + local armor + if type(self.armor) == "table" then + armor = table_copy(self.armor) + else + armor = {fleshy = self.armor, immortal = 1} + end + self.object:set_armor_groups(armor) + + -- mob defaults + self.old_y = self.object:get_pos().y + self.old_health = self.health + self.sounds.distance = self.sounds.distance or 10 + self.textures = textures + self.mesh = mesh + self.collisionbox = colbox + self.selectionbox = selbox + self.visual_size = vis_size + self.standing_in = "air" + self.standing_on = "air" + + -- check for existing nametag + self.nametag = self.nametag or def.nametag + + -- set anything changed above + self.object:set_properties(self) + self:set_yaw((random(0, 360) - 180) / 180 * pi, 6) + self:update_tag() + self:set_animation("stand") + + -- apply any texture mods + self.object:set_texture_mod(self.texture_mods) + + -- set 5.x flag to remove monsters when map area unloaded + if remove_far and self.type == "monster" and not self.tamed then + self.static_save = false + end + + -- run on_spawn function if found + if self.on_spawn and not self.on_spawn_run and self.on_spawn(self) then + self.on_spawn_run = true -- if true, set flag to run once only + end + + -- run after_activate + if def.after_activate then + def.after_activate(self, staticdata, def, dtime) + end + + if use_cmi then + self._cmi_components = cmi.activate_components(self.serialized_cmi_components) + cmi.notify_activate(self.object, dtime) + end +end + + +-- handle mob lifetimer and expiration +function mob_class:mob_expire(pos, dtime) + + -- when lifetimer expires remove mob (except npc and tamed) + if self.type ~= "npc" + and not self.tamed + and self.state ~= "attack" + and remove_far ~= true + and self.lifetimer < 20000 then + + self.lifetimer = self.lifetimer - dtime + + if self.lifetimer <= 0 then + + -- only despawn away from player + local objs = minetest.get_objects_inside_radius(pos, 15) + + for n = 1, #objs do + + if is_player(objs[n]) then + + self.lifetimer = 20 + + return + end + end + +-- minetest.log("action", S("lifetimer expired, removed @1", self.name)) + + effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0) + + remove_mob(self, true) + + return + end + end +end + + +-- get nodes mob is standing on/in, facing/above +function mob_class:get_nodes() + + local pos = self.object:get_pos() + local yaw = self.object:get_yaw() + + -- child mobs have a lower y_level + local y_level = self.child and self.collisionbox[2] * 0.5 or self.collisionbox[2] + + self.standing_in = node_ok({ + x = pos.x, y = pos.y + y_level + 0.25, z = pos.z}, "air").name + + self.standing_on = node_ok({ + x = pos.x, y = pos.y + y_level - 0.25, z = pos.z}, "air").name + + -- find front position + local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5) + local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5) + + -- nodes in front of mob and front/above + self.looking_at = node_ok({ + x = pos.x + dir_x, y = pos.y + y_level + 0.25, z = pos.z + dir_z}).name + + self.looking_above = node_ok({ + x = pos.x + dir_x, y = pos.y + y_level + 1.25, z = pos.z + dir_z}).name + + -- are we facing a fence or wall + if self.looking_at:find("fence") + or self.looking_at:find("gate") + or self.looking_at:find("wall") then + self.facing_fence = true + else + self.facing_fence = nil + end +--[[ +print("on: " .. self.standing_on + .. ", front: " .. self.looking_at + .. ", front above: " .. self.looking_above + .. ", fence: " .. (self.facing_fence and "yes" or "no") +) +]] +end + + +-- main mob function +function mob_class:on_step(dtime, moveresult) + + if self.state == "die" then return end + + if use_cmi then + cmi.notify_step(self.object, dtime) + end + + local pos = self.object:get_pos() + local yaw = self.object:get_yaw() + + -- early warning check, if no yaw then no entity, skip rest of function + if not yaw then return end + + self.node_timer = (self.node_timer or 0) + dtime + + -- get nodes every 1/4 second + if self.node_timer > node_timer_interval then + + -- get nodes above, below, in front and front-above + self:get_nodes() + + self.node_timer = 0 + + -- check and stop if standing at cliff and fear of heights + self.at_cliff = self:is_at_cliff() + + if self.pause_timer <= 0 and self.at_cliff then + self:set_velocity(0) + end + + -- has mob expired (0.25 instead of dtime since were in a timer) + self:mob_expire(pos, node_timer_interval) + + -- check if mob can jump or is blocked facing fence/gate etc. + self:do_jump() + end + + -- check if falling, flying, floating and return if player died + if self:falling(pos) then + return + end + + -- smooth rotation by ThomasMonroe314 + if self.delay and self.delay > 0 then + + 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 + yaw = yaw + dif / self.delay -- need to add + 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 + self.object:set_yaw(yaw) + end + + -- environmental damage timer (every 1 second) + self.env_damage_timer = self.env_damage_timer + dtime + + if self.env_damage_timer > 1 then + + self.env_damage_timer = 0 + + -- check for environmental damage (water, fire, lava etc.) + if self:do_env_damage() then return end + + -- node replace check (cow eats grass etc.) + self:replace(pos) + end + + -- knockback timer + if self.pause_timer > 0 then + + self.pause_timer = self.pause_timer - dtime + + if self.pause_timer <= 0 and self.order == "stand" then + + self.pause_timer = 0 + self:set_velocity(0) + self:set_animation("stand", true) + end + + return + end + + -- run custom function (defined in mob lua file) - when false skip going any further + if self.do_custom and self:do_custom(dtime, moveresult) == false then + return + end + + -- attack timer + self.timer = self.timer + dtime + + -- never go over 100 + if self.timer > 100 then + self.timer = 1 + end + + -- when attacking call do_states live (return if dead) + if self.state == "attack" then + if self:do_states(dtime) then return end + end + + -- one second timed calls + self.timer1 = (self.timer1 or 0) + dtime + + if self.timer1 >= main_timer_interval then + + -- mob plays random sound at times + if random(100) == 1 then + self:mob_sound(self.sounds.random) + end + + self:general_attack() + + self:breed() + + self:follow_flop() + + -- when not attacking call do_states every second (return if dead) + if self.state ~= "attack" then + if self:do_states(main_timer_interval) then return end + end + + self:do_runaway_from(self) + + self:do_stay_near() + + self.timer1 = 0 + end +end + + +-- default function when mobs are blown up with TNT +function mob_class:on_blast(damage) + +--print("-- blast damage", damage) + + self.object:punch(self.object, 1.0, { + full_punch_interval = 1.0, damage_groups = {fleshy = damage}}, nil) + + -- return no damage, no knockback, no item drops, mob api handles all + return false, false, {} +end + + +mobs.spawning_mobs = {} + +-- register mob entity +function mobs:register_mob(name, def) + + mobs.spawning_mobs[name] = {} + + local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25} + + -- quick fix to stop mobs glitching through nodes if too small + if mob_height_fix and -collisionbox[2] + collisionbox[5] < 1.01 then + collisionbox[5] = collisionbox[2] + 0.99 + end + +minetest.register_entity(name, setmetatable({ + + stepheight = def.stepheight, + name = name, + type = def.type, + attack_type = def.attack_type, + fly = def.fly, + fly_in = def.fly_in, + keep_flying = def.keep_flying, + owner = def.owner, + order = def.order, + on_die = def.on_die, + on_flop = def.on_flop, + do_custom = def.do_custom, + jump_height = def.jump_height, + can_leap = def.can_leap, + drawtype = def.drawtype, -- DEPRECATED, use rotate instead + rotate = rad(def.rotate or 0), -- 0=front 90=side 180=back 270=side2 + glow = def.glow, + lifetimer = def.lifetimer, + hp_min = max(1, (def.hp_min or 5) * difficulty), + hp_max = max(1, (def.hp_max or 10) * difficulty), + collisionbox = collisionbox, --def.collisionbox, + selectionbox = def.selectionbox or collisionbox, --def.collisionbox, + visual = def.visual, + visual_size = def.visual_size, + mesh = def.mesh, + makes_footstep_sound = def.makes_footstep_sound, + view_range = def.view_range, + walk_velocity = def.walk_velocity, + run_velocity = def.run_velocity, + damage = max(0, (def.damage or 0) * difficulty), + damage_group = def.damage_group, + damage_texture_modifier = def.damage_texture_modifier or "^[colorize:#c9900070", + light_damage = def.light_damage, + light_damage_min = def.light_damage_min, + light_damage_max = def.light_damage_max, + water_damage = def.water_damage, + lava_damage = def.lava_damage, + fire_damage = def.fire_damage, + air_damage = def.air_damage, + suffocation = def.suffocation, + fall_damage = def.fall_damage, + fall_speed = def.fall_speed, + drops = def.drops, + armor = def.armor, + on_rightclick = def.on_rightclick, + arrow = def.arrow, + arrow_override = def.arrow_override, + shoot_interval = def.shoot_interval, + sounds = def.sounds, + animation = def.animation, + follow = def.follow, + jump = def.jump, + walk_chance = def.walk_chance, + stand_chance = def.stand_chance, + attack_chance = def.attack_chance, + passive = def.passive, + knock_back = def.knock_back, + blood_amount = def.blood_amount, + blood_texture = def.blood_texture, + shoot_offset = def.shoot_offset, + floats = def.floats, + replace_rate = def.replace_rate, + replace_what = def.replace_what, + replace_with = def.replace_with, + replace_offset = def.replace_offset, + on_replace = def.on_replace, + reach = def.reach, + texture_list = def.textures, + texture_mods = def.texture_mods or "", + child_texture = def.child_texture, + docile_by_day = def.docile_by_day, + fear_height = def.fear_height, + runaway = def.runaway, + pathfinding = def.pathfinding, + immune_to = def.immune_to, + explosion_radius = def.explosion_radius, + explosion_damage_radius = def.explosion_damage_radius, + explosion_timer = def.explosion_timer, + allow_fuse_reset = def.allow_fuse_reset, + stop_to_explode = def.stop_to_explode, + custom_attack = def.custom_attack, + double_melee_attack = def.double_melee_attack, + dogshoot_switch = def.dogshoot_switch, + dogshoot_count_max = def.dogshoot_count_max, + dogshoot_count2_max = def.dogshoot_count2_max or def.dogshoot_count_max, + group_attack = def.group_attack, + group_helper = def.group_helper, + attack_monsters = def.attacks_monsters or def.attack_monsters, + attack_animals = def.attack_animals, + attack_players = def.attack_players, + attack_npcs = def.attack_npcs, + specific_attack = def.specific_attack, + friendly_fire = def.friendly_fire, + runaway_from = def.runaway_from, + owner_loyal = def.owner_loyal, + pushable = def.pushable, + stay_near = def.stay_near, + randomly_turn = def.randomly_turn ~= false, + ignore_invisibility = def.ignore_invisibility, + messages = def.messages, + + on_spawn = def.on_spawn, + on_blast = def.on_blast, -- class redifinition + do_punch = def.do_punch, + on_breed = def.on_breed, + on_grown = def.on_grown, + + on_activate = function(self, staticdata, dtime) + return self:mob_activate(staticdata, def, dtime) + end, + + get_staticdata = function(self) + return self:mob_staticdata(self) + end + +}, mob_class_meta)) + +end -- END mobs:register_mob function + + +-- count how many mobs of one type are inside an area +-- will also return true for second value if player is inside area +local function count_mobs(pos, type) + + local total = 0 + local objs = minetest.get_objects_inside_radius(pos, aoc_range * 2) + local ent + local players + + for n = 1, #objs do + + if not is_player(objs[n]) then + + ent = objs[n]:get_luaentity() + + -- count mob type and add to total also + if ent and ent.name and ent.name == type then + total = total + 1 + end + else + players = true + end + end + + return total, players +end + + +-- do we have enough space to spawn mob? (thanks wuzzy) +local function can_spawn(pos, name) + + local ent = minetest.registered_entities[name] + local width_x = max(1, ceil(ent.collisionbox[4] - ent.collisionbox[1])) + local min_x, max_x + + if width_x % 2 == 0 then + max_x = floor(width_x / 2) + min_x = -(max_x - 1) + else + max_x = floor(width_x / 2) + min_x = -max_x + end + + local width_z = max(1, ceil(ent.collisionbox[6] - ent.collisionbox[3])) + local min_z, max_z + + if width_z % 2 == 0 then + max_z = floor(width_z / 2) + min_z = -(max_z - 1) + else + max_z = floor(width_z / 2) + min_z = -max_z + end + + local max_y = max(0, ceil(ent.collisionbox[5] - ent.collisionbox[2]) - 1) + local pos2 + + for y = 0, max_y do + for x = min_x, max_x do + for z = min_z, max_z do + + pos2 = {x = pos.x + x, y = pos.y + y, z = pos.z + z} + + if minetest.registered_nodes[node_ok(pos2).name].walkable == true then + return nil + end + end + end + end + + -- tweak X/Z spawn pos + if width_x % 2 == 0 then + pos.x = pos.x + 0.5 + end + + if width_z % 2 == 0 then + pos.z = pos.z + 0.5 + end + + return pos +end + +function mobs:can_spawn(pos, name) + return can_spawn(pos, name) +end + + +-- global functions + +function mobs:add_mob(pos, def) + + -- nil check + if not pos or not def then +--print("--- no position or definition given") + return + end + + -- is mob actually registered? + if not mobs.spawning_mobs[def.name] + or not minetest.registered_entities[def.name] then +--print("--- mob doesn't exist", def.name) + return + end + + -- are we over active mob limit + if active_limit > 0 and active_mobs >= active_limit then +--print("--- active mob limit reached", active_mobs, active_limit) + return + end + + -- get total number of this mob in area + local num_mob, is_pla = count_mobs(pos, def.name) + + if not is_pla then +--print("--- no players within active area, will not spawn " .. def.name) + return + end + + local aoc = mobs.spawning_mobs[def.name] and mobs.spawning_mobs[def.name].aoc or 1 + + if def.ignore_count ~= true and num_mob >= aoc then +--print("--- too many " .. def.name .. " in area", num_mob .. "/" .. aoc) + return + end + + local mob = minetest.add_entity(pos, def.name) + +--print("[mobs] Spawned " .. def.name .. " at " .. minetest.pos_to_string(pos)) + + local ent = mob:get_luaentity() + + if not ent then +--print("[mobs] entity not found " .. def.name) + return false + end + + if def.child then + + local textures = ent.base_texture + + -- using specific child texture (if found) + if ent.child_texture then + textures = ent.child_texture[1] + end + + -- and resize to half height (multiplication is faster than division) + mob: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.child = true + end + + if def.owner then + ent.tamed = true + ent.owner = def.owner + end + + if def.nametag then + + -- limit name entered to 64 characters long + if def.nametag:len() > 64 then + def.nametag = def.nametag:sub(1, 64) + end + + ent.nametag = def.nametag + + ent:update_tag() + end + + return ent +end + + +function mobs:spawn_abm_check(pos, node, name) + -- global function to add additional spawn checks + -- return true to stop spawning mob +end + + +function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval, + chance, aoc, min_height, max_height, day_toggle, on_spawn, map_load) + + -- Do mobs spawn at all? + if not mobs_spawn or not mobs.spawning_mobs[name] then +--print ("--- spawning not registered for " .. name) + return + end + + -- chance/spawn number override in minetest.conf for registered mob + local numbers = settings:get(name) + + if numbers then + + numbers = numbers:split(",") + chance = tonumber(numbers[1]) or chance + aoc = tonumber(numbers[2]) or aoc + + if chance == 0 then + + minetest.log("warning", + string.format("[mobs] %s has spawning disabled", name)) + return + end + + minetest.log("action", string.format( + "[mobs] Chance setting for %s changed to %s (total: %s)", + name, chance, aoc)) + end + + mobs.spawning_mobs[name].aoc = aoc + + local spawn_action = function( + pos, node, active_object_count, active_object_count_wider) + + -- use instead of abm's chance setting when using lbm + if map_load and random(max(1, (chance * mob_chance_multiplier))) > 1 then + return + end + + -- use instead of abm's neighbor setting when using lbm + if map_load and not minetest.find_node_near(pos, 1, neighbors) then +--print("--- lbm neighbors not found") + return + end + + -- is mob actually registered? + if not mobs.spawning_mobs[name] + or not minetest.registered_entities[name] then +--print("--- mob doesn't exist", name) + return + end + + -- are we over active mob limit + if active_limit > 0 and active_mobs >= active_limit then +--print("--- active mob limit reached", active_mobs, active_limit) + return + end + + -- additional custom checks for spawning mob + if mobs:spawn_abm_check(pos, node, name) == true then + return + end + + -- do not spawn if too many entities in area + if active_object_count_wider and active_object_count_wider >= max_per_block then +--print("--- too many entities in area", active_object_count_wider) + return + end + + -- get total number of this mob in area + local num_mob, is_pla = count_mobs(pos, name) + + if not is_pla then +--print("--- no players within active area, will not spawn " .. name) + return + end + + if num_mob >= aoc then +--print("--- too many " .. name .. " in area", num_mob .. "/" .. aoc) + return + end + + -- if toggle set to nil then ignore day/night check + if day_toggle ~= nil then + + local tod = (minetest.get_timeofday() or 0) * 24000 + + if tod > 4500 and tod < 19500 then + -- daylight, but mob wants night + if day_toggle == false then +--print("--- mob needs night", name) + return + end + else + -- night time but mob wants day + if day_toggle == true then +--print("--- mob needs day", name) + return + end + end + end + + -- spawn above node + pos.y = pos.y + 1 + + -- are we spawning within height limits? + if pos.y > max_height or pos.y < min_height then +--print("--- height limits not met", name, pos.y) + return + end + + -- are light levels ok? + local light = minetest.get_node_light(pos) + if not light or light > max_light or light < min_light then +--print("--- light limits not met", name, light) + return + end + + -- check if mob can spawn inside protected areas + if (spawn_protected == false + or (spawn_monster_protected == false + and minetest.registered_entities[name].type == "monster")) + and minetest.is_protected(pos, "") then +--print("--- inside protected area", name) + return + end + + -- only spawn a set distance away from player + local objs = minetest.get_objects_inside_radius(pos, mob_nospawn_range) + + for n = 1, #objs do + + if is_player(objs[n]) then +--print("--- player too close", name) + return + end + end + + local ent = minetest.registered_entities[name] + + -- should we check mob area for obstructions ? + if mob_area_spawn ~= true then + + -- do we have enough height clearance to spawn mob? + local height = max(0, ent.collisionbox[5] - ent.collisionbox[2]) + + for n = 0, floor(height) do + + local pos2 = {x = pos.x, y = pos.y + n, z = pos.z} + + if minetest.registered_nodes[node_ok(pos2).name].walkable == true then +--print ("--- inside block", name, node_ok(pos2).name) + return + end + end + else + -- returns position if we have enough space to spawn mob + pos = can_spawn(pos, name) + end + + if pos then + + -- adjust for mob collision box + pos.y = pos.y + (ent.collisionbox[2] * -1) - 0.4 + + local mob = minetest.add_entity(pos, name) + +-- print("[mobs] Spawned " .. name .. " at " +-- .. minetest.pos_to_string(pos) .. " on " +-- .. node.name .. " near " .. neighbors[1]) + + if mob_log_spawn then + + minetest.log("[MOBS] Spawned " .. name .. " at " + .. minetest.pos_to_string(pos)) + end + + if on_spawn and mob then + on_spawn(mob:get_luaentity(), pos) + end + else +--print("--- not enough space to spawn", name) + end + end + + + -- are we registering an abm or lbm? + if map_load == true then + + minetest.register_lbm({ + name = name .. "_spawning", + label = name .. " spawning", + nodenames = nodes, + run_at_every_load = false, + + action = function(pos, node) + spawn_action(pos, node) + end + }) + else + minetest.register_abm({ + label = name .. " spawning", + nodenames = nodes, + neighbors = neighbors, + interval = interval, + chance = max(1, (chance * mob_chance_multiplier)), + catch_up = false, + + action = function(pos, node, active_object_count, active_object_count_wider) + spawn_action(pos, node, active_object_count, active_object_count_wider) + end + }) + end +end + + +-- compatibility with older mob registration +function mobs:register_spawn(name, nodes, max_light, min_light, chance, + active_object_count, max_height, day_toggle) + + mobs:spawn_specific(name, nodes, {"air"}, min_light, max_light, 30, + chance, active_object_count, -31000, max_height, day_toggle) +end + + +-- MarkBu's spawn function (USE this one please) +function mobs:spawn(def) + + mobs:spawn_specific( + def.name, + def.nodes or {"group:soil", "group:stone"}, + def.neighbors or {"air"}, + def.min_light or 0, + def.max_light or 15, + def.interval or 30, + def.chance or 5000, + def.active_object_count or 1, + def.min_height or -31000, + def.max_height or 31000, + def.day_toggle, + def.on_spawn, + def.on_map_load) +end + + +-- register arrow for shoot attack +function mobs:register_arrow(name, def) + + if not name or not def then return end -- errorcheck + + minetest.register_entity(name, { + + physical = def.physical or false, + collide_with_objects = def.collide_with_objects or false, + static_save = 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 = def.collisionbox or {-.1, -.1, -.1, .1, .1, .1}, + timer = 0, + lifetime = def.lifetime or 4.5, + switch = 0, + owner_id = def.owner_id, + rotate = def.rotate, + automatic_face_movement_dir = def.rotate + and (def.rotate - (pi / 180)) or false, + + on_activate = def.on_activate, + + on_punch = def.on_punch or function(self, hitter, tflp, tool_capabilities, dir) + end, + + on_step = def.on_step or function(self, dtime) + + self.timer = self.timer + dtime + + local pos = self.object:get_pos() + + if self.switch == 0 or self.timer > self.lifetime then + + self.object:remove() ; -- print("removed arrow") + + 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(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() ; -- print("hit node") + + 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.0)) do + + if self.hit_player and is_player(player) then + + self:hit_player(player) + + self.object:remove() ; -- print("hit player") + + return + end + + local entity = player:get_luaentity() + + if entity + and self.hit_mob + and entity._cmi_is_mob == true + and tostring(player) ~= self.owner_id + and entity.name ~= self.object:get_luaentity().name then + + self:hit_mob(player) + + self.object:remove() ; -- print("hit mob") + + return + end + + if entity + and self.hit_object + and (not entity._cmi_is_mob) + and tostring(player) ~= self.owner_id + and entity.name ~= self.object:get_luaentity().name then + + self:hit_object(player) + + self.object:remove() ; -- print("hit object") + + return + end + end + end + + self.lastpos = pos + end + }) +end + + +-- compatibility function (deprecated) +function mobs:explosion(pos, radius) + mobs:boom({sounds = {explode = "tnt_explode"}}, pos, radius, radius, "tnt_smoke.png") +end + + +-- no damage to nodes explosion +function mobs:safe_boom(self, pos, radius, texture) + + 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) + + entity_physics(pos, radius) + + effect(pos, 32, texture, radius * 3, radius * 5, radius, 1, 0) +end + + +-- make explosion with protection and tnt mod check +function mobs:boom(self, pos, radius, damage_radius, texture) + + if mobs_griefing + and minetest.get_modpath("tnt") and tnt and tnt.boom + and not minetest.is_protected(pos, "") then + + tnt.boom(pos, { + radius = radius, + damage_radius = damage_radius, + sound = self.sounds and self.sounds.explode, + explode_center = true, + tiles = {(texture or "tnt_smoke.png")} + }) + else + mobs:safe_boom(self, pos, radius, texture) + 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 mobs:register_egg(mob, desc, background, 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 = background + + if addegg == 1 then + invimg = "mobs_chicken_egg.png^(" .. invimg .. + "^[mask:mobs_chicken_egg_overlay.png)" + end + + -- register new spawn egg containing mob information (cannot be stacked) + minetest.register_craftitem(mob .. "_set", { + + description = S("@1 (Tamed)", desc), + inventory_image = invimg, + groups = {spawn_egg = 2, not_in_creative_inventory = 1}, + stack_max = 1, + + on_place = function(itemstack, placer, pointed_thing) + + local pos = pointed_thing.above + + -- does existing on_rightclick function exist? + 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, pointed_thing) + end + + if pos + and not minetest.is_protected(pos, placer:get_player_name()) then + + if not minetest.registered_entities[mob] then + return + end + + pos.y = pos.y + 1 + + local data = itemstack:get_metadata() + local smob = minetest.add_entity(pos, mob, data) + local ent = smob and smob:get_luaentity() + + if not ent then return end -- sanity check + + -- set owner if not a monster + if ent.type ~= "monster" then + ent.owner = placer:get_player_name() + ent.tamed = true + end + + -- since mob is unique we remove egg once spawned + itemstack:take_item() + end + + return itemstack + end + }) + + + -- register old stackable mob egg + minetest.register_craftitem(mob, { + + description = desc, + inventory_image = invimg, + groups = grp, + + on_place = function(itemstack, placer, pointed_thing) + + local pos = pointed_thing.above + + -- does existing on_rightclick function exist? + 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, pointed_thing) + end + + if pos and not minetest.is_protected(pos, placer:get_player_name()) then + + if not minetest.registered_entities[mob] then + return + end + + -- have we reached active mob limit + if active_limit > 0 and active_mobs >= active_limit then + minetest.chat_send_player(placer:get_player_name(), + S("Active Mob Limit Reached!") + .. " (" .. active_mobs + .. " / " .. active_limit .. ")") + return + end + + pos.y = pos.y + 1 + + local smob = minetest.add_entity(pos, mob) + local ent = smob and smob:get_luaentity() + + if not ent then return end -- sanity check + + -- 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 + + -- if not in creative then take item + if not mobs.is_creative(placer:get_player_name()) then + itemstack:take_item() + end + end + + return itemstack + end + }) +end + + +-- force capture a mob if space available in inventory, or drop as spawn egg +function mobs:force_capture(self, clicker) + + -- add special mob egg with all mob information + local new_stack = ItemStack(self.name .. "_set") + + local data_str = minetest.serialize(clean_staticdata(self)) + + new_stack:set_metadata(data_str) + + local inv = clicker:get_inventory() + + if inv:room_for_item("main", new_stack) then + inv:add_item("main", new_stack) + else + minetest.add_item(clicker:get_pos(), new_stack) + end + + self:mob_sound("default_place_node_hard") + + remove_mob(self, true) +end + + +-- capture critter (thanks to blert2112 for idea) +function mobs:capture_mob( + self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith) + + if not self or not is_player(clicker) or not clicker:get_inventory() then + return false + end + + -- get name of clicked mob + local mobname = self.name + + -- if not nil change what will be added to inventory + if replacewith then + mobname = replacewith + end + + local name = clicker:get_player_name() + local tool = clicker:get_wielded_item() + + -- are we using hand, net or lasso to pick up mob? + if tool:get_name() ~= "" + and tool:get_name() ~= "mobs:net" + and tool:get_name() ~= "mobs:lasso" then + return false + end + + -- is mob tamed? + if self.tamed == false and force_take == false then + + minetest.chat_send_player(name, S("Not tamed!")) + + return false + end + + -- cannot pick up if not owner (unless player has protection_bypass priv) + if not minetest.check_player_privs(name, "protection_bypass") + and self.owner ~= name and force_take == false then + + minetest.chat_send_player(name, S("@1 is owner!", self.owner)) + + return false + end + + if clicker:get_inventory():room_for_item("main", mobname) then + + -- was mob clicked with hand, net, or lasso? + local chance = 0 + + if tool:get_name() == "" then + chance = chance_hand + + elseif tool:get_name() == "mobs:net" then + + chance = chance_net + + tool:add_wear(4000) -- 17 uses + + clicker:set_wielded_item(tool) + + elseif tool:get_name() == "mobs:lasso" then + + chance = chance_lasso + + tool:add_wear(650) -- 100 uses + + clicker:set_wielded_item(tool) + + end + + -- calculate chance.. add to inventory if successful? + if chance and chance > 0 and random(100) <= chance then + + -- default mob egg + local new_stack = ItemStack(mobname) + + -- add special mob egg with all mob information + -- unless 'replacewith' contains new item to use + if not replacewith then + + new_stack = ItemStack(mobname .. "_set") + + local data_str = minetest.serialize(clean_staticdata(self)) + + new_stack:set_metadata(data_str) + end + + local inv = clicker:get_inventory() + + if inv:room_for_item("main", new_stack) then + inv:add_item("main", new_stack) + else + minetest.add_item(clicker:get_pos(), new_stack) + end + + self:mob_sound("default_place_node_hard") + + remove_mob(self, true) + + return new_stack + + -- when chance above fails or set to 0, miss! + elseif chance and chance ~= 0 then + + minetest.chat_send_player(name, S("Missed!")) + + self:mob_sound("mobs_swing") + + return false + + -- when chance is nil always return a miss (used for npc walk/follow) + elseif not chance then + return false + end + end + + return true +end + + +-- protect tamed mob with rune item +function mobs:protect(self, clicker) + + local name = clicker:get_player_name() + local tool = clicker:get_wielded_item() + local tool_name = tool:get_name() + + if tool_name ~= "mobs:protector" and tool_name ~= "mobs:protector2" then + return false + end + + if not self.tamed then + minetest.chat_send_player(name, S("Not tamed!")) + return true + end + + if (self.protected and tool_name == "mobs:protector") + or (self.protected == 2 and tool_name == "mobs:protector2") then + minetest.chat_send_player(name, S("Already protected!")) + return true + end + + if not mobs.is_creative(clicker:get_player_name()) then + tool:take_item() -- take 1 protection rune + clicker:set_wielded_item(tool) + end + + -- set protection level + if tool_name == "mobs:protector" then + self.protected = true + else + self.protected = 2 ; self.fire_damage = 0 + end + + local pos = self.object:get_pos() + + pos.y = pos.y + self.collisionbox[2] + 0.5 + + effect(self.object:get_pos(), 25, "mobs_protect_particle.png", 0.5, 4, 2, 15) + + self:mob_sound("mobs_spell") + + return true +end + + +local mob_obj = {} +local mob_sta = {} + +-- feeding, taming and breeding (thanks blert2112) +function mobs:feed_tame(self, clicker, feed_count, breed, tame) + + -- can eat/tame with item in hand + if self.follow and self:follow_holding(clicker) then + + -- if not in creative then take item + if not mobs.is_creative(clicker:get_player_name()) then + + local item = clicker:get_wielded_item() + + item:take_item() + + clicker:set_wielded_item(item) + end + + -- increase health + self.health = self.health + 4 + + if self.health >= self.hp_max then + + self.health = self.hp_max + end + + self.object:set_hp(self.health) + + -- make children grow quicker + if self.child == true then + + -- deduct 10% of the time to adulthood + self.hornytimer = math.floor(self.hornytimer + ( + (CHILD_GROW_TIME - self.hornytimer) * 0.1)) +--print ("====", self.hornytimer) + return true + end + + -- feed and tame + self.food = (self.food or 0) + 1 + self._breed_countdown = feed_count - self.food + + if self.food >= feed_count then + + self.food = 0 + self._breed_countdown = nil + + if breed and self.hornytimer == 0 then + self.horny = true + end + + if tame then + + if self.tamed == false then + minetest.chat_send_player(clicker:get_player_name(), + S("@1 has been tamed!", + self.name:split(":")[2])) + end + + self.tamed = true + self.static_save = true + + if not self.owner or self.owner == "" then + self.owner = clicker:get_player_name() + end + end + + -- make sound when fed so many times + self:mob_sound(self.sounds.random) + end + + self:update_tag() + + return true + end + + local item = clicker:get_wielded_item() + local name = clicker:get_player_name() + + -- if mob has been tamed you can name it with a nametag + if item:get_name() == "mobs:nametag" + and (name == self.owner + or minetest.check_player_privs(name, "protection_bypass")) then + + -- store mob and nametag stack in external variables + mob_obj[name] = self + mob_sta[name] = item + + local tag = self.nametag or "" + local esc = minetest.formspec_escape + + minetest.show_formspec(name, "mobs_nametag", + "size[8,4]" .. + "field[0.5,1;7.5,0;name;" .. + esc(S("Enter name:")) .. + ";" .. tag .. "]" .. + "button_exit[2.5,3.5;3,1;mob_rename;" .. + esc(S("Rename")) .. "]") + + return true + end + + -- if mob follows items and user right clicks while holding sneak it shows info + if self.follow then + + if clicker:get_player_control().sneak then + + if type(self.follow) == "string" then + self.follow = {self.follow} + end + + minetest.chat_send_player(clicker:get_player_name(), + S("@1 follows:\n- @2", + self.name:split(":")[2], + table.concat(self.follow, "\n- "))) + end + end + + return false +end + + +-- inspired by blockmen's nametag mod +minetest.register_on_player_receive_fields(function(player, formname, fields) + + -- right-clicked with nametag and name entered? + if formname == "mobs_nametag" and fields.name and fields.name ~= "" then + + local name = player:get_player_name() + + if not mob_obj[name] or not mob_obj[name].object then + return + end + + -- make sure nametag is being used to name mob + local item = player:get_wielded_item() + + if item:get_name() ~= "mobs:nametag" then + return + end + + -- limit name entered to 64 characters long + if fields.name:len() > 64 then + fields.name = fields.name:sub(1, 64) + end + + -- update nametag + mob_obj[name].nametag = fields.name + + mob_obj[name]:update_tag() + + -- if not in creative then take item + if not mobs.is_creative(name) then + + mob_sta[name]:take_item() + + player:set_wielded_item(mob_sta[name]) + end + + -- reset external variables + mob_obj[name] = nil + mob_sta[name] = nil + end +end) + + +-- compatibility function for old mobs entities to new mobs_redo modpack +function mobs:alias_mob(old_name, new_name) + + -- check old_name entity doesnt already exist + if minetest.registered_entities[old_name] then + return + end + + -- spawn egg + minetest.register_alias(old_name, new_name) + + -- entity + minetest.register_entity(":" .. old_name, { + + physical = false, static_save = false, + + on_activate = function(self, staticdata) + + if minetest.registered_entities[new_name] then + + minetest.add_entity(self.object:get_pos(), new_name, staticdata) + end + + remove_mob(self) + end, + + get_staticdata = function(self) + return minetest.serialize(clean_staticdata(self)) + end + }) +end + + +-- admin command to remove untamed mobs around players +minetest.register_chatcommand("clear_mobs", { + params = "", + description = "Remove untamed mobs from around players.", + privs = {server = true}, + + func = function (name, param) + + local count = 0 + + for _, player in pairs(minetest.get_connected_players()) do + + if player then + + local pos = player:get_pos() + local objs = minetest.get_objects_inside_radius(pos, 28) + + for _, obj in pairs(objs) do + + if obj then + + local ent = obj:get_luaentity() + + -- only remove mobs redo mobs that are not tamed + if ent and ent._cmi_is_mob and ent.tamed ~= true then + + remove_mob(ent, true) + + count = count + 1 + end + end + end + end + end + + minetest.chat_send_player(name, S("@1 mobs removed.", count)) + end +}) diff --git a/mods/mobs/api.txt b/mods/mobs/api.txt new file mode 100644 index 0000000..8d7c645 --- /dev/null +++ b/mods/mobs/api.txt @@ -0,0 +1,938 @@ + +Mobs Redo API +============= + +Welcome to the world of mobs in minetest and hopefully an easy guide to defining +your own mobs and having them appear in your worlds. + + +Quick Note +---------- + +Since the mobs redo api checks for nodes around the mob to function, it relies on a +default node incase anything goes wrong, so in the default game this is default:dirt +but for any custom game please make sure the following line is registered with your +preferred dirt node of choice: + +minetest.register_alias("mapgen_dirt", "mymod:my_dirt_node") + + +Registering Mobs +---------------- + +To register a mob and have it ready for use requires the following function: + + mobs:register_mob(name, definition) + +The 'name' of a mob usually starts with the mod name it's running from followed +by it's own name e.g. + + "mobs_monster:sand_monster" or "mymod:totally_awesome_beast" + +... and the 'definition' is a table which holds all of the settings and +functions needed for the mob to work properly which contains the following: + + 'nametag' contains the name which is shown above mob. + 'type' holds the type of mob that inhabits your world e.g. + "animal" usually docile and walking around. + "monster" attacks player or npc on sight. + "npc" walk around and will defend themselves if hit first, they + kill monsters. + 'hp_min' has the minimum health value the mob can spawn with. + 'hp_max' has the maximum health value the mob can spawn with. + 'armor' holds strength of mob, 100 is normal, lower is more powerful + and needs more hits and better weapons to kill. + 'passive' when false allows animals to defend themselves when hit, + otherwise they amble onwards. + 'walk_velocity' is the speed that your mob can walk around. + 'run_velocity' is the speed your mob can run with, usually when attacking. + 'stand_chance' has a 0-100 chance value your mob will stand from walking. + 'walk_chance' has a 0-100 chance value your mob will walk from standing, + set to 0 for jumping mobs only. + 'randomly_turn' if set to false then mob will not turn to face player or + randomly turn while walking or standing. + 'jump' when true allows your mob to jump updwards. + 'jump_height' holds the height your mob can jump, 0 to disable jumping. + 'can_leap' when true obstacles like fences or pits wont stop a mob + from trying to jump out. + 'stepheight' height of a block that your mob can easily walk up onto, + defaults to 1.1. + 'fly' when true allows your mob to fly around instead of walking. + 'fly_in' holds the node name that the mob flies (or swims) around + in e.g. "air" or "default:water_source". + 'keep_flying' when true mobs like birds no longer stop and stand. + 'stay_near' when set allows mobs the chance to stay around certain nodes. + 'nodes' string or table of nodes to stay nearby e.g. "farming:straw" + 'chance' chance of searching for above node(s), default is 10. + 'runaway' if true causes animals to turn and run away when hit. + 'pushable' when true mobs can be pushed by player or other mobs. + 'view_range' how many nodes in distance the mob can see a player. + 'damage' how many health points the mob does to a player or another + mob when melee attacking. + 'damage_group' group in which damage is dealt, dedaults to "fleshy". + 'damage_texture_modifier' applies texture modifier on hit e.g "^[brighten" + or default when enabled is "^[colorize:#c9900070". + 'knock_back' when true has mobs falling backwards when hit, the greater + the damage the more they move back. + 'fear_height' is how high a cliff or edge has to be before the mob stops + walking, 0 to turn off height fear. + 'fall_speed' has the maximum speed the mob can fall at, default is -10. + 'fall_damage' when true causes falling to inflict damage. + 'water_damage' holds the damage per second infliced to mobs when standing in + water. + 'air_damage' holds damage per second inflicted to mob when standing in air. + 'lava_damage' holds the damage per second inflicted to mobs when standing + in lava. + 'fire_damage' holds the damage per second inflicted to mobs when standing + in fire. + + 'light_damage' holds the damage per second inflicted to mobs when light + level is between the min and max values below + 'light_damage_min' minimum light value when mob is affected (default: 14) + 'light_damage_max' maximum light value when mob is affected (default: 15) + When set to 16 then only natural light will kill mob. + 'suffocation' when > 0 mobs will suffocate inside solid blocks and will be + hurt by the value given every second (0 to disable). + 'floats' when set to 1 mob will float in water, 0 has them sink. + 'follow' mobs follow player when holding any of the items which appear + on this table, the same items can be fed to a mob to tame or + breed e.g. {"farming:wheat", "default:apple", "group:fish"} + + 'reach' is how far the mob can attack player when standing + nearby, default is 3 nodes. + 'docile_by_day' when true has mobs wandering around during daylight + hours and only attacking player at night or when + provoked. + 'attack_chance' 0 to 100 chance the mob will attack (default is 5). + 'attack_monsters' when true mob will attack monsters. + 'attack_animals' when true mob will attack animals. + 'attack_npcs' when true mob will attack npcs within range. + 'attack_players' when true mob will attack players nearby. + 'owner_loyal' when true non-docile tamed mobs attack anything player + punches when nearby. + 'group_attack' when true has same mob type grouping together to attack + offender. + 'group_helper' string containing mob name that attacks alongside + current mob when group attacking. + mob is attacking in groups. + 'attack_type' tells the api what a mob does when attacking the player + or another mob: + 'dogfight' is a melee attack when player is within mob reach. + 'shoot' has mob shoot pre-defined arrows at player when inside + view_range. + 'dogshoot' has melee attack when inside reach and shoot attack + when inside view_range. + 'explode' causes mob to stop and explode when inside reach. + 'explosion_radius' the radius of explosion node destruction, + defaults to 1 + 'explosion_damage_radius' the radius of explosion entity & player damage, + defaults to explosion_radius * 2 + 'explosion_timer' number of seconds before mob explodes while its target + is still inside reach or explosion_damage_radius, + defaults to 3. + 'allow_fuse_reset' Allow 'explode' attack_type to reset fuse and resume + chasing if target leaves the blast radius or line of + sight. Defaults to true. + 'stop_to_explode' When set to true (default), mob must stop and wait for + explosion_timer in order to explode. If false, mob will + continue chasing. + 'arrow' holds the pre-defined arrow object to shoot when + attacking. + 'arrow_override' function that allows tweaking of arrow entity from + inside mob definition (self) passed to function. + 'dogshoot_switch' allows switching between attack types by using timers + (1 for shoot, 2 for dogfight) + 'dogshoot_count_max' contains how many seconds before switching from + dogfight to shoot. + 'dogshoot_count2_max' contains how many seconds before switching from shoot + to dogfight. + 'shoot_interval' has the number of seconds between shots. + 'shoot_offset' holds the y position added as to where the + arrow/fireball appears on mob. + 'specific_attack' has a table of entity names that mob can also attack + e.g. {"player", "mobs_animal:chicken"}. + 'friendly_fire` when set to false, mobs will not be able to harm other + mobs of the same type with friendly fire arrows. + Defaults to true. + 'runaway_from' contains a table with mob names or nodesto run away + from, add "player" to list to runaway from player also. + 'ignore_invisibility' When true mob will still be able to see and attack + player even if invisible (invisibility mod only). + 'blood_amount' contains the number of blood droplets to appear when + mob is hit. + 'blood_texture' has the texture name to use for droplets e.g. + "mobs_blood.png", or table {"blood1.png", "blood2.png"} + 'pathfinding' set to 1 for mobs to use pathfinder feature to locate + player, set to 2 so they can build/break also (only + works with dogfight attack and when 'mobs_griefing' + in minetest.conf is not false). Adding {unbreakable=1} + to node groups stops them being broken by mobs. + 'immune_to' is a table that holds specific damage when being hit by + certain items e.g. + {"default:sword_wood", 0} -- causes no damage. + {"default:gold_lump", -10} -- heals by 10 health points. + {"default:coal_block", 20} -- 20 damage when hit on head with coal blocks. + {"all"} -- stops all weapons causing damage apart from those on list. + + 'makes_footstep_sound' when true you can hear mobs walking. + 'sounds' this is a table with sounds of the mob + 'distance' maximum distance sounds can be heard, default is 10. + 'random' random sound that plays during gameplay. + 'war_cry' what you hear when mob starts to attack player. + 'attack' what you hear when being attacked. + 'shoot_attack' sound played when mob shoots. + 'damage' sound heard when mob is hurt. + 'death' played when mob is killed. + 'jump' played when mob jumps. + 'fuse' sound played when mob explode timer starts. + 'explode' sound played when mob explodes. + + 'drops' table of items that are dropped when mob is killed, fields are: + 'name' name of item to drop. + 'chance' chance of drop, 1 for always, 2 for 1-in-2 chance etc. + 'min' minimum number of items dropped, set to 0 for rare drops. + 'max' maximum number of items dropped. + Note: If weapon has {fire=1} damage group set then cooked items will drop. + Note2: A function can now be passed which can also return drops table, e.g. + drops = function(pos) + -- do something + return { {name = "farming:bread"}, {name = "default:dirt", chance = 2} } + end + + 'visual' holds the look of the mob you wish to create: + 'cube' looks like a normal node + 'sprite' sprite which looks same from all angles. + 'upright_sprite' flat model standing upright. + 'wielditem' how it looks when player holds it in hand. + 'mesh' uses separate object file to define mob. + 'visual_size' has the size of the mob, defaults to {x = 1, y = 1} + 'collisionbox' has the box in which mob can be interacted with the + world e.g. {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5} + 'selectionbox' has the box in which player can interact with mob + 'textures' holds a table list of textures to be used for mob, or you + could use multiple lists inside another table for random + selection e.g. { {"texture1.png"}, {"texture2.png"} } + 'child_texture' holds the texture table for when baby mobs are used. + 'gotten_texture' holds the texture table for when self.gotten value is + true, used for milking cows or shearing sheep. + 'texture_mods' holds a string which overlays a texture on top of the + mob texture e.g. "^saddle.png" + 'mesh' holds the name of the external object used for mob model + e.g. "mobs_cow.b3d" + 'gotten_mesh" holds the name of the external object used for when + self.gotten is true for mobs. + 'rotate' custom model rotation, 0 = front, 90 = side, 180 = back, + 270 = other side. + 'glow' has mob glow without light source, 0 to 15 or nil to disable + 'double_melee_attack' when true has the api choose between 'punch' and + 'punch2' animations. [DEPRECATED] + + 'animation' holds a table containing animation names and settings for use with + mesh models: + 'stand_start' start frame for when mob stands still. + 'stand_end' end frame of stand animation. + 'stand_speed' speed of animation in frames per second. + 'walk_start' when mob is walking around. + 'walk_end' + 'walk_speed' + 'run_start' when a mob runs or attacks. + 'run_end' + 'run_speed' + 'fly_start' when a mob is flying. + 'fly_end' + 'fly_speed' + 'jump_start' when a mob is jumping + 'jump_end' + 'jump_speed' + 'punch_start' when a mob melee attacks. + 'punch_end' + 'punch_speed' + 'punch2_start' alternative melee attack animation. + 'punch2_end' + 'punch2_speed' + 'shoot_start' shooting animation. + 'shoot_end' + 'shoot_speed' + 'injured_start' when hit or damaged > 1 hp (if not set then 'walk' is used) + 'injured_end' + 'injured_speed' + 'die_start' death animation + 'die_end' + 'die_speed' + 'die_loop' when set to false stops the animation looping. + 'die_rotate' if true mob spins during death animation. + + Using '_loop = false' setting will stop any of the above animations from + looping. + + 'speed_normal' is used for animation speed for compatibility with some + older mobs. + + Note: Up to 5 different animations can be used per action e.g. + stand_start, stand_end, stand1_start, stand1_end .. up to stand4_start + + +Node Replacement +---------------- + +Mobs can look around for specific nodes as they walk and replace them to mimic +eating. + + 'replace_what' group of items to replace e.g. + {"farming:wheat_8", "farming:carrot_8"} + or you can use the specific options of what, with and + y offset by using this instead: + { + {"group:grass", "air", 0}, + {"default:dirt_with_grass", "default:dirt", -1} + } + 'replace_with' replace with what e.g. "air" or in chickens case "mobs:egg" + 'replace_rate' how random should the replace rate be (typically 10) + 'replace_offset' +/- value to check specific node to replace + + 'on_replace(self, pos, oldnode, newnode)' is called when mob is about to + replace a node. + 'self' ObjectRef of mob + 'pos' Position of node to replace + 'oldnode' Current node + 'newnode' What the node will become after replacing + + If false is returned, the mob will not replace the node. + + By default, replacing sets self.gotten to true and resets the object + properties. (DEPRECATED, use on_replace to make changes). + + +Custom Definition Functions +--------------------------- + +Along with the above mob registry settings we can also use custom functions to +enhance mob functionality and have them do many interesting things: + + 'on_rightclick' its same as in minetest.register_entity() + 'on_blast' is called when an explosion happens near mob when using TNT + functions, parameters are (damage) and returns + (do_damage, do_knockback, drops) + 'on_spawn' is a custom function that runs on mob spawn with 'self' as + variable, return true at end of function to run only once. + 'after_activate' is a custom function that runs once mob has been activated + with these paramaters (self, staticdata, def, dtime) + 'on_breed' called when two similar mobs breed, paramaters are + (parent1, parent2) objects, return false to stop child from + being resized and owner/tamed flags and child textures being + applied. Function itself must spawn new child mob. + 'on_grown' is called when a child mob has grown up, only paramater is + (self). + 'do_punch' called when mob is punched with paramaters (self, hitter, + time_from_last_punch, tool_capabilities, direction), return + false to stop punch damage and knockback from taking place. + 'custom_attack' when set this function is called instead of the normal mob + melee attack, parameters are (self, to_attack) and if true + is returned normal attack function continued. + 'on_die' a function that is called when mob is killed (self, pos), also + has access to self.cause_of_death table. + 'on_flop' function called when flying or swimmimng mob is no longer in + air/water, (self) paramater and return true to skip the built + in api flop feature. + 'do_custom' a custom function that is called every tick while mob is + active and which has access to all of the self.* variables + e.g. (self.health for health or self.standing_in for node + status), return with 'false' to skip remainder of mob API. + + +Internal Variables +------------------ + +The mob api also has some preset variables and functions that it will remember +for each mob. + + 'self.health' contains current health of mob (cannot exceed + self.hp_max) + 'self.texture_list' contains list of all mob textures + 'self.child_texture' contains mob child texture when growing up + 'self.base_texture' contains current skin texture which was randomly + selected from textures list + 'self.texture_mods' contains a list of textures to overlay above the mobs + base texture (used for horse saddle) + 'self.gotten' this is used for obtaining milk from cow and wool from + sheep + 'self.horny' when animal fed enough it is set to true and animal can + breed with same animal + 'self.hornytimer' background timer that controls breeding functions and + mob childhood timings + 'self.child' used for when breeding animals have child, will use + child_texture and be half size + 'self.owner' string used to set owner of npc mobs, typically used for + dogs + 'self.order' set to "follow" or "stand" so that npc will follow owner + or stand it's ground + 'self.nametag' contains the name of the mob which it can show above + 'self.pause_timer' used to stop mob thinking when punched so that knockback + can take effect. + 'self.disable_falling' currently used on spider mob when climbing walls, stops + the mob from experiencing gravity when true. + 'self.state' Current mob state. + "stand": no movement (except turning around) + "walk": walk or move around aimlessly + "attack": chase and attack enemy + "runaway": flee from target + "flop": bounce around aimlessly + (for swimming mobs that have stranded) + "die": during death + 'self.standing_on' Node name mob is standing on. + 'self.standing_in' Node name mob is standing inside. + 'self.looking_at' Node name in front of mob. + 'self.looking_above'Node name in front/above mob. + 'self.facing_fence' True if mob facing node containing "wall", "fence", "gate" + in it's name. + + +Adding Mobs in World +-------------------- + + mobs:add_mob(pos, { + name = "mobs_animal:chicken", + child = true, + owner = "singleplayer", + nametag = "Bessy", + ignore_count = true -- ignores mob count per map area + }) + +Returns false if mob could not be added, returns mob object if spawned ok. + + +Removing Mob from World +----------------------- + + mobs:remove(self, decrease) + +Removes mob 'self' from the world and if 'decrease' is true then the mob counter +will also be decreased by one. + + +Spawning Mobs in World +---------------------- + + mobs:spawn({ + name = "mobs_monster:tree_monster", + nodes = {"group:leaves"}, + max_light = 7, + }) + +Spawn functions require the following settings, some of which already have a +default setting and can be omitted: + + 'name' is the name of the animal/monster + 'nodes' is a list of nodenames on that the animal/monster can + spawn on top of (defaults to {"group:dirt", "group:stone"} + 'neighbors' is a list of nodenames on that the animal/monster will + spawn beside (default is {"air"}) + 'interval' is same as in register_abm() (default is 30) + 'chance' is same as in register_abm() (default is 5000) + 'min_light' is the minimum light level (default is 0) + 'max_light' is the maximum light (default is 15) + 'min_height' is the minimum height a mob can spawn (default: -31000) + 'max_height' is the maximum height a mob can spawn (default is 31000) + 'active_object_count' number of this type of mob to spawn at one time inside + map area (default is 1) + 'day_toggle' true for day spawning, false for night or nil for + anytime + 'on_spawn' is a custom function which runs after mob has spawned + and gives self and pos values. + 'on_map_load' when true mobs will have a chance of spawning only + when new areas of map are loaded, interval will not be + used. + +The older spawn functions are still active and working but have no defaults like +the mobs:spawn, so it is recommended to use the above instead. + +mobs:register_spawn(name, nodes, max_light, min_light, chance, + active_object_count, max_height, day_toggle) + +mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval, + chance, active_object_count, min_height, max_height, day_toggle, on_spawn) + +A simpler way to handle mob spawns has been added with the mobs:spawn(def) +command which uses above names to make settings clearer: + + +For each mob that spawns with this function is a field in mobs.spawning_mobs. +It tells if the mob should spawn or not. Default is true. So other mods can +only use the API of this mod by disabling the spawning of the default mobs in +this mod. + + +mobs:spawn_abm_check(pos, node, name) + +This global function can be changed to contain additional checks for mobs to +spawn e.g. mobs that spawn only in specific areas and the like. By returning +true the mob will not spawn. + + 'pos' holds the position of the spawning mob + 'node' contains the node the mob is spawning on top of + 'name' is the name of the animal/monster + + +Particle Effects +---------------- + +mobs:effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, fall) + +This function provides a quick way to spawn particles as an effect. + + 'pos' center position of particle effect. + 'amount' how many particles. + 'texture' texture filename to use for effect. + 'min_size' smallest particle size. + 'max_size' largest particle size. + 'radius' how far particles spread outward from center. + 'gravity' gravity applied to particles once they spawn. + 'glow' number between 1 and 15 for glowing particles. + 'fall' when true particles fall, false has them rising, nil has them scatter. + + +Making Arrows +------------- + +mobs:register_arrow(name, definition) + +This function registers a arrow for mobs with the attack type shoot. + + 'name' is the name of the arrow + 'definition' is a table with the following values: + 'visual' same is in minetest.register_entity() + 'visual_size' same is in minetest.register_entity() + 'textures' same is in minetest.register_entity() + 'physical' same is in minetest.register_entity() [default: false] + 'collide_with_objects' same as above + 'velocity' the velocity of the arrow + 'drop' if set to true any arrows hitting a node will drop as item + 'hit_player' a function that is called when the arrow hits a player; + this function should hurt the player, the parameters are + (self, player) + 'hit_mob' a function that is called when the arrow hits a mob; + this function should hurt the mob, the parameters are + (self, player) + 'hit_object' a function that is called when the arrow hits an object; + this function parameters are (self, player) + 'hit_node' a function that is called when the arrow hits a node, the + parameters are (self, pos, node) + 'tail' when set to 1 adds a trail or tail to mob arrows + 'tail_texture' texture string used for above effect + 'tail_size' has size for above texture (defaults to between 5 and 10) + 'expire' contains float value for how long tail appears for + (defaults to 0.25) + 'glow' has value for how brightly tail glows 1 to 10 (default is + 0 for no glow) + 'rotate' integer value in degrees to rotate arrow + 'on_step' is a custom function when arrow is active, nil for + default. + 'on_punch' is a custom function when arrow is punched, nil by default + 'collisionbox' is hitbox table for arrow, {-.1,-.1,-.1,.1,.1,.1} by default. + 'lifetime' contains float value for how many seconds arrow exists in + world before being removed (default is 4.5 seconds). + + +Spawn Eggs +---------- + +mobs:register_egg(name, description, background, addegg, no_creative) + +This function registers a spawn egg which can be used to properly spawn in a mob. +Animals are spawned as tamed unless sneak/shift is held while spawning. + + 'name' this is the name of your new mob to spawn e.g. "mob:sheep" + 'description' the name of the new egg you are creating e.g. "Spawn Sheep" + 'background' the texture displayed for the egg in inventory + 'addegg' would you like an egg image in front of your texture (1 = yes, + 0 = no) + 'no_creative' when set to true this stops spawn egg appearing in creative + mode for destructive mobs like Dungeon Masters. + + +Explosion Function +------------------ + +mobs:explosion(pos, radius) -- DEPRECATED!!! use mobs:boom() instead + +mobs:boom(self, pos, radius, damage_radius, texture) + 'self' mob entity + 'pos' centre position of explosion + 'radius' radius of explosion (typically set to 3) + 'damage_radius' radius of damage around explosion + 'texture' particle texture during explosion, defaults to "tnt_smoke.png" + +This function generates an explosion which removes nodes in a specific radius +and damages any entity caught inside the blast radius. Protection will limit +node destruction but not entity damage. + + +Capturing Mobs +-------------- + +mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, + force_take, replacewith) + +This function is generally called inside the on_rightclick section of the mob +api code, it provides a chance of capturing the mob by hand, using the net or +lasso items, and can also have the player take the mob by force if tamed and +replace with another item entirely. + + 'self' mob information + 'clicker' player information + 'chance_hand' chance of capturing mob by hand (1 to 100) 0 to disable + 'chance_net' chance of capturing mob using net (1 to 100) 0 to disable + 'chance_lasso' chance of capturing mob using magic lasso (1 to 100) 0 to + disable + 'force_take' take mob by force, even if tamed (true or false) + 'replacewith' once captured replace mob with this item instead (overrides + new mob eggs with saved information) + +mobs:force_capture(self, clicker) + +Same as above but does no checks, it simply captures any and all mobs and places +inside a spawn egg containing all of the mob information. + + +Feeding and Taming/Breeding +--------------------------- + +mobs:feed_tame(self, clicker, feed_count, breed, tame) + +This function allows the mob to be fed the item inside self.follow be it apple, +wheat or whatever a set number of times and be tamed or bred as a result. +Will return true when mob is fed with item it likes. + + 'self' mob information + 'clicker' player information + 'feed_count' number of times mob must be fed to tame or breed + 'breed' true or false stating if mob can be bred and a child created + afterwards + 'tame' true or false stating if mob can be tamed so player can pick + them up + + +Protecting Mobs +--------------- + +mobs:protect(self, clicker) + +This function can be used to right-click any tamed mob with mobs:protector item, +this will protect the mob from harm inside of a protected area from other +players. Will return true when mob right-clicked with mobs:protector item. + + 'self' mob information + 'clicker' player information + + +Riding Mobs +----------- + +Mobs can now be ridden by players and the following shows its functions and +usage: + + +mobs:attach(self, player) + +This function attaches a player to the mob so it can be ridden. + + 'self' mob information + 'player' player information + + +mobs:detach(player, offset) + +This function will detach the player currently riding a mob to an offset +position. + + 'player' player information + 'offset' position table containing offset values + + +mobs:drive(self, move_animation, stand_animation, can_fly, dtime) + +This function allows an attached player to move the mob around and animate it at +same time. + + 'self' mob information + 'move_animation' string containing movement animation e.g. "walk" + 'stand_animation' string containing standing animation e.g. "stand" + 'can_fly' if true then jump and sneak controls will allow mob to fly + up and down + 'dtime' tick time used inside drive function + + +mobs:fly(self, dtime, speed, can_shoot, arrow_entity, move_animation, stand_animation) + +This function allows an attached player to fly the mob around using directional +controls. + + 'self' mob information + 'dtime' tick time used inside fly function + 'speed' speed of flight + 'can_shoot' true if mob can fire arrow (sneak and left mouse button + fires) + 'arrow_entity' name of arrow entity used for firing + 'move_animation' string containing name of pre-defined animation e.g. "walk" + or "fly" etc. + 'stand_animation' string containing name of pre-defined animation e.g. + "stand" or "blink" etc. + +Note: animation names above are from the pre-defined animation lists inside mob +registry without extensions. + + +mobs:set_animation(self, name) + +This function sets the current animation for mob, defaulting to "stand" if not +found. + + 'self' mob information + 'name' name of animation + + +Certain variables need to be set before using the above functions: + + 'self.v2' toggle switch used to define below values for the + first time + 'self.max_speed_forward' max speed mob can move forward + 'self.max_speed_reverse' max speed mob can move backwards + 'self.accel' acceleration speed + 'self.terrain_type' integer containing terrain mob can walk on + (1 = water, 2 or 3 = land) + 'self.driver_attach_at' position offset for attaching player to mob + 'self.driver_eye_offset' position offset for attached player view + 'self.driver_scale' sets driver scale for mobs larger than {x=1, y=1} + + +mobs:line_of_sight(self, pos1, pos2, stepsize) [DEPRECATED] + +This function is for use within the mobs definition for special use cases and +returns true if a mob can see the player or victim. + +...'self' mob information + 'pos1' position of mob + 'pos2' position of vistim or player + 'stepsize' usually set to 1 + +Use this instead: + + mob_class:line_of_sight(pos1, pos2, stepsize) + + +mobs:can_spawn(pos, name) + +This function checks the surrounding area at [pos] to see if there is enough empty +space to spawn mob [name], if so then a new position is returned for use, +otherwise nil is returned. + + +mobs:is_node_dangerous(mob_object, nodename) + +This function returns true if the node name given is harmful to the mob (mob_object), +it is mainly used when a mob is near a node it has to avoid. + + +Looting Level +------------- + +If a tool is used with 'looting_level' defined under tool_capabilities then mobs can drop +extra items per level up to a maximum of 3 levels. 'looting_level' can also be read from +the tools own meta to override the default. + + +External Settings for "minetest.conf" +------------------------------------ + + 'mob_log_spawn' When True will log spawning position of mobs. + 'mob_node_timer_interval' How often mobs get nodes around them (0.25 is default) + for every 1/4 second. + 'mob_main_timer_interval' How often mobs run main functions (1.0 is default) for + every one second. + 'enable_damage' if true monsters will attack players (default is true) + 'only_peaceful_mobs' if true only animals will spawn in game (default is + false) + 'mobs_attack_creatura' When True mobs redo mobs will attack Creatura mod mobs. + 'mobs_disable_blood' if false blood effects appear when mob is hit (default + is false) + 'mob_hit_effect' False by default, when True and mobs are hit then + damage_texture_modifier is used to highlight mob. + 'mobs_spawn_protected' if set to false then mobs will not spawn in protected + areas (default is true) + 'mobs_spawn_monster_protected' if set to false then monsters will not spawn in + protected areas (default is true) + 'remove_far_mobs' if true then untamed mobs that are outside players + visual range will be removed (default is true) + 'mobname' can change specific mob chance rate (0 to disable) and + spawn number e.g. mobs_animal:cow = 1000,5 + 'mob_difficulty' sets difficulty level (health and hit damage + multiplied by this number), defaults to 1.0. + 'mob_chance_multiplier' multiplies chance of all mobs spawning and can be set + to 0.5 to have mobs spawn more or 2.0 to spawn less. + e.g. 1 in 7000 * 0.5 = 1 in 3500 so better odds of + spawning. + 'mobs_spawn' if false then mobs no longer spawn without spawner or + spawn egg. + 'mobs_drop_items' when false mobs no longer drop items when they die. + 'mobs_griefing' when false mobs cannot break blocks when using either + pathfinding level 2, replace functions or mobs:boom + function. + 'mob_nospawn_range' Minimum range a mob can spawn near player (def: 12) + 'mob_active_limit' Number of active mobs in game, 0 for unlimited + 'mob_area_spawn' When true will check surrounding area the size of the + mob for obstructions before spawning, otherwise it + defaults to checking the height of the mob only. + 'mob_smooth_rotate' Enables smooth rotation when mobs turn by default. + 'mob_height_fix' Enabled by default, increases smaller mob heights so they wont + glitch through certain nodes. + 'mob_pathfinding_enable' Enable pathfinding. + 'mob_pathfinder_enable' Use pathfinder mod if available. + 'mob_pathfinding_stuck_timeout' How long before stuck mobs start searching. (default 3.0) + 'mob_pathfinding_stuck_path_timeout' How long will mob follow path before giving up. (default 5.0) + 'mob_pathfinding_algorithm' Which pathfinding algorithm to use Dijkstra (default), A*_noprefetch (AStar_noprefetch) or A* (AStar) + (A* names differ cause Minetest doesn´t allow "*" in settings) + 'mob_pathfinding_searchdistance' max search distance from search positions (default 16) + 'mob_pathfinding_max_jump' max jump height for pathfinding (default 4) + 'mob_pathfinding_max_drop' max drop height for pathfinding (default 6) + +Players can override the spawn chance for each mob registered by adding a line +to their minetest.conf file with a new value, the lower the value the more each +mob will spawn e.g. + +mobs_animal:sheep 11000 +mobs_monster:sand_monster 100 + +...you can also change how many of a certain mob appear in an active mapblock by +adding a comma and then a new value e.g. + +mobs_animal:cow = 8000,4 <-- 4 cows per mapblock at 8000 spawn chance +mobs_monster:dirt_monster = ,20 <-- 20 dirt monsters per mapblock + + +Rideable Horse Example Mob +-------------------------- + +mobs:register_mob("mob_horse:horse", { + type = "animal", + visual = "mesh", + visual_size = {x = 1.20, y = 1.20}, + mesh = "mobs_horse.x", + collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.25, 0.4}, + animation = { + speed_normal = 15, + speed_run = 30, + stand_start = 25, + stand_end = 75, + walk_start = 75, + walk_end = 100, + run_start = 75, + run_end = 100, + }, + textures = { + {"mobs_horse.png"}, + {"mobs_horsepeg.png"}, + {"mobs_horseara.png"} + }, + fear_height = 3, + runaway = true, + fly = false, + walk_chance = 60, + view_range = 5, + follow = {"farming:wheat"}, + passive = true, + hp_min = 12, + hp_max = 16, + armor = 200, + lava_damage = 5, + fall_damage = 5, + water_damage = 1, + makes_footstep_sound = true, + drops = { + {name = "mobs:meat_raw", chance = 1, min = 2, max = 3} + }, + sounds = { + random = "horse_neigh.ogg", + damage = "horse_whinney.ogg", + }, + + do_custom = function(self, dtime) + + -- set needed values if not already present + if not self.v2 then + self.v2 = 0 + self.max_speed_forward = 6 + self.max_speed_reverse = 2 + self.accel = 6 + self.terrain_type = 3 + self.driver_attach_at = {x = 0, y = 20, z = -2} + self.driver_eye_offset = {x = 0, y = 3, z = 0} + self.driver_scale = {x = 1, y = 1} + end + + -- if driver present allow control of horse + if self.driver then + + mobs.drive(self, "walk", "stand", false, dtime) + + return false -- skip rest of mob functions + end + + return true + end, + + on_die = function(self, pos) + + -- drop saddle when horse is killed while riding + -- also detach from horse properly + if self.driver then + minetest.add_item(pos, "mobs:saddle") + mobs.detach(self.driver, {x = 1, y = 0, z = 1}) + end + + end, + + on_rightclick = function(self, clicker) + + -- make sure player is clicking + if not clicker or not clicker:is_player() then + return + end + + -- feed, tame or heal horse + if mobs:feed_tame(self, clicker, 10, true, true) then + return + end + + -- make sure tamed horse is being clicked by owner only + if self.tamed and self.owner == clicker:get_player_name() then + + local inv = clicker:get_inventory() + + -- detatch player already riding horse + if self.driver and clicker == self.driver then + + mobs.detach(clicker, {x = 1, y = 0, z = 1}) + + -- add saddle back to inventory + if inv:room_for_item("main", "mobs:saddle") then + inv:add_item("main", "mobs:saddle") + else + minetest.add_item(clicker.get_pos(), "mobs:saddle") + end + + -- attach player to horse + elseif not self.driver + and clicker:get_wielded_item():get_name() == "mobs:saddle" then + + self.object:set_properties({stepheight = 1.1}) + mobs.attach(self, clicker) + + -- take saddle from inventory + inv:remove_item("main", "mobs:saddle") + end + end + + -- used to capture horse with magic lasso + mobs:capture_mob(self, clicker, 0, 0, 80, false, nil) + end +}) diff --git a/mods/mobs/crafts.lua b/mods/mobs/crafts.lua new file mode 100644 index 0000000..b00a245 --- /dev/null +++ b/mods/mobs/crafts.lua @@ -0,0 +1,427 @@ + +local S = mobs.intllib +local mc2 = minetest.get_modpath("mcl_core") + +-- recipe items +local items = { + paper = (mc2 and "mcl_core:paper") or "default:paper", + dye_black = (mc2 and "mcl_dye:black") or "dye:black", + string = (mc2 and "mcl_mobitems:string") or "farming:string", + stick = (mc2 and "mcl_core:stick") or "default:stick", + diamond = (mc2 and "mcl_core:diamond") or "default:diamond", + steel_ingot = (mc2 and "mcl_core:iron_ingot") or "default:steel_ingot", + gold_block = (mc2 and "mcl_core:goldblock") or "default:goldblock", + diamond_block = (mc2 and "mcl_core:diamondblock") or "default:diamondblock", + stone = (mc2 and "mcl_core:stone") or "default:stone", + mese_crystal = (mc2 and "mcl_core:gold_ingot") or "default:mese_crystal", + wood = (mc2 and "mcl_core:wood") or "default:wood", + fence_wood = (mc2 and "group:fence_wood") or "default:fence_wood", + meat_raw = (mc2 and "mcl_mobitems:beef") or "group:food_meat_raw", + meat_cooked = (mc2 and "mcl_mobitems:cooked_beef") or "group:food_meat", +} + +-- name tag +minetest.register_craftitem("mobs:nametag", { + description = S("Name Tag"), + inventory_image = "mobs_nametag.png", + groups = {flammable = 2, nametag = 1} +}) + +if minetest.get_modpath("dye") and minetest.get_modpath("farming") then + minetest.register_craft({ + output = "mobs:nametag", + recipe = { + { items.paper, items.dye_black, items.string } + } + }) +end + +-- leather +minetest.register_craftitem("mobs:leather", { + description = S("Leather"), + inventory_image = "mobs_leather.png", + groups = {flammable = 2, leather = 1} +}) + +-- raw meat +minetest.register_craftitem("mobs:meat_raw", { + description = S("Raw Meat"), + inventory_image = "mobs_meat_raw.png", + on_use = minetest.item_eat(3), + groups = {food_meat_raw = 1, flammable = 2} +}) + +-- cooked meat +minetest.register_craftitem("mobs:meat", { + description = S("Meat"), + inventory_image = "mobs_meat.png", + on_use = minetest.item_eat(8), + groups = {food_meat = 1, flammable = 2} +}) + +minetest.register_craft({ + type = "cooking", + output = "mobs:meat", + recipe = "mobs:meat_raw", + cooktime = 5 +}) + +-- lasso +minetest.register_tool("mobs:lasso", { + description = S("Lasso (right-click animal to put in inventory)"), + inventory_image = "mobs_magic_lasso.png", + groups = {flammable = 2} +}) + +if minetest.get_modpath("farming") then + + minetest.register_craft({ + output = "mobs:lasso", + recipe = { + { items.string, "", items.string}, + { "", items.diamond, "" }, + { items.string, "", items.string } + } + }) +end + +minetest.register_alias("mobs:magic_lasso", "mobs:lasso") + +-- net +minetest.register_tool("mobs:net", { + description = S("Net (right-click animal to put in inventory)"), + inventory_image = "mobs_net.png", + groups = {flammable = 2} +}) + +if minetest.get_modpath("farming") then + + minetest.register_craft({ + output = "mobs:net", + recipe = { + { items.stick, "", items.stick }, + { items.stick, "", items.stick }, + { items.string, items.stick, items.string } + } + }) +end + +-- shears (right click to shear animal) +minetest.register_tool("mobs:shears", { + description = S("Steel Shears (right-click to shear)"), + inventory_image = "mobs_shears.png", + groups = {flammable = 2} +}) + +minetest.register_craft({ + output = "mobs:shears", + recipe = { + { "", items.steel_ingot, "" }, + { "", items.stick, items.steel_ingot } + } +}) + +-- protection rune +minetest.register_craftitem("mobs:protector", { + description = S("Mob Protection Rune"), + inventory_image = "mobs_protector.png", + groups = {flammable = 2} +}) + +minetest.register_craft({ + output = "mobs:protector", + recipe = { + { items.stone, items.stone, items.stone }, + { items.stone, items.gold_block, items.stone }, + { items.stone, items.stone, items.stone } + } +}) + +-- level 2 protection rune +minetest.register_craftitem("mobs:protector2", { + description = S("Mob Protection Rune (Level 2)"), + inventory_image = "mobs_protector2.png", + groups = {flammable = 2} +}) + +minetest.register_craft({ + output = "mobs:protector2", + recipe = { + { "mobs:protector", items.mese_crystal, "mobs:protector" }, + { items.mese_crystal, items.diamond_block, items.mese_crystal }, + { "mobs:protector", items.mese_crystal, "mobs:protector" } + } +}) + +-- saddle +minetest.register_craftitem("mobs:saddle", { + description = S("Saddle"), + inventory_image = "mobs_saddle.png", + groups = {flammable = 2, saddle = 1} +}) + +minetest.register_craft({ + output = "mobs:saddle", + recipe = { + {"mobs:leather", "mobs:leather", "mobs:leather"}, + {"mobs:leather", items.steel_ingot, "mobs:leather"}, + {"mobs:leather", items.steel_ingot, "mobs:leather"} + } +}) + + +-- make sure we can register fences +local mod_def = minetest.get_modpath("default") + +if mod_def and default.register_fence then + +-- mob fence (looks like normal fence but collision is 2 high) +default.register_fence("mobs:fence_wood", { + description = S("Mob Fence"), + texture = "default_wood.png", + material = "default:fence_wood", + groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2}, + sounds = mod_def and default.node_sound_wood_defaults(), + collision_box = { + type = "fixed", + fixed = { + {-0.5, -0.5, -0.5, 0.5, 1.9, 0.5}, + } + } +}) +end + +-- mob fence top (has enlarged collisionbox to stop mobs getting over) +minetest.register_node("mobs:fence_top", { + description = S("Mob Fence Top"), + drawtype = "nodebox", + tiles = {"default_wood.png"}, + paramtype = "light", + is_ground_content = false, + groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2}, + sounds = mod_def and default.node_sound_wood_defaults(), + node_box = { + type = "fixed", + fixed = {-0.2, -0.5, -0.2, 0.2, 0, 0.2} + }, + collision_box = { + type = "fixed", + fixed = {-0.4, -1.5, -0.4, 0.4, 0, 0.4} + }, + selection_box = { + type = "fixed", + fixed = {-0.4, -1.5, -0.4, 0.4, 0, 0.4} + } +}) + +minetest.register_craft({ + output = "mobs:fence_top 12", + recipe = { + {"group:wood", "group:wood", "group:wood"}, + {"", items.fence_wood, ""} + } +}) + + +-- items that can be used as fuel +minetest.register_craft({ + type = "fuel", + recipe = "mobs:nametag", + burntime = 3 +}) + +minetest.register_craft({ + type = "fuel", + recipe = "mobs:lasso", + burntime = 7 +}) + +minetest.register_craft({ + type = "fuel", + recipe = "mobs:net", + burntime = 8 +}) + +minetest.register_craft({ + type = "fuel", + recipe = "mobs:leather", + burntime = 4 +}) + +minetest.register_craft({ + type = "fuel", + recipe = "mobs:saddle", + burntime = 7 +}) + +minetest.register_craft({ + type = "fuel", + recipe = "mobs:fence_wood", + burntime = 7 +}) + +minetest.register_craft({ + type = "fuel", + recipe = "mobs:fence_top", + burntime = 2 +}) + + +-- this tool spawns same mob and adds owner, protected, nametag info +-- then removes original entity, this is used for fixing any issues. +-- also holding sneak while punching mob lets you change texture name. + +local tex_obj + +minetest.register_tool(":mobs:mob_reset_stick", { + description = S("Mob Reset Stick"), + inventory_image = "default_stick.png^[colorize:#ff000050", + stack_max = 1, + groups = {not_in_creative_inventory = 1}, + + on_use = function(itemstack, user, pointed_thing) + + if pointed_thing.type ~= "object" then + return + end + + local obj = pointed_thing.ref + + local control = user:get_player_control() + local sneak = control and control.sneak + + -- spawn same mob with saved stats, with random texture + if obj and not sneak then + + local self = obj:get_luaentity() + local obj2 = minetest.add_entity(obj:get_pos(), self.name) + + if obj2 then + + local ent2 = obj2:get_luaentity() + + ent2.protected = self.protected + ent2.owner = self.owner + ent2.nametag = self.nametag + ent2.gotten = self.gotten + ent2.tamed = self.tamed + ent2.health = self.health + ent2.order = self.order + + if self.child then + obj2:set_velocity({x = 0, y = self.jump_height, z = 0}) + end + + obj2:set_properties({nametag = self.nametag}) + + obj:remove() + end + end + + -- display form to enter texture name ending in .png + if obj and sneak then + + tex_obj = obj + + -- get base texture + local bt = tex_obj:get_luaentity().base_texture[1] + + if type(bt) ~= "string" then + bt = "" + end + + local name = user:get_player_name() + + minetest.show_formspec(name, "mobs_texture", "size[8,4]" + .. "field[0.5,1;7.5,0;name;" + .. minetest.formspec_escape(S("Enter texture:")) .. ";" .. bt .. "]" + .. "button_exit[2.5,3.5;3,1;mob_texture_change;" + .. minetest.formspec_escape(S("Change")) .. "]") + end + end +}) + +minetest.register_on_player_receive_fields(function(player, formname, fields) + + -- right-clicked with nametag and name entered? + if formname == "mobs_texture" + and fields.name + and fields.name ~= "" then + + -- does mob still exist? + if not tex_obj + or not tex_obj:get_luaentity() then + return + end + + -- make sure nametag is being used to name mob + local item = player:get_wielded_item() + + if item:get_name() ~= "mobs:mob_reset_stick" then + return + end + + -- limit name entered to 64 characters long + if fields.name:len() > 64 then + fields.name = fields.name:sub(1, 64) + end + + -- update texture + local self = tex_obj:get_luaentity() + + self.base_texture = {fields.name} + + tex_obj:set_properties({textures = {fields.name}}) + + -- reset external variable + tex_obj = nil + end +end) + + +-- Meat Block +minetest.register_node("mobs:meatblock", { + description = S("Meat Block"), + tiles = {"mobs_meat_top.png", "mobs_meat_bottom.png", "mobs_meat_side.png"}, + paramtype2 = "facedir", + groups = {choppy = 1, oddly_breakable_by_hand = 1, flammable = 2}, + sounds = mod_def and default.node_sound_leaves_defaults(), + on_place = minetest.rotate_node, + on_use = minetest.item_eat(20) +}) + +minetest.register_craft({ + output = "mobs:meatblock", + recipe = { + { items.meat_cooked, items.meat_cooked, items.meat_cooked }, + { items.meat_cooked, items.meat_cooked, items.meat_cooked }, + { items.meat_cooked, items.meat_cooked, items.meat_cooked } + } +}) + +-- Meat Block (raw) +minetest.register_node("mobs:meatblock_raw", { + description = S("Raw Meat Block"), + tiles = {"mobs_meat_raw_top.png", "mobs_meat_raw_bottom.png", "mobs_meat_raw_side.png"}, + paramtype2 = "facedir", + groups = {choppy = 1, oddly_breakable_by_hand = 1, flammable = 2}, + sounds = mod_def and default.node_sound_leaves_defaults(), + on_place = minetest.rotate_node, + on_use = minetest.item_eat(20) +}) + +minetest.register_craft({ + output = "mobs:meatblock_raw", + recipe = { + { items.meat_raw, items.meat_raw, items.meat_raw }, + { items.meat_raw, items.meat_raw, items.meat_raw }, + { items.meat_raw, items.meat_raw, items.meat_raw } + } +}) + +minetest.register_craft({ + type = "cooking", + output = "mobs:meatblock", + recipe = "mobs:meatblock_raw", + cooktime = 30 +}) diff --git a/mods/mobs/depends.txt b/mods/mobs/depends.txt new file mode 100644 index 0000000..dccd148 --- /dev/null +++ b/mods/mobs/depends.txt @@ -0,0 +1,12 @@ +default +tnt? +dye? +farming? +invisibility? +intllib? +lucky_block? +cmi? +toolranks? +pathfinder? +player_api? +mtobjid? diff --git a/mods/mobs/description.txt b/mods/mobs/description.txt new file mode 100644 index 0000000..c812ea0 --- /dev/null +++ b/mods/mobs/description.txt @@ -0,0 +1 @@ +MOBS api for mobs to add animals or monsters etc \ No newline at end of file diff --git a/mods/mobs/init.lua b/mods/mobs/init.lua new file mode 100644 index 0000000..0205b39 --- /dev/null +++ b/mods/mobs/init.lua @@ -0,0 +1,26 @@ + +local path = minetest.get_modpath("mobs") + +-- Peaceful player privilege +minetest.register_privilege("peaceful_player", { + description = "Prevents Mobs Redo mobs from attacking player", + give_to_singleplayer = false +}) + + +-- Mob API +dofile(path .. "/api.lua") + +-- Rideable Mobs +dofile(path .. "/mount.lua") + +-- Mob Items +dofile(path .. "/crafts.lua") + +-- Mob Spawner +dofile(path .. "/spawner.lua") + +-- Lucky Blocks +dofile(path .. "/lucky_block.lua") + +print("[MOD] Mobs Redo loaded") diff --git a/mods/mobs/license.txt b/mods/mobs/license.txt new file mode 100644 index 0000000..8bde134 --- /dev/null +++ b/mods/mobs/license.txt @@ -0,0 +1,37 @@ +The MIT License (MIT) + +Copyright (c) 2016 TenPlus1 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +Textures under CC0 license by TenPlus1 + + +ShadowNinja (CC BY-SA 3.0): + tnt_smoke.png + +mobs_swing.ogg by qubodup (CC0) + - http://freesound.org/people/qubodup/sounds/60012/ + +mobs_spell.ogg by littlerobotsoundfactory (CC0) + - http://freesound.org/people/LittleRobotSoundFactory/sounds/270396/ + +mobs_punch.ogg by Merrick079 (CC0) + - https://freesound.org/people/Merrick079/sounds/566436/ diff --git a/mods/mobs/locale/de_DE.po b/mods/mobs/locale/de_DE.po new file mode 100644 index 0000000..ac406bf --- /dev/null +++ b/mods/mobs/locale/de_DE.po @@ -0,0 +1,131 @@ +# Mobs Redo translation. +# Copyright (C) 2017 TenPlus1 +# This file is distributed under the same license as the mobs package. +# Wuzzy , 2017 +# +msgid "" +msgstr "" +"Project-Id-Version: mobs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-02 16:48+0200\n" +"PO-Revision-Date: 2017-07-02 14:27+0200\n" +"Last-Translator: Wuzzy \n" +"Language-Team: \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.0.2\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: api.lua +msgid "** Peaceful Mode Active - No Monsters Will Spawn" +msgstr "" + +#: api.lua +msgid "Mob has been protected!" +msgstr "Kreatur wurde geschützt!" + +#: api.lua +msgid "@1 (Tamed)" +msgstr "@1 (Gezähmt)" + +#: api.lua +msgid "Not tamed!" +msgstr "Nicht gezähmt!" + +#: api.lua +msgid "@1 is owner!" +msgstr "@1 ist der Besitzer!" + +#: api.lua +msgid "Missed!" +msgstr "Daneben!" + +#: api.lua +msgid "Already protected!" +msgstr "Bereits geschützt!" + +#: api.lua +msgid "@1 at full health (@2)" +msgstr "@1 bei voller Gesundheit (@2)" + +#: api.lua +msgid "@1 has been tamed!" +msgstr "@1 wurde gezähmt!" + +#: api.lua +msgid "Enter name:" +msgstr "Namen eingeben:" + +#: api.lua +msgid "Rename" +msgstr "Umbenennen" + +#: crafts.lua +msgid "Name Tag" +msgstr "Namensschild" + +#: crafts.lua +msgid "Leather" +msgstr "Leder" + +#: crafts.lua +msgid "Raw Meat" +msgstr "Rohes Fleisch" + +#: crafts.lua +msgid "Meat" +msgstr "Fleisch" + +#: crafts.lua +msgid "Lasso (right-click animal to put in inventory)" +msgstr "Lasso (Rechtsklick auf Tier, um es zu nehmen)" + +#: crafts.lua +msgid "Net (right-click animal to put in inventory)" +msgstr "Netz (Rechtsklick auf Tier, um es zu nehmen)" + +#: crafts.lua +msgid "Steel Shears (right-click to shear)" +msgstr "Stahlschere (Rechtsklick zum Scheren)" + +#: crafts.lua +msgid "Mob Protection Rune" +msgstr "Kreaturschutzrune" + +#: crafts.lua +msgid "Saddle" +msgstr "Sattel" + +#: crafts.lua +msgid "Mob Fence" +msgstr "Kreaturen Zaun" + +#: spawner.lua +msgid "Mob Spawner" +msgstr "Kreaturenspawner" + +#: spawner.lua +msgid "Mob MinLight MaxLight Amount PlayerDist" +msgstr "Kreatur MinLicht MaxLicht Menge SpielerEntfng" + +#: spawner.lua +msgid "Spawner Not Active (enter settings)" +msgstr "Nicht aktiv (Einstellungen eingeben)" + +#: spawner.lua +msgid "Spawner Active (@1)" +msgstr "Spawner aktiv (@1)" + +#: spawner.lua +msgid "Mob Spawner settings failed!" +msgstr "Kreaturenspawner-Einstellungen gescheitert!" + +#: spawner.lua +msgid "" +"Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] " +"distance[1-20] y_offset[-10 to 10]”" +msgstr "" +"Syntax: „name min_licht[0-14] max_licht[0-14] max_mobs_im_gebiet[0 zum " +"Deaktivieren] distanz[1-20] y_versatz[-10 bis 10]“" diff --git a/mods/mobs/locale/es.po b/mods/mobs/locale/es.po new file mode 100644 index 0000000..849db66 --- /dev/null +++ b/mods/mobs/locale/es.po @@ -0,0 +1,128 @@ +# Mobs Redo translation. +# Copyright (C) 2017 TenPlus1 +# This file is distributed under the same license as the mobs package. +# Wuzzy , 2017 +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-16 16:48+0200\n" +"PO-Revision-Date: 2017-07-16 16:48+0200\n" +"Last-Translator: Aleks \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: api.lua +msgid "** Peaceful Mode Active - No Monsters Will Spawn" +msgstr "" + +#: api.lua +msgid "Mob has been protected!" +msgstr "El mob ha sido protegido!" + +#: api.lua +msgid "@1 (Tamed)" +msgstr "@1 (Domesticado)" + +#: api.lua +msgid "Not tamed!" +msgstr "No domesticado!" + +#: api.lua +msgid "@1 is owner!" +msgstr "@1 es el dueño!" + +#: api.lua +msgid "Missed!" +msgstr "Perdido!" + +#: api.lua +msgid "Already protected!" +msgstr "Ya está protegido!" + +#: api.lua +msgid "@1 at full health (@2)" +msgstr "@1 con salud llena (@2)" + +#: api.lua +msgid "@1 has been tamed!" +msgstr "@1 ha sido domesticado!" + +#: api.lua +msgid "Enter name:" +msgstr "Ingrese nombre:" + +#: api.lua +msgid "Rename" +msgstr "Renombrar" + +#: crafts.lua +msgid "Name Tag" +msgstr "Nombrar etiqueta" + +#: crafts.lua +msgid "Leather" +msgstr "Cuero" + +#: crafts.lua +msgid "Raw Meat" +msgstr "Carne cruda" + +#: crafts.lua +msgid "Meat" +msgstr "Carne" + +#: crafts.lua +msgid "Lasso (right-click animal to put in inventory)" +msgstr "Lazo (click derecho en animal para colocar en inventario)" + +#: crafts.lua +msgid "Net (right-click animal to put in inventory)" +msgstr "Red (click derecho en animal para colocar en inventario)" + +#: crafts.lua +msgid "Steel Shears (right-click to shear)" +msgstr "Tijera de acero (click derecho para esquilar)" + +#: crafts.lua +msgid "Mob Protection Rune" +msgstr "Runa de protección de Mob" + +#: crafts.lua +msgid "Saddle" +msgstr "Montura" + +#: crafts.lua +msgid "Mob Fence" +msgstr "" + +#: spawner.lua +msgid "Mob Spawner" +msgstr "Generador de Mob" + +#: spawner.lua +msgid "Mob MinLight MaxLight Amount PlayerDist" +msgstr "Mob LuzMin LuzMax Cantidad DistJugador" + +#: spawner.lua +msgid "Spawner Not Active (enter settings)" +msgstr "Generador no activo (ingrese config)" + +#: spawner.lua +msgid "Spawner Active (@1)" +msgstr "Generador activo (@1)" + +#: spawner.lua +msgid "Mob Spawner settings failed!" +msgstr "Configuracion de generador de Mob falló!" + +#: spawner.lua +msgid "" +"Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] " +"distance[1-20] y_offset[-10 to 10]”" +msgstr "Sintaxis: “nombre luz_min[0-14] luz_max[0-14] max_mobs_en_area[0 para deshabilitar] " +"distancia[1-20] compensacion[-10 a 10]”" diff --git a/mods/mobs/locale/fr.po b/mods/mobs/locale/fr.po new file mode 100644 index 0000000..53b5f9f --- /dev/null +++ b/mods/mobs/locale/fr.po @@ -0,0 +1,136 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-29 09:13+0200\n" +"PO-Revision-Date: 2020-08-13 21:20+0500\n" +"Last-Translator: Olivier Dragon \n" +"Language-Team: \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: api.lua +msgid "** Peaceful Mode Active - No Monsters Will Spawn" +msgstr "** Mode pacifique activé - Aucun monstre ne sera généré" + +#: api.lua +msgid "Mob has been protected!" +msgstr "L'animal a été protégé !" + +#: api.lua +msgid "@1 (Tamed)" +msgstr "@1 (apprivoisé)" + +#: api.lua +msgid "Not tamed!" +msgstr "Non-apprivoisé !" + +#: api.lua +msgid "@1 is owner!" +msgstr "Appartient à @1 !" + +#: api.lua +msgid "Missed!" +msgstr "Raté !" + +#: api.lua +msgid "Already protected!" +msgstr "Déjà protégé !" + +#: api.lua +msgid "@1 at full health (@2)" +msgstr "@1 est en pleine forme (@2) " + +#: api.lua +msgid "@1 has been tamed!" +msgstr "@1 a été apprivoisé ! " + +#: api.lua +msgid "Enter name:" +msgstr "Saisissez un nom :" + +#: api.lua +msgid "Rename" +msgstr "Renommer" + +#: crafts.lua +msgid "Name Tag" +msgstr "Étiquette de collier" + +#: crafts.lua +msgid "Leather" +msgstr "Cuir" + +#: crafts.lua +msgid "Raw Meat" +msgstr "Viande crue" + +#: crafts.lua +msgid "Meat" +msgstr "Viande" + +#: crafts.lua +msgid "Lasso (right-click animal to put in inventory)" +msgstr "Lasso (clic droit sur l'animal pour le mettre dans l'inventaire)" + +#: crafts.lua +msgid "Net (right-click animal to put in inventory)" +msgstr "Filet (clic droit sur l'animal pour le mettre dans l'inventaire)" + +#: crafts.lua +msgid "Steel Shears (right-click to shear)" +msgstr "Ciseaux à laine (clic droit pour tondre)" + +#: crafts.lua +msgid "Mob Protection Rune" +msgstr "Rune de protection des animaux" + +#: crafts.lua +msgid "Saddle" +msgstr "Selle" + +#: crafts.lua +msgid "Mob Fence" +msgstr "Clôture à animaux" + +#: crafts.lua +msgid "Mob Fence Top" +msgstr "Haut de clôture à animaux" + +#: spawner.lua +msgid "Mob Spawner" +msgstr "Générateur de mob" + +#: spawner.lua +msgid "(mob name) (min light) (max light) (amount) (player distance) (Y offset)" +msgstr "(Nom) (MinLumière) (MaxLumière) (Quantité) (Distance du Joueur) (Décalage en Y)" + +#: spawner.lua +msgid "Spawner Not Active (enter settings)" +msgstr "Générateur non actif (entrez les paramètres)" + +#: spawner.lua +msgid "Command:" +msgstr "Commande:" + +#: spawner.lua +msgid "Spawner Active (@1)" +msgstr "Générateur actif (@1)" + +#: spawner.lua +msgid "Mob Spawner settings failed!" +msgstr "Echec des paramètres du générateur" + +#: spawner.lua +msgid "" +"Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] " +"distance[1-20] y_offset[-10 to 10]”" +msgstr "Syntaxe : “nom min_lumière[0-14] max_lumière[0-14] max_mobs_dans_zone[0 pour désactiver] distance[1-20] décalage_y[-10 à 10]“" diff --git a/mods/mobs/locale/it.po b/mods/mobs/locale/it.po new file mode 100644 index 0000000..a40c33f --- /dev/null +++ b/mods/mobs/locale/it.po @@ -0,0 +1,131 @@ +# ITALIAN LOCALE FILE FOR THE MOBS REDO MODULE +# Copyright (c) 2014 Krupnov Pavel and 2016 TenPlus1 +# This file is distributed under the same license as the MOBS REDO package. +# Hamlet , 2017. +# +msgid "" +msgstr "" +"Project-Id-Version: Italian locale file for the Mobs Redo module\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-02 16:48+0200\n" +"PO-Revision-Date: 2020-05-11 13:33+0200\n" +"Last-Translator: Hamlet \n" +"Language-Team: \n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 2.2.1\n" + +#: api.lua +msgid "** Peaceful Mode Active - No Monsters Will Spawn" +msgstr "** Modalità pacifica attiva - non comparirà nessun mostro" + +#: api.lua +msgid "Mob has been protected!" +msgstr "Il mob è stato protetto!" + +#: api.lua +msgid "@1 (Tamed)" +msgstr "@1 (Addomesticato)" + +#: api.lua +msgid "Not tamed!" +msgstr "Non addomesticato!" + +#: api.lua +msgid "@1 is owner!" +msgstr "Il padrone è @1!" + +#: api.lua +msgid "Missed!" +msgstr "Mancato!" + +#: api.lua +msgid "Already protected!" +msgstr "Già protetto!" + +#: api.lua +msgid "@1 at full health (@2)" +msgstr "@1 in piena salute (@2)" + +#: api.lua +msgid "@1 has been tamed!" +msgstr "@1 è stato addomesticato!" + +#: api.lua +msgid "Enter name:" +msgstr "Inserire il nome:" + +#: api.lua +msgid "Rename" +msgstr "Rinomina" + +#: crafts.lua +msgid "Name Tag" +msgstr "Targhetta" + +#: crafts.lua +msgid "Leather" +msgstr "Pelle" + +#: crafts.lua +msgid "Raw Meat" +msgstr "Carne cruda" + +#: crafts.lua +msgid "Meat" +msgstr "Carne" + +#: crafts.lua +msgid "Lasso (right-click animal to put in inventory)" +msgstr "Lazo (click di destro per mettere l'animale nell'inventario)" + +#: crafts.lua +msgid "Net (right-click animal to put in inventory)" +msgstr "Rete (click destro per mettere l'animale nell'inventario)" + +#: crafts.lua +msgid "Steel Shears (right-click to shear)" +msgstr "Cesoie d'acciaio (click destro per tosare)" + +#: crafts.lua +msgid "Mob Protection Rune" +msgstr "Runa di protezione per mob" + +#: crafts.lua +msgid "Saddle" +msgstr "Sella" + +#: crafts.lua +msgid "Mob Fence" +msgstr "Recinzione per mob" + +#: spawner.lua +msgid "Mob Spawner" +msgstr "Generatore di mob" + +#: spawner.lua +msgid "Mob MinLight MaxLight Amount PlayerDist" +msgstr "Mob LuceMin LuceMax Ammontare DistGiocat." + +#: spawner.lua +msgid "Spawner Not Active (enter settings)" +msgstr "Generatore inattivo (inserire le impostazioni)" + +#: spawner.lua +msgid "Spawner Active (@1)" +msgstr "Generatore attivo (@1)" + +#: spawner.lua +msgid "Mob Spawner settings failed!" +msgstr "Impostazioni del generatore di mob fallite!" + +#: spawner.lua +msgid "" +"Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] " +"distance[1-20] y_offset[-10 to 10]”" +msgstr "" +"Sintassi: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 per " +"disabilitare] distance[1-20] y_offset[-10 fino a 10]”" diff --git a/mods/mobs/locale/mobs.de.tr b/mods/mobs/locale/mobs.de.tr new file mode 100644 index 0000000..f11b665 --- /dev/null +++ b/mods/mobs/locale/mobs.de.tr @@ -0,0 +1,34 @@ +# textdomain:mobs +#** Peaceful Mode Active - No Monsters Will Spawn= +@1 (Tamed)=@1 (Gezähmt) +@1 at full health (@2)=@1 bei voller Gesundheit (@2) +@1 has been tamed!=@1 wurde gezähmt! +@1 is owner!=@1 ist der Besitzer! +#Active Mob Limit Reached!= +Already protected!=Bereits geschützt! +#Change= +#Command:= +Enter name:=Namen eingeben: +#Enter texture:= +Lasso (right-click animal to put in inventory)=Lasso (Rechtsklick auf Tier, um es zu nehmen) +Leather=Leder +Meat=Fleisch +Missed!=Daneben! +Mob Fence=Kreaturen Zaun +#Mob Fence Top= +Mob Protection Rune=Kreaturschutzrune +#Mob Reset Stick= +#Mob Spawner= +Mob Spawner settings failed!=Kreaturenspawner-Einstellungen gescheitert! +Mob has been protected!=Kreatur wurde geschützt! +Name Tag=Namensschild +Net (right-click animal to put in inventory)=Netz (Rechtsklick auf Tier, um es zu nehmen) +Not tamed!=Nicht gezähmt! +Raw Meat=Rohes Fleisch +Rename=Umbenennen +Saddle=Sattel +Spawner Active (@1)=Spawner aktiv (@1) +Spawner Not Active (enter settings)=Nicht aktiv (Einstellungen eingeben) +Steel Shears (right-click to shear)=Stahlschere (Rechtsklick zum Scheren) +#Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”= +#lifetimer expired, removed @1= diff --git a/mods/mobs/locale/mobs.en.tr b/mods/mobs/locale/mobs.en.tr new file mode 100644 index 0000000..fab62b7 --- /dev/null +++ b/mods/mobs/locale/mobs.en.tr @@ -0,0 +1,34 @@ +# textdomain:mobs +#** Peaceful Mode Active - No Monsters Will Spawn= +#@1 (Tamed)= +#@1 at full health (@2)= +#@1 has been tamed!= +#@1 is owner!= +#Active Mob Limit Reached!= +#Already protected!= +#Change= +#Command:= +#Enter name:= +#Enter texture:= +#Lasso (right-click animal to put in inventory)= +#Leather= +#Meat= +#Missed!= +#Mob Fence= +#Mob Fence Top= +#Mob Protection Rune= +#Mob Reset Stick= +#Mob Spawner= +#Mob Spawner settings failed!= +#Mob has been protected!= +#Name Tag= +#Net (right-click animal to put in inventory)= +#Not tamed!= +#Raw Meat= +#Rename= +#Saddle= +#Spawner Active (@1)= +#Spawner Not Active (enter settings)= +#Steel Shears (right-click to shear)= +#Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”= +#lifetimer expired, removed @1= diff --git a/mods/mobs/locale/mobs.es.tr b/mods/mobs/locale/mobs.es.tr new file mode 100644 index 0000000..29ee934 --- /dev/null +++ b/mods/mobs/locale/mobs.es.tr @@ -0,0 +1,34 @@ +# textdomain:mobs +#** Peaceful Mode Active - No Monsters Will Spawn= +@1 (Tamed)=@1 (Domesticado) +@1 at full health (@2)=@1 con salud llena (@2) +@1 has been tamed!=@1 ha sido domesticado! +@1 is owner!=@1 es el dueño! +#Active Mob Limit Reached!= +Already protected!=Ya está protegido! +#Change= +#Command:= +Enter name:=Ingrese nombre: +#Enter texture:= +Lasso (right-click animal to put in inventory)=Lazo (click derecho en animal para colocar en inventario) +Leather=Cuero +Meat=Carne +Missed!=Perdido! +#Mob Fence= +#Mob Fence Top= +Mob Protection Rune=Runa de protección de Mob +#Mob Reset Stick= +#Mob Spawner= +Mob Spawner settings failed!=Configuracion de generador de Mob falló! +Mob has been protected!=El mob ha sido protegido! +Name Tag=Nombrar etiqueta +Net (right-click animal to put in inventory)=Red (click derecho en animal para colocar en inventario) +Not tamed!=No domesticado! +Raw Meat=Carne cruda +Rename=Renombrar +Saddle=Montura +Spawner Active (@1)=Generador activo (@1) +Spawner Not Active (enter settings)=Generador no activo (ingrese config) +Steel Shears (right-click to shear)=Tijera de acero (click derecho para esquilar) +#Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”= +#lifetimer expired, removed @1= diff --git a/mods/mobs/locale/mobs.fr.tr b/mods/mobs/locale/mobs.fr.tr new file mode 100644 index 0000000..3e137fb --- /dev/null +++ b/mods/mobs/locale/mobs.fr.tr @@ -0,0 +1,34 @@ +# textdomain:mobs +** Peaceful Mode Active - No Monsters Will Spawn=** Mode pacifique activé - aucun monstre ne sera généré +@1 (Tamed)=@1 (apprivoisé) +@1 at full health (@2)=@1 est en pleine forme (@2) +@1 has been tamed!=@1 a été apprivoisé ! +@1 is owner!=Appartient à @1 ! +Active Mob Limit Reached!=Limite atteinte du nombre des êtres vivants actifs ! +Already protected!=Déjà protégé ! +Change=Changer +Command:=Commande : +Enter name:=Saisissez un nom : +Enter texture:=Saisissez une texture : +Lasso (right-click animal to put in inventory)=Lasso (clic droit sur l'animal pour le mettre dans l'inventaire) +Leather=Cuir +Meat=Viande +Missed!=Raté ! +Mob Fence= Clôture à animaux +Mob Fence Top=Haut de clôture à animaux +Mob Protection Rune=Rune de protection des animaux +Mob Reset Stick=Baguette de réinitialisation des êtres vivants +Mob Spawner=Créateur d'êtres vivants +Mob Spawner settings failed!=Échec des paramètres du créateur d'être vivants ! +Mob has been protected!=L'animal a été protégé ! +Name Tag=Étiquette de collier +Net (right-click animal to put in inventory)=Filet (clic droit sur l'animal pour le mettre dans l'inventaire) +Not tamed!=Non-apprivoisé ! +Raw Meat=Viande crue +Rename=Renommer +Saddle=Selle +Spawner Active (@1)=Créateur actif (@1) +Spawner Not Active (enter settings)=Créateur non actif (entrez les paramètres) +Steel Shears (right-click to shear)=Ciseaux à laine (clic droit pour tondre) +Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=Syntaxe : «name min_lumière[0-14] max_lumière[0-14] max_être_vivant_dans_région[0 pour désactiver] distance_joueur[1-20] décalage_y[-10 to 10]» +lifetimer expired, removed @1=Être immortel expiré ; @1 retiré diff --git a/mods/mobs/locale/mobs.it.tr b/mods/mobs/locale/mobs.it.tr new file mode 100644 index 0000000..6b5edd9 --- /dev/null +++ b/mods/mobs/locale/mobs.it.tr @@ -0,0 +1,34 @@ +# textdomain:mobs +** Peaceful Mode Active - No Monsters Will Spawn=** Modalità pacifica attiva - non comparirà nessun mostro +@1 (Tamed)=@1 (Addomesticato) +@1 at full health (@2)=@1 in piena salute (@2) +@1 has been tamed!=@1 è stato addomesticato! +@1 is owner!=Il padrone è @1! +#Active Mob Limit Reached!= +Already protected!=Già protetto! +#Change= +#Command:= +Enter name:=Inserire il nome: +#Enter texture:= +Lasso (right-click animal to put in inventory)=Lazo (click di destro per mettere l'animale nell'inventario) +Leather=Pelle +Meat=Carne +Missed!=Mancato! +Mob Fence=Recinzione per mob +#Mob Fence Top= +Mob Protection Rune=Runa di protezione per mob +#Mob Reset Stick= +#Mob Spawner= +Mob Spawner settings failed!=Impostazioni del generatore di mob fallite! +Mob has been protected!=Il mob è stato protetto! +Name Tag=Targhetta +Net (right-click animal to put in inventory)=Rete (click destro per mettere l'animale nell'inventario) +Not tamed!=Non addomesticato! +Raw Meat=Carne cruda +Rename=Rinomina +Saddle=Sella +Spawner Active (@1)=Generatore attivo (@1) +Spawner Not Active (enter settings)=Generatore inattivo (inserire le impostazioni) +Steel Shears (right-click to shear)=Cesoie d'acciaio (click destro per tosare) +#Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”= +#lifetimer expired, removed @1= diff --git a/mods/mobs/locale/mobs.ms.tr b/mods/mobs/locale/mobs.ms.tr new file mode 100644 index 0000000..f79e2fb --- /dev/null +++ b/mods/mobs/locale/mobs.ms.tr @@ -0,0 +1,34 @@ +# textdomain:mobs +** Peaceful Mode Active - No Monsters Will Spawn=** Mod Aman Diaktifkan - Tiada Raksasa Akan Muncul +@1 (Tamed)=@1 (Jinak) +@1 at full health (@2)=Mata kesihatan @1 telah penuh (@2) +@1 has been tamed!=@1 telah dijinakkan! +@1 is owner!=Ini hak milik @1! +#Active Mob Limit Reached!= +Already protected!=Telah dilindungi! +#Change= +#Command:= +Enter name:=Masukkan nama: +#Enter texture:= +Lasso (right-click animal to put in inventory)=Tanjul (klik-kanan haiwan untuk masukkan ke inventori) +Leather=Kulit +Meat=Daging Bakar +Missed!=Terlepas! +Mob Fence=Pagar Mob +#Mob Fence Top= +Mob Protection Rune=Rune Perlindungan Mob +#Mob Reset Stick= +#Mob Spawner= +Mob Spawner settings failed!=Penetapan Pewujud Mob gagal! +Mob has been protected!=Mob telah pun dilindungi! +Name Tag=Tanda Nama +Net (right-click animal to put in inventory)=Jaring (klik-kanan haiwan untuk masukkan ke inventori) +Not tamed!=Belum dijinakkan! +Raw Meat=Daging Mentah +Rename=Namakan semula +Saddle=Pelana +Spawner Active (@1)=Pewujud Mob Aktif (@1) +Spawner Not Active (enter settings)=Pewujud Mob Tidak Aktif (masukkan tetapan) +Steel Shears (right-click to shear)=Ketam Keluli (klik-kanan untuk mengetam bulu biri-biri) +#Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”= +#lifetimer expired, removed @1= diff --git a/mods/mobs/locale/mobs.pt.tr b/mods/mobs/locale/mobs.pt.tr new file mode 100644 index 0000000..b62e989 --- /dev/null +++ b/mods/mobs/locale/mobs.pt.tr @@ -0,0 +1,34 @@ +# textdomain:mobs +#** Peaceful Mode Active - No Monsters Will Spawn= +#@1 (Tamed)= +@1 at full health (@2)=@1 em plena saude (@2) +@1 has been tamed!=@1 foi domesticado! +@1 is owner!=Dono @1! +#Active Mob Limit Reached!= +#Already protected!= +#Change= +#Command:= +Enter name:=Insira um nome: +#Enter texture:= +Lasso (right-click animal to put in inventory)=Laço (clique-direito no animal para por no inventario) +Leather=Couro +Meat=Carne +Missed!=Faltou! +#Mob Fence= +#Mob Fence Top= +#Mob Protection Rune= +#Mob Reset Stick= +#Mob Spawner= +Mob Spawner settings failed!=Configuraçao de Spawnador do Mob falhou! +#Mob has been protected!= +Name Tag=Etiqueta +Net (right-click animal to put in inventory)=Net (clique-direito no animal para por no inventario) +Not tamed!=Indomesticado! +Raw Meat=Carne crua +Rename=Renomear +#Saddle= +Spawner Active (@1)=Spawnador Ativo (@1) +Spawner Not Active (enter settings)=Spawnador Inativo (configurar) +Steel Shears (right-click to shear)=Tesoura de Aço (clique-direito para tosquiar) +#Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”= +#lifetimer expired, removed @1= diff --git a/mods/mobs/locale/mobs.ru.tr b/mods/mobs/locale/mobs.ru.tr new file mode 100644 index 0000000..f7c201a --- /dev/null +++ b/mods/mobs/locale/mobs.ru.tr @@ -0,0 +1,34 @@ +# textdomain:mobs +** Peaceful Mode Active - No Monsters Will Spawn=** Мирный модус активирован - монстры не спаунятся +@1 (Tamed)=@1 (Прирученный) +@1 at full health (@2)=@1 при полном здоровье (@2) +@1 has been tamed!=@1 приручен +@1 is owner!=@1 владелец +#Active Mob Limit Reached!= +Already protected!=Уже защищен! +#Change= +#Command:= +Enter name:=Введите имя: +#Enter texture:= +Lasso (right-click animal to put in inventory)=Лассо (Правый клик - положить животное в инвентарь) +Leather=Кожа +Meat=Мясо +Missed!=Промазал! +Mob Fence=Забор от мобов +#Mob Fence Top= +Mob Protection Rune=Защитная руна мобов +#Mob Reset Stick= +#Mob Spawner= +Mob Spawner settings failed!=Настройки спаунера моба провалились +Mob has been protected!=Моб защищен! +Name Tag=Новый тэг +Net (right-click animal to put in inventory)=Сеть (Правый клик - положить животное в инвентарь) +Not tamed!=Не прирученный +Raw Meat=Сырое мясо +Rename=Переименовать +Saddle=Седло +Spawner Active (@1)=Активные спаунер (@1) +Spawner Not Active (enter settings)=Спаунер не активен (введите настройки) +Steel Shears (right-click to shear)=Ножницы (Правый клик - подстричь) +#Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”= +#lifetimer expired, removed @1= diff --git a/mods/mobs/locale/mobs.tr.tr b/mods/mobs/locale/mobs.tr.tr new file mode 100644 index 0000000..6e28706 --- /dev/null +++ b/mods/mobs/locale/mobs.tr.tr @@ -0,0 +1,34 @@ +# textdomain:mobs +#** Peaceful Mode Active - No Monsters Will Spawn= +#@1 (Tamed)= +@1 at full health (@2)=@1 tam canında (@2) +@1 has been tamed!=@1 tamamen evcilleştirilmiştir! +@1 is owner!=Sahibi @1! +#Active Mob Limit Reached!= +#Already protected!= +#Change= +#Command:= +Enter name:=İsim gir: +#Enter texture:= +Lasso (right-click animal to put in inventory)=Kement (hayvana sağ tıklayarak envantere koy) +Leather=Deri +Meat=Et +Missed!=Kaçırdın! +Mob Fence=Canavar Yaratıcı +#Mob Fence Top= +#Mob Protection Rune= +#Mob Reset Stick= +#Mob Spawner= +Mob Spawner settings failed!=Yaratıcı ayarları uygulanamadı. +#Mob has been protected!= +Name Tag=İsim etiketi +Net (right-click animal to put in inventory)=Ağ (hayvana sağ tıklayarak envantere koy) +Not tamed!=Evcil değil! +Raw Meat=Çiğ et +Rename=Yeniden adlandır +#Saddle= +Spawner Active (@1)=Yaratıcı aktif (@1) +Spawner Not Active (enter settings)=Yaratıcı aktif değil (ayarlara gir) +Steel Shears (right-click to shear)=Çelik makas (sağ tıklayarak kes) +#Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”= +#lifetimer expired, removed @1= diff --git a/mods/mobs/locale/mobs.zh_CN.tr b/mods/mobs/locale/mobs.zh_CN.tr new file mode 100644 index 0000000..fe7556c --- /dev/null +++ b/mods/mobs/locale/mobs.zh_CN.tr @@ -0,0 +1,34 @@ +# textdomain:mobs +** Peaceful Mode Active - No Monsters Will Spawn=** 和平模式已激活——没有怪物会产生 +@1 (Tamed)=@1(已驯服) +@1 at full health (@2)=@1已经满血(@2) +@1 has been tamed!=@1已经被驯服! +@1 is owner!=@1 是主人 +#Active Mob Limit Reached!= +Already protected!=已经被保护! +#Change= +#Command:= +Enter name:=输入名称: +#Enter texture:= +Lasso (right-click animal to put in inventory)=套索(右键单击动物以放入物品栏) +Leather=皮革 +Meat=肉 +Missed!=没抓住! +Mob Fence=Mob 栅栏 +#Mob Fence Top= +Mob Protection Rune=Mob 保护符文 +#Mob Reset Stick= +#Mob Spawner= +Mob Spawner settings failed!=Mob 孵化器设置失败! +Mob has been protected!=Mob 已经被保护了! +Name Tag=名称标签 +Net (right-click animal to put in inventory)=网(右键单击动物以放入物品栏) +Not tamed!=没有驯服! +Raw Meat=生肉 +Rename=重新命名 +Saddle=鞍 +Spawner Active (@1)=孵化器正在运转(@1) +Spawner Not Active (enter settings)=孵化器未使用(输入设置) +Steel Shears (right-click to shear)=钢剪(右键单击以剪切) +#Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”= +#lifetimer expired, removed @1= diff --git a/mods/mobs/locale/mobs.zh_TW.tr b/mods/mobs/locale/mobs.zh_TW.tr new file mode 100644 index 0000000..89bed26 --- /dev/null +++ b/mods/mobs/locale/mobs.zh_TW.tr @@ -0,0 +1,34 @@ +# textdomain:mobs +** Peaceful Mode Active - No Monsters Will Spawn=** 和平模式已激活——沒有怪物會產生 +@1 (Tamed)=@1(已馴服) +@1 at full health (@2)=@1已經滿血(@2) +@1 has been tamed!=@1已經被馴服! +@1 is owner!=@1 是主人 +#Active Mob Limit Reached!= +Already protected!=已經被保護! +#Change= +#Command:= +Enter name:=輸入名稱: +#Enter texture:= +Lasso (right-click animal to put in inventory)=套索(右鍵單擊動物以放入物品欄) +Leather=皮革 +Meat=肉 +Missed!=沒抓住! +Mob Fence=Mob 柵欄 +#Mob Fence Top= +Mob Protection Rune=Mob 保護符文 +#Mob Reset Stick= +#Mob Spawner= +Mob Spawner settings failed!=Mob 孵化器設置失敗! +Mob has been protected!=Mob 已經被保護了! +Name Tag=名稱標籤 +Net (right-click animal to put in inventory)=網(右鍵單擊動物以放入物品欄) +Not tamed!=沒有馴服! +Raw Meat=生肉 +Rename=重新命名 +Saddle=鞍 +Spawner Active (@1)=孵化器正在運轉(@1) +Spawner Not Active (enter settings)=孵化器未使用(輸入設置) +Steel Shears (right-click to shear)=鋼剪(右鍵單擊以剪切) +#Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”= +#lifetimer expired, removed @1= diff --git a/mods/mobs/locale/ms.po b/mods/mobs/locale/ms.po new file mode 100644 index 0000000..a2a31f3 --- /dev/null +++ b/mods/mobs/locale/ms.po @@ -0,0 +1,131 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-02-05 23:40+0800\n" +"PO-Revision-Date: 2018-02-05 23:51+0800\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.0.6\n" +"Last-Translator: MuhdNurHidayat (MNH48) \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Language: ms\n" + +#: api.lua +msgid "** Peaceful Mode Active - No Monsters Will Spawn" +msgstr "** Mod Aman Diaktifkan - Tiada Raksasa Akan Muncul" + +#: api.lua +msgid "Mob has been protected!" +msgstr "Mob telah pun dilindungi!" + +#: api.lua +msgid "@1 (Tamed)" +msgstr "@1 (Jinak)" + +#: api.lua +msgid "Not tamed!" +msgstr "Belum dijinakkan!" + +#: api.lua +msgid "@1 is owner!" +msgstr "Ini hak milik @1!" + +#: api.lua +msgid "Missed!" +msgstr "Terlepas!" + +#: api.lua +msgid "Already protected!" +msgstr "Telah dilindungi!" + +#: api.lua +msgid "@1 at full health (@2)" +msgstr "Mata kesihatan @1 telah penuh (@2)" + +#: api.lua +msgid "@1 has been tamed!" +msgstr "@1 telah dijinakkan!" + +#: api.lua +msgid "Enter name:" +msgstr "Masukkan nama:" + +#: api.lua +msgid "Rename" +msgstr "Namakan semula" + +#: crafts.lua +msgid "Name Tag" +msgstr "Tanda Nama" + +#: crafts.lua +msgid "Leather" +msgstr "Kulit" + +#: crafts.lua +msgid "Raw Meat" +msgstr "Daging Mentah" + +#: crafts.lua +msgid "Meat" +msgstr "Daging Bakar" + +#: crafts.lua +msgid "Lasso (right-click animal to put in inventory)" +msgstr "Tanjul (klik-kanan haiwan untuk masukkan ke inventori)" + +#: crafts.lua +msgid "Net (right-click animal to put in inventory)" +msgstr "Jaring (klik-kanan haiwan untuk masukkan ke inventori)" + +#: crafts.lua +msgid "Steel Shears (right-click to shear)" +msgstr "Ketam Keluli (klik-kanan untuk mengetam bulu biri-biri)" + +#: crafts.lua +msgid "Mob Protection Rune" +msgstr "Rune Perlindungan Mob" + +#: crafts.lua +msgid "Saddle" +msgstr "Pelana" + +#: crafts.lua +msgid "Mob Fence" +msgstr "Pagar Mob" + +#: spawner.lua +msgid "Mob Spawner" +msgstr "Pewujud Mob" + +#: spawner.lua +msgid "Mob MinLight MaxLight Amount PlayerDist" +msgstr "Mob CahayaMin CahayaMax Amaun JarakPemain" + +#: spawner.lua +msgid "Spawner Not Active (enter settings)" +msgstr "Pewujud Mob Tidak Aktif (masukkan tetapan)" + +#: spawner.lua +msgid "Spawner Active (@1)" +msgstr "Pewujud Mob Aktif (@1)" + +#: spawner.lua +msgid "Mob Spawner settings failed!" +msgstr "Penetapan Pewujud Mob gagal!" + +#: spawner.lua +msgid "" +"Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] " +"distance[1-20] y_offset[-10 to 10]”" +msgstr "" +"Sintaks: \"nama cahaya_minimum[0-14] cahaya_maksimum[0-14] " +"amaun_mob_maksimum[0 untuk lumpuhkan] jarak[1-20] ketinggian[-10 hingga 10]\"" diff --git a/mods/mobs/locale/pt.po b/mods/mobs/locale/pt.po new file mode 100644 index 0000000..b52afd6 --- /dev/null +++ b/mods/mobs/locale/pt.po @@ -0,0 +1,133 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: mobs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-02 16:48+0200\n" +"PO-Revision-Date: 2017-07-02 14:55+0200\n" +"Last-Translator: Wuzzy \n" +"Language-Team: \n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.0.2\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: api.lua +msgid "** Peaceful Mode Active - No Monsters Will Spawn" +msgstr "" + +#: api.lua +msgid "Mob has been protected!" +msgstr "" + +#: api.lua +msgid "@1 (Tamed)" +msgstr "" + +#: api.lua +msgid "Not tamed!" +msgstr "Indomesticado!" + +#: api.lua +msgid "@1 is owner!" +msgstr "Dono @1!" + +#: api.lua +msgid "Missed!" +msgstr "Faltou!" + +#: api.lua +msgid "Already protected!" +msgstr "" + +#: api.lua +msgid "@1 at full health (@2)" +msgstr "@1 em plena saude (@2)" + +#: api.lua +msgid "@1 has been tamed!" +msgstr "@1 foi domesticado!" + +#: api.lua +msgid "Enter name:" +msgstr "Insira um nome:" + +#: api.lua +msgid "Rename" +msgstr "Renomear" + +#: crafts.lua +msgid "Name Tag" +msgstr "Etiqueta" + +#: crafts.lua +msgid "Leather" +msgstr "Couro" + +#: crafts.lua +msgid "Raw Meat" +msgstr "Carne crua" + +#: crafts.lua +msgid "Meat" +msgstr "Carne" + +#: crafts.lua +#, fuzzy +msgid "Lasso (right-click animal to put in inventory)" +msgstr "Laço (clique-direito no animal para por no inventario)" + +#: crafts.lua +msgid "Net (right-click animal to put in inventory)" +msgstr "Net (clique-direito no animal para por no inventario)" + +#: crafts.lua +msgid "Steel Shears (right-click to shear)" +msgstr "Tesoura de Aço (clique-direito para tosquiar)" + +#: crafts.lua +msgid "Mob Protection Rune" +msgstr "" + +#: crafts.lua +msgid "Saddle" +msgstr "" + +#: crafts.lua +msgid "Mob Fence" +msgstr "" + +#: spawner.lua +msgid "Mob Spawner" +msgstr "Spawnador de Mob" + +#: spawner.lua +msgid "Mob MinLight MaxLight Amount PlayerDist" +msgstr "Mob LuzMinima LuzMaxima Valor DistJogador" + +#: spawner.lua +msgid "Spawner Not Active (enter settings)" +msgstr "Spawnador Inativo (configurar)" + +#: spawner.lua +msgid "Spawner Active (@1)" +msgstr "Spawnador Ativo (@1)" + +#: spawner.lua +msgid "Mob Spawner settings failed!" +msgstr "Configuraçao de Spawnador do Mob falhou!" + +#: spawner.lua +#, fuzzy +msgid "" +"Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] " +"distance[1-20] y_offset[-10 to 10]”" +msgstr "" +"> nome luz_min[0-14] luz_max[0-14] max_mobs_na_area[0 para desabilitar] " +"distancia[1-20] y_offset[-10 a 10]" diff --git a/mods/mobs/locale/ru.po b/mods/mobs/locale/ru.po new file mode 100644 index 0000000..6cde7ef --- /dev/null +++ b/mods/mobs/locale/ru.po @@ -0,0 +1,129 @@ +# Russian translation for the mobs_redo mod. +# Copyright (C) 2018 TenPlus1 +# This file is distributed under the same license as the mobs_redo package. +# Oleg720 , 2017. +# CodeXP , 2018. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-08-13 15:47+0200\n" +"PO-Revision-Date: 2018-03-23 22:22+0100\n" +"Last-Translator: CodeXP \n" +"Language-Team: \n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: api.lua +msgid "** Peaceful Mode Active - No Monsters Will Spawn" +msgstr "** Мирный модус активирован - монстры не спаунятся" + +#: api.lua +msgid "Mob has been protected!" +msgstr "Моб защищен!" + +#: api.lua +msgid "@1 (Tamed)" +msgstr "@1 (Прирученный)" + +#: api.lua +msgid "Not tamed!" +msgstr "Не прирученный" + +#: api.lua +msgid "@1 is owner!" +msgstr "@1 владелец" + +#: api.lua +msgid "Missed!" +msgstr "Промазал!" + +#: api.lua +msgid "Already protected!" +msgstr "Уже защищен!" + +#: api.lua +msgid "@1 at full health (@2)" +msgstr "@1 при полном здоровье (@2)" + +#: api.lua +msgid "@1 has been tamed!" +msgstr "@1 приручен" + +#: api.lua +msgid "Enter name:" +msgstr "Введите имя:" + +#: api.lua +msgid "Rename" +msgstr "Переименовать" + +#: crafts.lua +msgid "Name Tag" +msgstr "Новый тэг" + +#: crafts.lua +msgid "Leather" +msgstr "Кожа" + +#: crafts.lua +msgid "Raw Meat" +msgstr "Сырое мясо" + +#: crafts.lua +msgid "Meat" +msgstr "Мясо" + +#: crafts.lua +msgid "Lasso (right-click animal to put in inventory)" +msgstr "Лассо (Правый клик - положить животное в инвентарь)" + +#: crafts.lua +msgid "Net (right-click animal to put in inventory)" +msgstr "Сеть (Правый клик - положить животное в инвентарь)" + +#: crafts.lua +msgid "Steel Shears (right-click to shear)" +msgstr "Ножницы (Правый клик - подстричь)" + +#: crafts.lua +msgid "Mob Protection Rune" +msgstr "Защитная руна мобов" + +#: crafts.lua +msgid "Saddle" +msgstr "Седло" + +#: crafts.lua +msgid "Mob Fence" +msgstr "Забор от мобов" + +#: spawner.lua +msgid "Mob Spawner" +msgstr "Спаунер моба" + +#: spawner.lua +msgid "Mob MinLight MaxLight Amount PlayerDist" +msgstr "" + +#: spawner.lua +msgid "Spawner Not Active (enter settings)" +msgstr "Спаунер не активен (введите настройки)" + +#: spawner.lua +msgid "Spawner Active (@1)" +msgstr "Активные спаунер (@1)" + +#: spawner.lua +msgid "Mob Spawner settings failed!" +msgstr "Настройки спаунера моба провалились" + +#: spawner.lua +msgid "" +"Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] " +"distance[1-20] y_offset[-10 to 10]”" +msgstr "" diff --git a/mods/mobs/locale/template.pot b/mods/mobs/locale/template.pot new file mode 100644 index 0000000..a625ee3 --- /dev/null +++ b/mods/mobs/locale/template.pot @@ -0,0 +1,136 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-02 16:48+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: api.lua +msgid "** Peaceful Mode Active - No Monsters Will Spawn" +msgstr "" + +#: api.lua +msgid "Mob has been protected!" +msgstr "" + +#: api.lua +msgid "@1 (Tamed)" +msgstr "" + +#: api.lua +msgid "Not tamed!" +msgstr "" + +#: api.lua +msgid "@1 is owner!" +msgstr "" + +#: api.lua +msgid "Missed!" +msgstr "" + +#: api.lua +msgid "Already protected!" +msgstr "" + +#: api.lua +msgid "@1 at full health (@2)" +msgstr "" + +#: api.lua +msgid "@1 has been tamed!" +msgstr "" + +#: api.lua +msgid "Enter name:" +msgstr "" + +#: api.lua +msgid "Rename" +msgstr "" + +#: crafts.lua +msgid "Name Tag" +msgstr "" + +#: crafts.lua +msgid "Leather" +msgstr "" + +#: crafts.lua +msgid "Raw Meat" +msgstr "" + +#: crafts.lua +msgid "Meat" +msgstr "" + +#: crafts.lua +msgid "Lasso (right-click animal to put in inventory)" +msgstr "" + +#: crafts.lua +msgid "Net (right-click animal to put in inventory)" +msgstr "" + +#: crafts.lua +msgid "Steel Shears (right-click to shear)" +msgstr "" + +#: crafts.lua +msgid "Mob Protection Rune" +msgstr "" + +#: crafts.lua +msgid "Saddle" +msgstr "" + +#: crafts.lua +msgid "Mob Fence" +msgstr "" + +#: crafts.lua +msgid "Mob Fence Top" +msgstr "" + +#: spawner.lua +msgid "Mob Spawner" +msgstr "" + +#: spawner.lua +msgid "(mob name) (min light) (max light) (amount) (player distance) (Y offset)" +msgstr "" + +#: spawner.lua +msgid "Spawner Not Active (enter settings)" +msgstr "" + +#@ spawner.lua +msgid "Command:" +msgstr "" + +#: spawner.lua +msgid "Spawner Active (@1)" +msgstr "" + +#: spawner.lua +msgid "Mob Spawner settings failed!" +msgstr "" + +#: spawner.lua +msgid "" +"Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] " +"distance[1-20] y_offset[-10 to 10]”" +msgstr "" diff --git a/mods/mobs/locale/tr.po b/mods/mobs/locale/tr.po new file mode 100644 index 0000000..10688e2 --- /dev/null +++ b/mods/mobs/locale/tr.po @@ -0,0 +1,133 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: mobs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-02 16:48+0200\n" +"PO-Revision-Date: 2017-07-02 14:56+0200\n" +"Last-Translator: Wuzzy \n" +"Language-Team: \n" +"Language: tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.0.2\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: api.lua +msgid "** Peaceful Mode Active - No Monsters Will Spawn" +msgstr "" + +#: api.lua +msgid "Mob has been protected!" +msgstr "" + +#: api.lua +msgid "@1 (Tamed)" +msgstr "" + +#: api.lua +msgid "Not tamed!" +msgstr "Evcil değil!" + +#: api.lua +msgid "@1 is owner!" +msgstr "Sahibi @1!" + +#: api.lua +msgid "Missed!" +msgstr "Kaçırdın!" + +#: api.lua +msgid "Already protected!" +msgstr "" + +#: api.lua +msgid "@1 at full health (@2)" +msgstr "@1 tam canında (@2)" + +#: api.lua +msgid "@1 has been tamed!" +msgstr "@1 tamamen evcilleştirilmiştir!" + +#: api.lua +msgid "Enter name:" +msgstr "İsim gir:" + +#: api.lua +msgid "Rename" +msgstr "Yeniden adlandır" + +#: crafts.lua +msgid "Name Tag" +msgstr "İsim etiketi" + +#: crafts.lua +msgid "Leather" +msgstr "Deri" + +#: crafts.lua +msgid "Raw Meat" +msgstr "Çiğ et" + +#: crafts.lua +msgid "Meat" +msgstr "Et" + +#: crafts.lua +#, fuzzy +msgid "Lasso (right-click animal to put in inventory)" +msgstr "Kement (hayvana sağ tıklayarak envantere koy)" + +#: crafts.lua +msgid "Net (right-click animal to put in inventory)" +msgstr "Ağ (hayvana sağ tıklayarak envantere koy)" + +#: crafts.lua +msgid "Steel Shears (right-click to shear)" +msgstr "Çelik makas (sağ tıklayarak kes)" + +#: crafts.lua +msgid "Mob Protection Rune" +msgstr "" + +#: crafts.lua +msgid "Saddle" +msgstr "" + +#: crafts.lua +msgid "Mob Fence" +msgstr "Canavar Yaratıcı" + +#: spawner.lua +msgid "Mob Spawner" +msgstr "Canavar Yaratıcı" + +#: spawner.lua +msgid "Mob MinLight MaxLight Amount PlayerDist" +msgstr "Mob MinIşık MaxIşık Miktar OyuncuMesafesi" + +#: spawner.lua +msgid "Spawner Not Active (enter settings)" +msgstr "Yaratıcı aktif değil (ayarlara gir)" + +#: spawner.lua +msgid "Spawner Active (@1)" +msgstr "Yaratıcı aktif (@1)" + +#: spawner.lua +msgid "Mob Spawner settings failed!" +msgstr "Yaratıcı ayarları uygulanamadı." + +#: spawner.lua +#, fuzzy +msgid "" +"Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] " +"distance[1-20] y_offset[-10 to 10]”" +msgstr "" +"> isim min_isik[0-14] max_isik[0-14] alandaki_max_canavar_sayisi[kapatmak " +"icin 0] mesafe[1-20] y_cikinti[-10 ve 10 arası]" diff --git a/mods/mobs/locale/zh_CN.po b/mods/mobs/locale/zh_CN.po new file mode 100644 index 0000000..bd8ffa0 --- /dev/null +++ b/mods/mobs/locale/zh_CN.po @@ -0,0 +1,130 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# IFRFSX<1079092922@qq.com>, 2020. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-02 16:48+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: api.lua +msgid "** Peaceful Mode Active - No Monsters Will Spawn" +msgstr "** 和平模式已激活——没有怪物会产生" + +#: api.lua +msgid "Mob has been protected!" +msgstr "Mob 已经被保护了!" + +#: api.lua +msgid "@1 (Tamed)" +msgstr "@1(已驯服)" + +#: api.lua +msgid "Not tamed!" +msgstr "没有驯服!" + +#: api.lua +msgid "@1 is owner!" +msgstr "@1 是主人" + +#: api.lua +msgid "Missed!" +msgstr "没抓住!" + +#: api.lua +msgid "Already protected!" +msgstr "已经被保护!" + +#: api.lua +msgid "@1 at full health (@2)" +msgstr "@1已经满血(@2)" + +#: api.lua +msgid "@1 has been tamed!" +msgstr "@1已经被驯服!" + +#: api.lua +msgid "Enter name:" +msgstr "输入名称:" + +#: api.lua +msgid "Rename" +msgstr "重新命名" + +#: crafts.lua +msgid "Name Tag" +msgstr "名称标签" + +#: crafts.lua +msgid "Leather" +msgstr "皮革" + +#: crafts.lua +msgid "Raw Meat" +msgstr "生肉" + +#: crafts.lua +msgid "Meat" +msgstr "肉" + +#: crafts.lua +msgid "Lasso (right-click animal to put in inventory)" +msgstr "套索(右键单击动物以放入物品栏)" + +#: crafts.lua +msgid "Net (right-click animal to put in inventory)" +msgstr "网(右键单击动物以放入物品栏)" + +#: crafts.lua +msgid "Steel Shears (right-click to shear)" +msgstr "钢剪(右键单击以剪切)" + +#: crafts.lua +msgid "Mob Protection Rune" +msgstr "Mob 保护符文" + +#: crafts.lua +msgid "Saddle" +msgstr "鞍" + +#: crafts.lua +msgid "Mob Fence" +msgstr "Mob 栅栏" + +#: spawner.lua +msgid "Mob Spawner" +msgstr "Mob 孵化器" + +#: spawner.lua +msgid "Mob MinLight MaxLight Amount PlayerDist" +msgstr "Mob/最小光量/最大光量/玩家距离" + +#: spawner.lua +msgid "Spawner Not Active (enter settings)" +msgstr "孵化器未使用(输入设置)" + +#: spawner.lua +msgid "Spawner Active (@1)" +msgstr "孵化器正在运转(@1)" + +#: spawner.lua +msgid "Mob Spawner settings failed!" +msgstr "Mob 孵化器设置失败!" + +#: spawner.lua +msgid "" +"Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] " +"distance[1-20] y_offset[-10 to 10]”" +msgstr "" +"语法: “物品名称 最小光亮[0-14] 最大光亮[0-14] 范围内的最大Mob数量[0 to disable] " +"距离[1-20] y_offset[-10 to 10]”" diff --git a/mods/mobs/locale/zh_TW.po b/mods/mobs/locale/zh_TW.po new file mode 100644 index 0000000..6350a63 --- /dev/null +++ b/mods/mobs/locale/zh_TW.po @@ -0,0 +1,130 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# IFRFSX<1079092922@qq.com>, 2020. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-02 16:48+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: api.lua +msgid "** Peaceful Mode Active - No Monsters Will Spawn" +msgstr "** 和平模式已激活——沒有怪物會產生" + +#: api.lua +msgid "Mob has been protected!" +msgstr "Mob 已經被保護了!" + +#: api.lua +msgid "@1 (Tamed)" +msgstr "@1(已馴服)" + +#: api.lua +msgid "Not tamed!" +msgstr "沒有馴服!" + +#: api.lua +msgid "@1 is owner!" +msgstr "@1 是主人" + +#: api.lua +msgid "Missed!" +msgstr "沒抓住!" + +#: api.lua +msgid "Already protected!" +msgstr "已經被保護!" + +#: api.lua +msgid "@1 at full health (@2)" +msgstr "@1已經滿血(@2)" + +#: api.lua +msgid "@1 has been tamed!" +msgstr "@1已經被馴服!" + +#: api.lua +msgid "Enter name:" +msgstr "輸入名稱:" + +#: api.lua +msgid "Rename" +msgstr "重新命名" + +#: crafts.lua +msgid "Name Tag" +msgstr "名稱標籤" + +#: crafts.lua +msgid "Leather" +msgstr "皮革" + +#: crafts.lua +msgid "Raw Meat" +msgstr "生肉" + +#: crafts.lua +msgid "Meat" +msgstr "肉" + +#: crafts.lua +msgid "Lasso (right-click animal to put in inventory)" +msgstr "套索(右鍵單擊動物以放入物品欄)" + +#: crafts.lua +msgid "Net (right-click animal to put in inventory)" +msgstr "網(右鍵單擊動物以放入物品欄)" + +#: crafts.lua +msgid "Steel Shears (right-click to shear)" +msgstr "鋼剪(右鍵單擊以剪切)" + +#: crafts.lua +msgid "Mob Protection Rune" +msgstr "Mob 保護符文" + +#: crafts.lua +msgid "Saddle" +msgstr "鞍" + +#: crafts.lua +msgid "Mob Fence" +msgstr "Mob 柵欄" + +#: spawner.lua +msgid "Mob Spawner" +msgstr "Mob 孵化器" + +#: spawner.lua +msgid "Mob MinLight MaxLight Amount PlayerDist" +msgstr "Mob/最小光量/最大光量/玩家距離" + +#: spawner.lua +msgid "Spawner Not Active (enter settings)" +msgstr "孵化器未使用(輸入設置)" + +#: spawner.lua +msgid "Spawner Active (@1)" +msgstr "孵化器正在運轉(@1)" + +#: spawner.lua +msgid "Mob Spawner settings failed!" +msgstr "Mob 孵化器設置失敗!" + +#: spawner.lua +msgid "" +"Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] " +"distance[1-20] y_offset[-10 to 10]”" +msgstr "" +"語法: “物品名稱 最小光亮[0-14] 最大光亮[0-14] 範圍內的最大Mob數量[0 to disable] " +"距離[1-20] y_offset[-10 to 10]”" diff --git a/mods/mobs/lucky_block.lua b/mods/mobs/lucky_block.lua new file mode 100644 index 0000000..b823e83 --- /dev/null +++ b/mods/mobs/lucky_block.lua @@ -0,0 +1,18 @@ + +if minetest.get_modpath("lucky_block") then + + lucky_block:add_blocks({ + {"dro", {"mobs:meat_raw"}, 5}, + {"dro", {"mobs:meat"}, 5}, + {"dro", {"mobs:nametag"}, 1}, + {"dro", {"mobs:leather"}, 5}, + {"dro", {"default:stick"}, 10}, + {"dro", {"mobs:net"}, 1}, + {"dro", {"mobs:lasso"}, 1}, + {"dro", {"mobs:shears"}, 1}, + {"dro", {"mobs:protector"}, 1}, + {"dro", {"mobs:fence_wood"}, 10}, + {"dro", {"mobs:fence_top"}, 12}, + {"lig"} + }) +end diff --git a/mods/mobs/mod.conf b/mods/mobs/mod.conf new file mode 100644 index 0000000..f4632c5 --- /dev/null +++ b/mods/mobs/mod.conf @@ -0,0 +1,4 @@ +name = mobs +depends =default +optional_depends = tnt, player_api, dye, farming, invisibility, intllib, lucky_block, cmi, toolranks, pathfinder, mtobjid +description = MOBS api for mobs to add animals or monsters etc diff --git a/mods/mobs/mount.lua b/mods/mobs/mount.lua new file mode 100644 index 0000000..3341f65 --- /dev/null +++ b/mods/mobs/mount.lua @@ -0,0 +1,538 @@ +-- lib_mount by Blert2112 (edited by TenPlus1) + +--[[ one of these is needed (for now) to ride mobs, otherwise no riding for you +if not minetest.get_modpath("default") +or not minetest.get_modpath("player_api") then + + function mobs.attach() end + function mobs.detach() end + function mobs.fly() end + function mobs.drive() end + + return +end +]] + +local is_pa = minetest.get_modpath("player_api") -- 5.x compatibility +local is_mc2 = minetest.get_modpath("mcl_mobs") -- MineClone2 compatibility + +-- are we a real player ? +local function is_player(player) + + if player then + if type(player) == "userdata" then return true end + end + return false +end + + +local abs, cos, floor, sin, sqrt, pi = + math.abs, math.cos, math.floor, math.sin, math.sqrt, math.pi + +-- +-- Helper functions +-- + +local node_ok = function(pos, fallback) + + fallback = fallback or mobs.fallback_node + + local node = minetest.get_node_or_nil(pos) + + if node and minetest.registered_nodes[node.name] then + return node + end + + return {name = fallback} +end + + +local function node_is(pos) + + local node = node_ok(pos) + + if node.name == "air" then + return "air" + end + + if minetest.get_item_group(node.name, "lava") ~= 0 then + return "lava" + end + + if minetest.get_item_group(node.name, "liquid") ~= 0 then + return "liquid" + end + + if minetest.registered_nodes[node.name].walkable == true then + return "walkable" + end + + return "other" +end + + +local function get_sign(i) + + i = i or 0 + + if i == 0 then + return 0 + else + return i / abs(i) + end +end + + +local function get_velocity(v, yaw, y) + + local x = -sin(yaw) * v + local z = cos(yaw) * v + + return {x = x, y = y, z = z} +end + + +local function get_v(v) + return sqrt(v.x * v.x + v.z * v.z) +end + + +local function force_detach(player) + + if not is_player(player) then return end + + local attached_to = player:get_attach() + + if not attached_to then + return + end + + local entity = attached_to:get_luaentity() + + if entity and entity.driver and entity.driver == player then + entity.driver = nil + end + + player:set_detach() + + local name = player:get_player_name() + + if is_pa then + player_api.player_attached[name] = false + player_api.set_animation(player, "stand", 30) + elseif is_mc2 then + mcl_player.player_attached[player:get_player_name()] = false + mcl_player.player_set_animation(player, "stand", 30) + else + default.player_attached[name] = false + default.player_set_animation(player, "stand", 30) + end + + player:set_eye_offset({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0}) + player:set_properties({visual_size = {x = 1, y = 1}}) +end + + +minetest.register_on_leaveplayer(function(player) + force_detach(player) +end) + + +minetest.register_on_shutdown(function() + + local players = minetest.get_connected_players() + + for i = 1, #players do + force_detach(players[i]) + end +end) + + +minetest.register_on_dieplayer(function(player) + force_detach(player) + return true +end) + + +-- Just for correct detaching +local function find_free_pos(pos) + + local check = { + {x = 1, y = 0, z = 0}, + {x = 1, y = 1, z = 0}, + {x = -1, y = 0, z = 0}, + {x = -1, y = 1, z = 0}, + {x = 0, y = 0, z = 1}, + {x = 0, y = 1, z = 1}, + {x = 0, y = 0, z = -1}, + {x = 0, y = 1, z = -1} + } + + for _, c in pairs(check) do + + local npos = {x = pos.x + c.x, y = pos.y + c.y, z = pos.z + c.z} + local node = minetest.get_node_or_nil(npos) + + if node and node.name then + + local def = minetest.registered_nodes[node.name] + + if def and not def.walkable and def.liquidtype == "none" then + return npos + end + end + end + + return pos +end + + +function mobs.attach(entity, player) + + if not is_player(player) then return end + + entity.player_rotation = entity.player_rotation or {x = 0, y = 0, z = 0} + entity.driver_attach_at = entity.driver_attach_at or {x = 0, y = 0, z = 0} + entity.driver_eye_offset = entity.driver_eye_offset or {x = 0, y = 0, z = 0} + entity.driver_scale = entity.driver_scale or {x = 1, y = 1} + + local rot_view = 0 + + if entity.player_rotation.y == 90 then + rot_view = pi / 2 + end + + local attach_at = entity.driver_attach_at + local eye_offset = entity.driver_eye_offset + + entity.driver = player + + force_detach(player) + + if is_pa then + player_api.player_attached[player:get_player_name()] = true + elseif is_mc2 then + mcl_player.player_attached[player:get_player_name()] = true + else + default.player_attached[player:get_player_name()] = true + end + + player:set_attach(entity.object, "", attach_at, entity.player_rotation) + player:set_eye_offset(eye_offset, {x = 0, y = 0, z = 0}) + + player:set_properties({ + visual_size = { + x = entity.driver_scale.x, + y = entity.driver_scale.y + } + }) + + minetest.after(0.2, function() + + if is_player(player) then + + if is_pa then + player_api.set_animation(player, "sit", 30) + elseif is_mc2 then + mcl_player.player_set_animation(player, "sit_mount" , 30) + else + default.player_set_animation(player, "sit", 30) + end + end + end) + + player:set_look_horizontal(entity.object:get_yaw() - rot_view) +end + + +function mobs.detach(player) + + force_detach(player) + + minetest.after(0.1, function() + + if is_player(player) then + + local pos = find_free_pos(player:get_pos()) + + pos.y = pos.y + 0.5 + + player:set_pos(pos) + end + end) +end + + +function mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime) + + local yaw = entity.object:get_yaw() or 0 + local rot_view = 0 + + if entity.player_rotation.y == 90 then + rot_view = pi / 2 + end + + local acce_y = 0 + local velo = entity.object:get_velocity() ; if not velo then return end + + entity.v = get_v(velo) * get_sign(entity.v) + + -- process controls + if entity.driver then + + local ctrl = entity.driver:get_player_control() + + if ctrl.up then -- move forwards + + entity.v = entity.v + entity.accel * dtime + + elseif ctrl.down then -- move backwards + + if entity.max_speed_reverse == 0 and entity.v == 0 then + return + end + + entity.v = entity.v - entity.accel * dtime + end + + -- mob rotation + local horz + + if entity.alt_turn == true then + + horz = yaw + + if ctrl.left then + horz = horz + 0.05 + + elseif ctrl.right then + horz = horz - 0.05 + end + else + horz = entity.driver:get_look_horizontal() or 0 + end + + entity.object:set_yaw(horz - entity.rotate) + + if can_fly then + + if ctrl.jump then -- fly up + + velo.y = velo.y + 1 + + if velo.y > entity.accel then velo.y = entity.accel end + + elseif velo.y > 0 then + + velo.y = velo.y - dtime + + if velo.y < 0 then velo.y = 0 end + end + + if ctrl.sneak then -- fly down + + velo.y = velo.y - 1 + + if velo.y < -entity.accel then velo.y = -entity.accel end + + elseif velo.y < 0 then + + velo.y = velo.y + dtime + + if velo.y > 0 then velo.y = 0 end + end + else + if ctrl.jump then -- jump + + if velo.y == 0 then + velo.y = velo.y + entity.jump_height + acce_y = acce_y + (acce_y * 3) + 1 + end + end + end + end + + -- if not moving then set animation and return + if entity.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then + + if stand_anim then + mobs:set_animation(entity, stand_anim) + end + + return + end + + -- set moving animation + if moving_anim then + mobs:set_animation(entity, moving_anim) + end + + -- Stop! + local s = get_sign(entity.v) + + entity.v = entity.v - 0.02 * s + + if s ~= get_sign(entity.v) then + + entity.object:set_velocity({x = 0, y = 0, z = 0}) + entity.v = 0 + + return + end + + -- enforce speed limit forward and reverse + if entity.v > entity.max_speed_forward then + entity.v = entity.max_speed_forward + elseif entity.v < -entity.max_speed_reverse then + entity.v = -entity.max_speed_reverse + end + + -- Set position, velocity and acceleration + local p = entity.object:get_pos() + + if not p then return end + + local new_acce = {x = 0, y = entity.fall_speed, z = 0} + + p.y = p.y - 0.5 + + local ni = node_is(p) + local v = entity.v + + if ni == "air" then + + if can_fly == true then + new_acce.y = 0 + end + + elseif ni == "liquid" or ni == "lava" then + + if ni == "lava" and entity.lava_damage ~= 0 then + + entity.lava_counter = (entity.lava_counter or 0) + dtime + + if entity.lava_counter > 1 then + + minetest.sound_play("default_punch", { + object = entity.object, + max_hear_distance = 5 + }, true) + + entity.object:punch(entity.object, 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = entity.lava_damage} + }, nil) + + entity.lava_counter = 0 + end + end + + local terrain_type = entity.terrain_type + + if terrain_type == 2 or terrain_type == 3 then + + new_acce.y = 0 + p.y = p.y + 1 + + if node_is(p) == "liquid" then + + if velo.y >= 5 then + velo.y = 5 + elseif velo.y < 0 then + new_acce.y = 20 + else + new_acce.y = 5 + end + else + if abs(velo.y) < 1 then + + local pos = entity.object:get_pos() + + if not pos then return end + + pos.y = floor(pos.y) + 0.5 + entity.object:set_pos(pos) + velo.y = 0 + end + end + else + v = v * 0.25 + end + end + + local new_velo = get_velocity(v, yaw - rot_view, velo.y) + + new_acce.y = new_acce.y + acce_y + + entity.object:set_velocity(new_velo) + entity.object:set_acceleration(new_acce) + + entity.v2 = v +end + + +-- directional flying routine by D00Med (edited by TenPlus1) +function mobs.fly(entity, _, speed, shoots, arrow, moving_anim, stand_anim) + + local ctrl = entity.driver:get_player_control() ; if not ctrl then return end + local velo = entity.object:get_velocity() ; if not velo then return end + local dir = entity.driver:get_look_dir() + local yaw = entity.driver:get_look_horizontal() + 1.57 + + if ctrl.up then + + entity.object:set_velocity({ + x = dir.x * speed, + y = dir.y * speed + 2, + z = dir.z * speed + }) + + elseif ctrl.down then + + entity.object:set_velocity({ + x = -dir.x * speed, + y = dir.y * speed + 2, + z = -dir.z * speed + }) + + elseif not ctrl.down or ctrl.up or ctrl.jump then + entity.object:set_velocity({x = 0, y = -2, z = 0}) + end + + entity.object:set_yaw(yaw + pi + pi / 2 - entity.rotate) + + -- firing arrows + if ctrl.LMB and ctrl.sneak and shoots then + + local pos = entity.object:get_pos() + local obj = minetest.add_entity({ + x = pos.x + 0 + dir.x * 2.5, + y = pos.y + 1.5 + dir.y, + z = pos.z + 0 + dir.z * 2.5}, arrow) + + local ent = obj:get_luaentity() + + if ent then + + ent.switch = 1 -- for mob specific arrows + ent.owner_id = tostring(entity.object) -- so arrows dont hurt entity you are riding + + local vec = {x = dir.x * 6, y = dir.y * 6, z = dir.z * 6} + + yaw = entity.driver:get_look_horizontal() + + obj:set_yaw(yaw + pi / 2) + obj:set_velocity(vec) + else + obj:remove() + end + end + + -- change animation if stopped + if velo.x == 0 and velo.y == 0 and velo.z == 0 then + mobs:set_animation(entity, stand_anim) + else + -- moving animation + mobs:set_animation(entity, moving_anim) + end +end diff --git a/mods/mobs/readme.MD b/mods/mobs/readme.MD new file mode 100644 index 0000000..803df73 --- /dev/null +++ b/mods/mobs/readme.MD @@ -0,0 +1,418 @@ +MOBS REDO for MINETEST +====================== + +This mod contains the API only for adding your own mobs into the world, so +please use the additional modpacks to add animals, monsters, and npcs. + +https://forum.minetest.net/viewtopic.php?f=11&t=9917 + +Information +----------- + +Built from PilzAdam's original Simple Mobs with additional mobs by KrupnoPavel, +Zeg9, ExeterDad and AspireMint. + +## Crafts + +- **Nametag**. Can be crafted by paper, black dye, and string. Can be used + to right-click on a tamed mob to give them a name. +- **Net**. Used to right-click tamed mobs to pick them up and place inside + inventory as a spawn egg. +- **Magic lasso**. Similar to nets but with a better chance of picking up + larger mobs. +- **Shears**. Used to right-click sheep and return 1-3 wool. +- **Protection Rune**. Protects tamed mobs from being harmed by other players. +- **Mob Fence and Fence Top**. Stops mobs escaping or glitching throughfences. + +**Lucky Blocks**: 12 + +## Changelog + +### Version 1.57 + +* Added 'injured' animation when mob hurt +* Fixed yaw clamping to stop spinning mobs +* Added 'mob_node_timer_interval' and 'mob_main_timer_interval' settings +* Added ability for mobs to die only in natural daylight +* Refactored do_jump and added get_nodes function +* Many bug fixes and tweaks to improve performance +* Added 'mobs_attack_creatura' setting so that monsters can attack Creatura mobs +* Nodes can be added to 'runaway_from' table +* Better Mineclone2 compatibility with api, items and recipes +* Added 'mob_log_spawn' setting to log spawning of mobs and position + +### Version 1.56 + +* Added `arrow_override` function to mob definition to tweak arrow entity settings +* Added injured animation and mob hit effect +* Tamed monsters no longer despawn when outside loaded map area +* `looting_level` can be read from tool definition or tool meta to add extra + drops when mob killed + +### Version 1.55 + +* Added `peaceful_player` privilege and setting so mobs don't attack specific + players (thanks sfence) +* Added support for MarkBu's `pathfinder` mod, remove need for default mod + +### Version 1.54 + +* **New support for swimming mobs** + - `on_flop` (for mobs not in water) + - `air_damage` added +* Added editable settings (thanks Wuzzy) +* Simplified animal breeding function +* Child mobs now take twenty minutes to grow up +* Reverted to simple mob spawning with setting to use area checks + +### Version 1.53 + +* Added `on_map_load` settings to `mobs:spawn` so that mobs will only spawn + when new areas of map are loaded. + +### Version 1.52 + +* Added `mob_active_limit` in settings to set number of mobs in game. The + default is 0, for unlimited mobs. +* Removed `{immortal}` from mob armor +* Fluid viscocity slows mobs (for example, water) + +### Version 1.51 + +* Added node checks for dangerous nodes +* Add `mob_nospawn_range` setting +* Jumping and falling tweaks +* Spawn area check (thanks for idea wuzzy) +* Re-enabled mob suffocation + +### Version 1.50 + +* Added new `line_of_sight` function that uses raycasting if Minetest 5.0 is + found, (thanks Astrobe) +* Added Chinese local +* Removed ability to spawn mobs if world anchor nearby (`technic` or + `simple_anchor` mods) + +### Version 1.49 + +* Added `mobs:force_capture(self, player)` function +* API functions now use metatables thanks to bell07 + +### Version 1.48 + +* Added `mobs:set_velocity(self, velocity)` global function + +### Version 1.47 + +* Added minimum and maximum light level for damage +* Mob damage changes +* Ignition sources checked for lava damage + +### Version 1.46 + +* Mobs only drop rare items when killed by player. You can make change the + drops to rare items by using `drops.min = 0` +* Pathfinding no longer sees through walkable nodes + +### Version 1.45 + +* Added fence top to add on top of any fence to stop mobs escaping +* New `line_of_sight` tweaked by `Astrobe` + +### Version 1.44 + +* Added `ToolRanks` support for swords when attacking mobs + +### Version 1.43 + +* Added general attack function and settings +* Better Minetest 0.4.16 compatibility + +### Version 1.42 + +* Added `"all"` option to `immune_to` definition table +* Tidied floating mobs to be less intensive + +### Version 1.41 + +* Mob pathfinding has been updated thanks to `Elkien3` + +### Version 1.40 + +* Updated to use newer functions, requires Minetest 0.4.16+ to work + +### Version 1.39 + +* **New custom functions**: + - `on_breed` (called when mobs have just been bred) + - `on_grown` (called when baby mobs have grown up) + - `do_punch` (called when the mob has been punched or damaged by another mob) + +### Version 1.38 + +* Better entity checking +* Nametag setting +* `on_spawn` function added to mob registry +* Tweaked light damage + +### Version 1.37 + +* Added support for `Raymoo`'s CMI (common mob interface) mod. See + https://forum.minetest.net/viewtopic.php?f=9&t=15448 for details + +### Version 1.36 + +* Added death check. If the mob dies in fire/lava/with lava pick, then drops + are cooked + +### Version 1.35 + +* Added `owner_loyal` flag for owned mobs to attack player enemies +* Fixed `group_attack` + +### Version 1.34 + +* Added function to fly mob using directional movement (thanks D00Med for + flying code) + +### Version 1.33 + +* Added functions to mount ride mobs: + - `mobs.attach` + - `mobs.detach` + - `mobs.drive`. Many thanks to `Blert2112` + +### Version 1.32 + +* Added new spawn check to count specific mobs AND new `minetest.conf` setting + to chance spawn chance and numbers +* Added ability to protect tamed mobs + +### Version 1.31 + +* Added `attack_animals` and `specific_attack` flags for custom monster + attacks +* Added 'mob_difficulty' .conf setting to make mobs harder + +### Version 1.30 + +* Added support for `invisibility` mod +* Tweaked and tidied code + +### Version 1.29 + +* Split original Mobs Redo into a modpack to make it easier to disable mob sets + (animal, monster, npc) or simply use the API itself for your own mod + +### Version 1.28 + +* Added new damage system with ability for mob to be immune to weapons or + healed by them :) + +### Version 1.27 + +* Added new sheep, lava flan and spawn egg textures +* New Lava Pick tool smelts what you dig +* New `atan` checking function + +### Version 1.26 + +* Pathfinding feature added thanks to rnd +* When monsters attack they become scary smart in finding you :) +* Beehive produces honey now :) + +### Version 1.25 + +* Mobs no longer spawn within 12 blocks of player or despawn within same + range +* Spawners now have player detection +* Tidy and tweak code + +### Version 1.24 + +* Added feature where certain animals run away when punched + (`runaway = true` in mob definition) + +### Version 1.23 + +* Added mob spawner block for admin to setup spawners in-game (place and + right-click to enter settings) + +### Version 1.22 + +* Added ability to name tamed animals and NPCs using nametags +* NPCs will attack anyone who punches them apart from owner + +### Version 1.21 + +* Added some more error checking to reduce `serialize.h` error and added height + checks for falling off cliffs (thanks `cmdskp`) + +### Version 1.20 + +* Error checking added to remove bad mobs +* Out of map limit mobs and stop `serialize.h` error + +### Version 1.19 + +* Chickens now drop egg items instead of placing the egg +* Throwing eggs result in ⅛ chance of spawning chick + +### Version 1.18 + +* Added `docile_by_day` flag so that monsters will not attack automatically + during daylight hours unless hit first + +### Version 1.17 + +* Added `dogshoot` attack type. Mobs now shoot when out of reach +* Melee attack when in reach, also API tweaks and `self.reach` added + +### Version 1.16 + +* Mobs follow multiple items now +* NPCs can now breed + +### Version 1.15 + +* Added feeding, taming, and breeding function +* Right-click to pick up any sheep with X mark on them and replace with new one + to fix compatibility. + +### Version 1.14 + +* All variables saved in staticdata +* Fixed health bug + +### Version 1.13 + +* Added capture function (thanks `blert2112`) chance of picking up mob with a + hand, a net, or a magic lasso +* Replaced some `.x` models with newer `.b3d` ones + +### Version 1.12 + +* Added animal ownership so that players cannot steal your tamed animals + +### Version 1.11 + +* Added flying and swimming mobs +* `fly=true` and `fly_in="air"` or `"default:water_source"` for fishy + +### Version 1.10 + +* Added explosion routine for exploding mob +* Footstep removed (use replace) + +### Version 1.09 + + +* Added mob rotation value +* Added footstep feature +* Added jumping mobs with sounds feature +* Aadded magic lasso for picking up animals +* Reworked breeding routine + +### Version 1.08 + +* Added drops that appear when mob is killed +* New custom function: `on_die` function +* Mob throwing attack has been rehauled so that they can damage one another, + +### Version 1.07 + +* NPCs can now be set to follow player or stand by using `order` and `owner` + variables + +* BETA: Npc mob added. They kill monsters (maybe as guards) and attack players + when punched by them. Right-clicking them with food will heal them, and + giving them gold lump will make them drop a random item. + +### Version 1.06 + +* Changed recovery times after breeding. Time taken to grow up can be sped up + by feeding the baby animal. + +### Version 1.05 + +* Added `ExeterDad`'s bunniess which can be picked up and tamed with four carrots from `farming_redo` or `farming_plus` +* Added shears to get wool from sheep +* Added Jordach/BSD's kitten + +### Version 1.04 + +* Added mating for sheep, cows and hogs +* Added feature to feed animals to make horny and hope for a baby which is half + size, they will grow up quick though :) + +### Version 1.03 + +* Added mob drop/replace feature so that chickens can drop eggs and cow/sheep + can eat grass/wheat etc. + +### Version 1.02 + +* Sheared sheep are remembered and spawn shaven +* Warthogs will attack when threatened +* API additions + +### Version 1.01 + +* Mobs that suffer fall damage or die in water/lava/sunlight will now drop + items + +### Version 1.0 + +* More work on API so that certain mobs can float in water while some sink like + a brick :) + +### Version 0.9 + +* Spawn eggs added for all mobs (admin only, cannot be placed in protected + areas) +* Tweaked API + +### Version 0.8 + +* Added sounds to monster mobs (thanks `Cyberpangolin` for the `sfx`) +* Added chicken sound +### Version 0.7 + +* `mobs.protected` switch added to `api.lua`. When set to 1 mobs no longer + spawn in protected areas +* Minor bugfixes + +### Version 0.6 + +* API now supports multi-textured mobs, e.g oerkki, dungeon master, rats and + chickens have random skins when spawning (sheep fix TODO) +* Added new Honey block + +### Version 0.5 + +* Mobs now float in water, die from falling +* Minor code improvements + +### Version 0.4 + +* Added new sheep sound :) +* Dungeon Masters and Mese Monsters have much better aim due to `shoot_offset` +* They can both shoot through nodes that aren't walkable (flowers, grass, etc.) + +### Version 0.3 + +* Added `LOTT`'s Spider mob +* Added Cobwebs +* Added KPavel's Bee with Honey and Beehives (made texture) +* Warthogs now have sound and can be tamed +* Taming of shaved sheep or milked cow with 8 wheat so it will not despawn +* Multiple bug fixes :) + +### Version 0.2 + +* Cooking bucket of milk into cheese now returns empty bucket + +### Version 0.1 + +- Initial Release + diff --git a/mods/mobs/settingtypes.txt b/mods/mobs/settingtypes.txt new file mode 100644 index 0000000..fcec162 --- /dev/null +++ b/mods/mobs/settingtypes.txt @@ -0,0 +1,83 @@ +# Enable setting so that Mobs Redo mobs can attack Creatura mobs +mobs_attack_creatura (Attack Creatura Mobs) bool false + +# How often mobs get nodes around them (default is 0.25, 1/4 second) +mob_node_timer_interval (Mob Node Timer Interval) float 0.25 + +# How often mobs run main functions (default is 1.00, 1 second) +mob_main_timer_interval (Mob Node Timer Interval) float 1.00 + +# If false then mobs no longer spawn in world without spawner or spawn egg +mobs_spawn (Spawn Mobs) bool true + +# If enabled then monsters no longer spawn in world +only_peaceful_mobs (Only spawn peaceful Mobs) bool false + +# If enabled then punching mobs no longer shows blood effects +mobs_disable_blood (Disable Mob blood) bool false + +# If enabled the mobs will be highlighted when hit +mob_hit_effect (Highlight Mob when Hit) bool false + +# If disabled then Mobs no longer destroy world blocks +mobs_griefing (Griefing Mobs) bool true + +# If false then Mobs no longer spawn inside player protected areas +mobs_spawn_protected (Spawn Mobs in protected areas) bool true + +# If false then Monsters no longer spawn inside player protected areas +mobs_spawn_monster_protected (Spawn Monsters in protected areas) bool true + +# If true Mobs will be removed once a map chunk is out of view +remove_far_mobs (Remove far Mobs) bool true + +# Sets Mob difficulty level by multiplying punch damage +mob_difficulty (Mob difficulty) float 1.0 + +# Contains a value used to multiply Mob spawn values +mob_chance_multiplier (Mob chance multiplier) float 1.0 + +# When false Mob no longer drop items when killed +mobs_drop_items (Mob drops) bool true + +# Sets minimum distance around player that mobs cannot spawn +mob_nospawn_range (Mob no-spawn range) float 12.0 + +# Sets maximum number of active mobs in game (0 for unlimited) +mob_active_limit (Mob Active Limit) float 0 + +# Enables area check when spawning mobs +mob_area_spawn (Mob Area Spawn) bool false + +# Enable peaceful player attack prevention +enable_peaceful_player (Mobs do not attack peaceful player without reason) bool false + +# Enable mobs smooth rotation +mob_smooth_rotate (Smooth rotation for mobs) bool true + +# Fix Mob Height if too low so they cannot escape through specific nodes +mob_height_fix (Fix Mob Height) bool true + +mob_log_spawn (Log Mob Spawning) bool false + +[Pathfinding] +# Enable pathfinding (default Enabled) +mob_pathfinding_enable (Enable pathfinding) bool true +# Use pathfinder mod if available (default Enabled) +mob_pathfinder_enable (Use pathfinder mod if available) bool true +# How long before stuck mobs starts searching (default 3.0) +mob_pathfinding_stuck_timeout (How long before stuck mobs start searching) float 3.0 +# How long will mob follow path before giving up (default 5.0) +mob_pathfinding_stuck_path_timeout (How long will mob follow path before giving up) float 5.0 +# Which pathfinding algorithm to use +# - Dijkstra (default) +# - A*_noprefetch (AStar_noprefetch) +# - A* (AStar) +# (A* names differ cause Minetest doesn´t allow "*" in settings) +mob_pathfinding_algorithm (pathfinding algorithm) enum Dijkstra Dijkstra,AStar_noprefetch,AStar +# max search distance from search positions (default 16) +mob_pathfinding_searchdistance (path search distance) int 16 +# max jump height for pathfinding (default 4) +mob_pathfinding_max_jump (path max jump height) int 4 +# max drop height for pathfinding (default 6) +mob_pathfinding_max_drop (path max drop height) int 6 diff --git a/mods/mobs/sounds/mobs_punch.ogg b/mods/mobs/sounds/mobs_punch.ogg new file mode 100644 index 0000000..86cabff Binary files /dev/null and b/mods/mobs/sounds/mobs_punch.ogg differ diff --git a/mods/mobs/sounds/mobs_spell.ogg b/mods/mobs/sounds/mobs_spell.ogg new file mode 100644 index 0000000..455b54f Binary files /dev/null and b/mods/mobs/sounds/mobs_spell.ogg differ diff --git a/mods/mobs/sounds/mobs_swing.ogg b/mods/mobs/sounds/mobs_swing.ogg new file mode 100644 index 0000000..30609f0 Binary files /dev/null and b/mods/mobs/sounds/mobs_swing.ogg differ diff --git a/mods/mobs/spawner.lua b/mods/mobs/spawner.lua new file mode 100644 index 0000000..4840d1c --- /dev/null +++ b/mods/mobs/spawner.lua @@ -0,0 +1,201 @@ + +local S = mobs.intllib + + +-- are we a real player ? +local function is_player(player) + + if player and type(player) == "userdata" and minetest.is_player(player) then + return true + end +end + + +-- mob spawner +local spawner_default = "mobs_animal:pumba 10 15 0 0 0" + +minetest.register_node("mobs:spawner", { + tiles = {"mob_spawner.png"}, + drawtype = "glasslike", + paramtype = "light", + walkable = true, + description = S("Mob Spawner"), + groups = {cracky = 1}, + + on_construct = function(pos) + + local meta = minetest.get_meta(pos) + + -- setup formspec + local head = S("(mob name) (min light) (max light) (amount)" + .. " (player distance) (Y offset)") + + -- text entry formspec + meta:set_string("formspec", + "size[10,3.5]" + .. "label[0.15,0.5;" .. minetest.formspec_escape(head) .. "]" + .. "field[1,2.5;8.5,0.8;text;" .. S("Command:") + .. ";${command}]") + + meta:set_string("infotext", S("Spawner Not Active (enter settings)")) + meta:set_string("command", spawner_default) + end, + + on_right_click = function(pos, placer) + + if minetest.is_protected(pos, placer:get_player_name()) then + return + end + end, + + on_receive_fields = function(pos, formname, fields, sender) + + if not fields.text or fields.text == "" then + return + end + + local meta = minetest.get_meta(pos) + local comm = fields.text:split(" ") + local name = sender:get_player_name() + + if minetest.is_protected(pos, name) then + minetest.record_protection_violation(pos, name) + return + end + + local mob = comm[1] -- mob to spawn + local mlig = tonumber(comm[2]) -- min light + local xlig = tonumber(comm[3]) -- max light + local num = tonumber(comm[4]) -- total mobs in area + local pla = tonumber(comm[5]) -- player distance (0 to disable) + local yof = tonumber(comm[6]) or 0 -- Y offset to spawn mob + + if mob and mob ~= "" and mobs.spawning_mobs[mob] + and num and num >= 0 and num <= 10 + and mlig and mlig >= 0 and mlig <= 15 + and xlig and xlig >= 0 and xlig <= 15 + and pla and pla >= 0 and pla <= 20 + and yof and yof > -10 and yof < 10 then + + meta:set_string("command", fields.text) + meta:set_string("infotext", S("Spawner Active (@1)", mob)) + + else + minetest.chat_send_player(name, S("Mob Spawner settings failed!")) + minetest.chat_send_player(name, + S("Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”")) + end + end +}) + + +local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 99) + +-- spawner abm +minetest.register_abm({ + label = "Mob spawner node", + nodenames = {"mobs:spawner"}, + interval = 10, + chance = 4, + catch_up = false, + + action = function(pos, node, active_object_count, active_object_count_wider) + + -- return if too many entities already + if active_object_count_wider >= max_per_block then + return + end + + -- get meta and command + local meta = minetest.get_meta(pos) + local comm = meta:get_string("command"):split(" ") + + -- get settings from command + local mob = comm[1] + local mlig = tonumber(comm[2]) + local xlig = tonumber(comm[3]) + local num = tonumber(comm[4]) + local pla = tonumber(comm[5]) or 0 + local yof = tonumber(comm[6]) or 0 + + -- if amount is 0 then do nothing + if num == 0 then + return + end + + -- are we spawning a registered mob? + if not mobs.spawning_mobs[mob] then + --print ("--- mob doesn't exist", mob) + return + end + + -- check objects inside 9x9 area around spawner + local objs = minetest.get_objects_inside_radius(pos, 9) + local count = 0 + local ent + + -- count mob objects of same type in area + for _, obj in ipairs(objs) do + + ent = obj:get_luaentity() + + if ent and ent.name and ent.name == mob then + count = count + 1 + end + end + + -- is there too many of same type? + if count >= num then + return + end + + -- spawn mob if player detected and in range + if pla > 0 then + + local in_range = 0 + local objsp = minetest.get_objects_inside_radius(pos, pla) + + for _, oir in pairs(objsp) do + + if is_player(oir) then + + in_range = 1 + + break + end + end + + -- player not found + if in_range == 0 then + return + end + end + + -- set medium mob usually spawns in (defaults to air) + local reg = minetest.registered_entities[mob].fly_in + + if not reg or type(reg) == "string" then + reg = {(reg or "air")} + end + + -- find air blocks within 5 nodes of spawner + local air = minetest.find_nodes_in_area( + {x = pos.x - 5, y = pos.y + yof, z = pos.z - 5}, + {x = pos.x + 5, y = pos.y + yof, z = pos.z + 5}, reg) + + -- spawn in random air block + if air and #air > 0 then + + local pos2 = air[math.random(#air)] + local lig = minetest.get_node_light(pos2) or 0 + + pos2.y = pos2.y + 0.5 + + -- only if light levels are within range + if lig >= mlig and lig <= xlig + and minetest.registered_entities[mob] then + minetest.add_entity(pos2, mob) + end + end + end +}) diff --git a/mods/mobs/textures/00.sh b/mods/mobs/textures/00.sh new file mode 100755 index 0000000..92d9eb3 --- /dev/null +++ b/mods/mobs/textures/00.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +set -eu + +FIXED=0 + +for f in ./*.png; do + WARN="$(pngcrush -n -warn "$f" 2>&1)" + printf %s\\n "file = $f" "warn = $WARN" + case "$WARN" in + *'PCS illuminant is not D50'*|*'known incorrect sRGB profile'* ) + pngcrush -s -ow -rem allb -reduce "$f" + FIXED=$((FIXED + 1)) + ;; + esac + pngquant -v -f --ext .png --quality 0-50 --speed 1 "$f" +done + +printf %s\\n "$FIXED errors fixed" diff --git a/mods/mobs/textures/mob_spawner.png b/mods/mobs/textures/mob_spawner.png new file mode 100644 index 0000000..b3e4618 Binary files /dev/null and b/mods/mobs/textures/mob_spawner.png differ diff --git a/mods/mobs/textures/mobs_blood.png b/mods/mobs/textures/mobs_blood.png new file mode 100644 index 0000000..649e4bf Binary files /dev/null and b/mods/mobs/textures/mobs_blood.png differ diff --git a/mods/mobs/textures/mobs_chicken_egg.png b/mods/mobs/textures/mobs_chicken_egg.png new file mode 100644 index 0000000..4ef70cb Binary files /dev/null and b/mods/mobs/textures/mobs_chicken_egg.png differ diff --git a/mods/mobs/textures/mobs_chicken_egg_overlay.png b/mods/mobs/textures/mobs_chicken_egg_overlay.png new file mode 100644 index 0000000..5cc05fb Binary files /dev/null and b/mods/mobs/textures/mobs_chicken_egg_overlay.png differ diff --git a/mods/mobs/textures/mobs_leather.png b/mods/mobs/textures/mobs_leather.png new file mode 100644 index 0000000..29b1823 Binary files /dev/null and b/mods/mobs/textures/mobs_leather.png differ diff --git a/mods/mobs/textures/mobs_magic_lasso.png b/mods/mobs/textures/mobs_magic_lasso.png new file mode 100644 index 0000000..dce8823 Binary files /dev/null and b/mods/mobs/textures/mobs_magic_lasso.png differ diff --git a/mods/mobs/textures/mobs_meat.png b/mods/mobs/textures/mobs_meat.png new file mode 100644 index 0000000..4f704c0 Binary files /dev/null and b/mods/mobs/textures/mobs_meat.png differ diff --git a/mods/mobs/textures/mobs_meat_bottom.png b/mods/mobs/textures/mobs_meat_bottom.png new file mode 100644 index 0000000..96232a4 Binary files /dev/null and b/mods/mobs/textures/mobs_meat_bottom.png differ diff --git a/mods/mobs/textures/mobs_meat_raw.png b/mods/mobs/textures/mobs_meat_raw.png new file mode 100644 index 0000000..fd62670 Binary files /dev/null and b/mods/mobs/textures/mobs_meat_raw.png differ diff --git a/mods/mobs/textures/mobs_meat_raw_bottom.png b/mods/mobs/textures/mobs_meat_raw_bottom.png new file mode 100644 index 0000000..1dae3b7 Binary files /dev/null and b/mods/mobs/textures/mobs_meat_raw_bottom.png differ diff --git a/mods/mobs/textures/mobs_meat_raw_side.png b/mods/mobs/textures/mobs_meat_raw_side.png new file mode 100644 index 0000000..173a5a4 Binary files /dev/null and b/mods/mobs/textures/mobs_meat_raw_side.png differ diff --git a/mods/mobs/textures/mobs_meat_raw_top.png b/mods/mobs/textures/mobs_meat_raw_top.png new file mode 100644 index 0000000..89f1bae Binary files /dev/null and b/mods/mobs/textures/mobs_meat_raw_top.png differ diff --git a/mods/mobs/textures/mobs_meat_side.png b/mods/mobs/textures/mobs_meat_side.png new file mode 100644 index 0000000..a8ba7e1 Binary files /dev/null and b/mods/mobs/textures/mobs_meat_side.png differ diff --git a/mods/mobs/textures/mobs_meat_top.png b/mods/mobs/textures/mobs_meat_top.png new file mode 100644 index 0000000..6b04018 Binary files /dev/null and b/mods/mobs/textures/mobs_meat_top.png differ diff --git a/mods/mobs/textures/mobs_nametag.png b/mods/mobs/textures/mobs_nametag.png new file mode 100644 index 0000000..5ad99ba Binary files /dev/null and b/mods/mobs/textures/mobs_nametag.png differ diff --git a/mods/mobs/textures/mobs_net-fs8.png b/mods/mobs/textures/mobs_net-fs8.png new file mode 100644 index 0000000..24eb578 Binary files /dev/null and b/mods/mobs/textures/mobs_net-fs8.png differ diff --git a/mods/mobs/textures/mobs_net.png b/mods/mobs/textures/mobs_net.png new file mode 100644 index 0000000..24eb578 Binary files /dev/null and b/mods/mobs/textures/mobs_net.png differ diff --git a/mods/mobs/textures/mobs_noentry_particle.png b/mods/mobs/textures/mobs_noentry_particle.png new file mode 100644 index 0000000..f6e7f74 Binary files /dev/null and b/mods/mobs/textures/mobs_noentry_particle.png differ diff --git a/mods/mobs/textures/mobs_protect_particle.png b/mods/mobs/textures/mobs_protect_particle.png new file mode 100644 index 0000000..4d0f9ac Binary files /dev/null and b/mods/mobs/textures/mobs_protect_particle.png differ diff --git a/mods/mobs/textures/mobs_protector.png b/mods/mobs/textures/mobs_protector.png new file mode 100644 index 0000000..eb65080 Binary files /dev/null and b/mods/mobs/textures/mobs_protector.png differ diff --git a/mods/mobs/textures/mobs_protector2.png b/mods/mobs/textures/mobs_protector2.png new file mode 100644 index 0000000..ce48fc4 Binary files /dev/null and b/mods/mobs/textures/mobs_protector2.png differ diff --git a/mods/mobs/textures/mobs_saddle.png b/mods/mobs/textures/mobs_saddle.png new file mode 100644 index 0000000..4c80662 Binary files /dev/null and b/mods/mobs/textures/mobs_saddle.png differ diff --git a/mods/mobs/textures/mobs_shears.png b/mods/mobs/textures/mobs_shears.png new file mode 100644 index 0000000..fd94698 Binary files /dev/null and b/mods/mobs/textures/mobs_shears.png differ diff --git a/mods/mobs/textures/tnt_smoke.png b/mods/mobs/textures/tnt_smoke.png new file mode 100644 index 0000000..e093c65 Binary files /dev/null and b/mods/mobs/textures/tnt_smoke.png differ