diff --git a/mods/achievements/init.lua b/mods/achievements/init.lua index 58efbdc..7d99447 100644 --- a/mods/achievements/init.lua +++ b/mods/achievements/init.lua @@ -12,21 +12,6 @@ achievements.registered_achievements_list = {} local achievements_file = minetest.get_worldpath() .. "/achievements.dat" local saving = false -function achievements.register_achievement(name, def) - local rd = { - title = def.title or name, -- good-looking name of the achievement - description = def.description or "The " .. name .. " achievement", -- description of what the achievement is, and how to get it - times = def.times or 1, -- how many times to trigger before getting the achievement - dignode = def.dignode or nil, -- digging this node also triggers the achievement - placenode = def.placenode or nil, -- placing this node also triggers the achievement - craftitem = def.craftitem or nil, -- crafting this item also triggers the achievement - } - - achievements.registered_achievements[name] = def - - table.insert(achievements.registered_achievements_list, name) -end - local function save_achievements() local f = io.open(achievements_file, "w") @@ -57,9 +42,26 @@ local function load_achievements() end end +function achievements.register_achievement(name, def) + local rd = { + title = def.title or name, -- good-looking name of the achievement + description = def.description or "The " .. name .. " achievement", -- description of what the achievement is, and how to get it + times = def.times or 1, -- how many times to trigger before getting the achievement + dignode = def.dignode or nil, -- digging this node also triggers the achievement + placenode = def.placenode or nil, -- placing this node also triggers the achievement + craftitem = def.craftitem or nil, -- crafting this item also triggers the achievement + } + + achievements.registered_achievements[name] = def + + table.insert(achievements.registered_achievements_list, name) +end + function achievements.trigger_achievement(player, aname, times) local name = player:get_player_name() + times = times or 1 + if achievements.achievements[name] == nil then achievements.achievements[name] = {} end @@ -72,19 +74,25 @@ function achievements.trigger_achievement(player, aname, times) return end - achievements.achievements[name][aname] = achievements.achievements[name][aname] + (times or 1) + achievements.achievements[name][aname] = achievements.achievements[name][aname] + times if not achievements.registered_achievements[aname] then - default.log("[mod:achievements] Cannot find registered achievement " .. aname, "error") + default.log("[mod:achievements] Cannot find registered achievement " + .. aname, "error") return end - if achievements.achievements[name][aname] >= achievements.registered_achievements[aname].times then + if achievements.achievements[name][aname] + >= achievements.registered_achievements[aname].times then achievements.achievements[name][aname] = -1 - minetest.after(2.0, function() - minetest.chat_send_all( - minetest.colorize("#0f0", "*** " .. name .." has earned the achievement [" .. - achievements.registered_achievements[aname].title .. "]")) + minetest.after( + 2.0, + function() + minetest.chat_send_all( + minetest.colorize( + "#0f0", + "*** " .. name .." has earned the achievement [" .. + achievements.registered_achievements[aname].title .. "]")) end) end @@ -177,7 +185,8 @@ crafting.register_on_craft(on_craft) local form = default.ui.get_page("default:default") form = form .. "tableoptions[background=#DDDDDD30]" -form = form .. "tablecolumns[text,align=left,width=11;text,align=left,width=28;text,align=left,width=5]" +form = form .. "tablecolumns[text,align=left,width=11;text,align=left,width=28;" + .. "text,align=left,width=5]" default.ui.register_page("achievements:achievements", form) function achievements.get_formspec(name, row) @@ -209,13 +218,15 @@ function achievements.get_formspec(name, row) end achievement_list = achievement_list .. minetest.formspec_escape(def.title) .. "," - achievement_list = achievement_list .. minetest.formspec_escape(def.description) .. "," + achievement_list = achievement_list .. minetest.formspec_escape(def.description) + .. "," achievement_list = achievement_list .. progress end local form = default.ui.get_page("achievements:achievements") - form = form .. "table[0.25,2.5;7.75,5.5;achievement_list;" .. achievement_list .. ";" .. row .. "]" + form = form .. "table[0.25,2.5;7.75,5.5;achievement_list;" .. achievement_list + .. ";" .. row .. "]" local aname = achievements.registered_achievements_list[row] local def = achievements.registered_achievements[aname] @@ -231,7 +242,11 @@ function achievements.get_formspec(name, row) progress = "Missing" end - form = form .. "label[0.25,8.15;" .. minetest.formspec_escape(amt_gotten .. " of " .. #achievements.registered_achievements_list .. " achievements gotten, " .. amt_progress .. " in progress") .. "]" + form = form .. "label[0.25,8.15;" + .. minetest.formspec_escape(amt_gotten.. " of " + .. #achievements.registered_achievements_list + .. " achievements gotten, " .. amt_progress + .. " in progress") .. "]" form = form .. "label[0.25,0.25;" .. minetest.formspec_escape(def.title) .. "]" form = form .. "label[7.25,0.25;" .. minetest.formspec_escape(progress) .. "]" @@ -258,7 +273,11 @@ local function receive_fields(player, form_name, fields) end end - minetest.show_formspec(name, "achievements:achievements", achievements.get_formspec(name, selected)) + minetest.show_formspec( + name, + "achievements:achievements", + achievements.get_formspec(name, selected) + ) end minetest.register_on_player_receive_fields(receive_fields) diff --git a/mods/mobs/achievements.lua b/mods/mobs/achievements.lua new file mode 100644 index 0000000..bc2360c --- /dev/null +++ b/mods/mobs/achievements.lua @@ -0,0 +1,36 @@ + +-- +-- Achievements +-- + +achievements.register_achievement( + "hunter", + { + title = "Hunter", + description = "Kill 5 animals for food", + times = 5, +}) + +achievements.register_achievement( + "bomb_has_been_defused", + { + title = "Bomb has Been Defused!", + description = "Kill a Mineturtle", + times = 1, +}) + +achievements.register_achievement( + "ranger", + { + title = "Ranger", + description = "Capture a tame animal", + times = 1, +}) + +achievements.register_achievement( + "best_friends_forever", + { + title = "Best Friends Forever", + description = "Tame an animal", + times = 1, +}) diff --git a/mods/mobs/api.lua b/mods/mobs/api.lua index 653f958..0541cb6 100644 --- a/mods/mobs/api.lua +++ b/mods/mobs/api.lua @@ -1,8 +1,11 @@ + -- Mobs Api (9th August 2015) + mobs = {} mobs.mod = "redo" -- Initial settings check + local damage_enabled = minetest.setting_getbool("enable_damage") or false local peaceful_only = minetest.setting_getbool("only_peaceful_mobs") or false local enable_blood = minetest.setting_getbool("mobs_enable_blood") or false @@ -95,1182 +98,1193 @@ function mobs:register_mob(name, def) health = 0, on_replace = def.on_replace or nil, on_spawn = def.on_spawn or nil, - check_fed = function(self, name, feed_count, breed) -- check if a mob is fed - self.food = (self.food or 0) + 1 + check_fed = function(self, name, feed_count, breed) -- Check if a mob is fed + self.food = (self.food or 0) + 1 - -- make children grow quicker - if self.child == true then - self.hornytimer = self.hornytimer + 20 - return true - end + -- Make children grow quicker - -- feed and tame - if self.food >= feed_count then - self.food = 0 + if self.child == true then + self.hornytimer = self.hornytimer + 20 + return true + end - if breed and self.hornytimer == 0 then - self.horny = true - end + -- Feed and tame - self.gotten = false + if self.food >= feed_count then + self.food = 0 - if (not self.tamed) and name ~= nil and (not self.owner) or self.owner == "" then - self.owner = name - self.tamed = true - end + if breed and self.hornytimer == 0 then + self.horny = true + end - -- make sound when fed so many times - if self.sounds.random then - minetest.sound_play( - self.sounds.random, - { - object = self.object, - max_hear_distance = self.sounds.distance - }) - end - end - end, + self.gotten = false + + if (not self.tamed) and name ~= nil + and ((not self.owner) or self.owner == "") then + self.owner = name + self.tamed = true + + achievements.trigger_achievement( + minetest.get_player_by_name(name), + "best_friends_forever" + ) + end + + -- Make sound when fed so many times + + if self.sounds.random then + minetest.sound_play( + self.sounds.random, + { + object = self.object, + max_hear_distance = self.sounds.distance + }) + end + end + end, do_attack = function(self, player, dist) - if is_too_near_spawn(self.object:getpos()) then return end + if is_too_near_spawn(self.object:getpos()) then return end - if self.state ~= "attack" then - if math.random(0,100) < 90 - and self.sounds.war_cry then - minetest.sound_play( - self.sounds.war_cry, - { - object = self.object, - max_hear_distance = self.sounds.distance - }) - end - self.state = "attack" - self.attack.player = player - self.attack.dist = dist - end - end, + if self.state ~= "attack" then + if math.random(0,100) < 90 + and self.sounds.war_cry then + minetest.sound_play( + self.sounds.war_cry, + { + object = self.object, + max_hear_distance = self.sounds.distance + }) + end + self.state = "attack" + self.attack.player = player + self.attack.dist = dist + end + end, set_velocity = function(self, v) - v = (v or 0) - if def.drawtype - and def.drawtype == "side" then - self.rotate = math.rad(90) - end + v = (v or 0) + if def.drawtype + and def.drawtype == "side" then + self.rotate = math.rad(90) + end - local tmpyaw = self.object:getyaw() - local yaw = self.rotate + local tmpyaw = self.object:getyaw() + local yaw = self.rotate - if tmpyaw ~= tmpyaw then -- It's a nan value - minetest.log("warning", "[mod:mobs] object:getyaw() nan shim used") - else - yaw = yaw + tmpyaw - end + if tmpyaw ~= tmpyaw then -- It's a nan value + minetest.log("warning", "[mod:mobs] object:getyaw() nan shim used") + else + yaw = yaw + tmpyaw + end - local x = math.sin(yaw) * -v - local z = math.cos(yaw) * v + local x = math.sin(yaw) * -v + local z = math.cos(yaw) * v - self.object:setvelocity({x = x, y = self.object:getvelocity().y, z = z}) - end, + self.object:setvelocity({x = x, y = self.object:getvelocity().y, z = z}) + end, get_velocity = function(self) - local v = self.object:getvelocity() - return (v.x ^ 2 + v.z ^ 2) ^ (0.5) - end, - --[[ - in_fov = function(self,pos) - -- checks if POS is in self's FOV - local yaw = self.object:getyaw() + self.rotate - local vx = math.sin(yaw) - local vz = math.cos(yaw) - local ds = math.sqrt(vx ^ 2 + vz ^ 2) - local ps = math.sqrt(pos.x ^ 2 + pos.z ^ 2) - local d = { x = vx / ds, z = vz / ds } - local p = { x = pos.x / ps, z = pos.z / ps } - local an = ( d.x * p.x ) + ( d.z * p.z ) - - a = math.deg( math.acos( an ) ) - - if a > ( self.fov / 2 ) then - return false - else - return true - end - end,--]] - set_animation = function(self, type) - if not self.animation then - return - end - - if not self.animation.current then - self.animation.current = "" - end - - if type == "stand" - and self.animation.current ~= "stand" then - if self.animation.stand_start - and self.animation.stand_end - and self.animation.speed_normal then - self.object:set_animation({ - x = self.animation.stand_start, - y = self.animation.stand_end}, - self.animation.speed_normal, 0) - self.animation.current = "stand" - end - elseif type == "walk" - and self.animation.current ~= "walk" then - if self.animation.walk_start - and self.animation.walk_end - and self.animation.speed_normal then - self.object:set_animation({ - x = self.animation.walk_start, - y = self.animation.walk_end}, - self.animation.speed_normal, 0) - self.animation.current = "walk" - end - elseif type == "run" - and self.animation.current ~= "run" then - if self.animation.run_start - and self.animation.run_end - and self.animation.speed_run then - self.object:set_animation({ - x = self.animation.run_start, - y = self.animation.run_end}, - self.animation.speed_run, 0) - self.animation.current = "run" - end - elseif type == "punch" - and self.animation.current ~= "punch" then - if self.animation.punch_start - and self.animation.punch_end - and self.animation.speed_normal then - self.object:set_animation({ - x = self.animation.punch_start, - y = self.animation.punch_end}, - self.animation.speed_normal, 0) - self.animation.current = "punch" - end - end - end, - on_step = function(self, dtime) - - if self.type == "monster" - and peaceful_only then - self.object:remove() - return - end - - -- if lifetimer run out and not npc; tamed or attacking then remove mob - if self.type ~= "npc" - and not self.tamed then - self.lifetimer = self.lifetimer - dtime - if self.lifetimer <= 0 - and self.state ~= "attack" then - minetest.log("action","lifetimer expired, removed "..self.name) - effect(self.object:getpos(), 15, "tnt_smoke.png") - self.object:remove() - return - end - end - - -- check for mob drop/replace (used for chicken egg and sheep eating grass/wheat) - if self.replace_rate - and self.child == false - and math.random(1, self.replace_rate) == 1 then - local pos = self.object:getpos() - pos.y = pos.y + self.replace_offset - -- print ("replace node = ".. minetest.get_node(pos).name, pos.y) - if self.replace_what and self.object:getvelocity().y == 0 and #minetest.find_nodes_in_area(pos, pos, self.replace_what) > 0 then - --and self.state == "stand" then - if self.on_replace ~= nil then - self.on_replace(self, pos) - else - minetest.set_node(pos, {name = self.replace_with}) - end - end - end - - local yaw = 0 - - if not self.fly then - -- floating in water (or falling) - local pos = self.object:getpos() - local nod = minetest.get_node_or_nil(pos) - if nod then nod = nod.name else nod = "default:dirt" end - local nodef = minetest.registered_nodes[nod] - - local v = self.object:getvelocity() - if v.y > 0.1 then - self.object:setacceleration({ - x = 0, - y= self.fall_speed, - z = 0 - }) - end - if nodef.groups.water then - if self.floats == 1 then - self.object:setacceleration( - { - x = 0, - y = -self.fall_speed / (math.max(1, v.y) ^ 2), - z = 0 - }) - end - else - self.object:setacceleration( - { - x = 0, - y = self.fall_speed, - z = 0 - }) - - -- fall damage - if self.fall_damage == 1 - and self.object:getvelocity().y == 0 then - local d = (self.old_y or 0) - self.object:getpos().y - if d > 5 then - self.object:set_hp(self.object:get_hp() - math.floor(d - 5)) - effect(self.object:getpos(), 5, "tnt_smoke.png") - check_for_death(self) - end - self.old_y = self.object:getpos().y - end - end - end - - -- knockback timer - if self.pause_timer > 0 then - self.pause_timer = self.pause_timer - dtime - if self.pause_timer < 1 then - self.pause_timer = 0 - end - return - end - - -- attack timer - self.timer = self.timer + dtime - if self.state ~= "attack" then - if self.timer < 1 then - return - end - self.timer = 0 - end - - if self.sounds.random - and math.random(1, 100) <= 1 then - minetest.sound_play( - self.sounds.random, - { - object = self.object, - max_hear_distance = self.sounds.distance - }) - end - - local do_env_damage = function(self) - - local pos = self.object:getpos() - local tod = minetest.get_timeofday() - - -- daylight above ground - if self.light_damage ~= 0 - and pos.y > 0 - and tod > 0.2 - and tod < 0.8 - and (minetest.get_node_light(pos) or 0) > 12 then - self.object:set_hp(self.object:get_hp() - self.light_damage) - effect(pos, 5, "tnt_smoke.png") - if check_for_death(self) then return end - end - - pos.y = pos.y + self.collisionbox[2] -- foot level - local nod = minetest.get_node_or_nil(pos) - if not nod then return end ; -- print ("standing in "..nod.name) - local nodef = minetest.registered_nodes[nod.name] - pos.y = pos.y + 1 - - -- water - if self.water_damage ~= 0 - and nodef.groups.water then - self.object:set_hp(self.object:get_hp() - self.water_damage) - effect(pos, 5, "bubble.png") - if check_for_death(self) then return end - end - - -- lava or fire - if self.lava_damage ~= 0 - and nodef.groups.lava then - self.object:set_hp(self.object:get_hp() - self.lava_damage) - effect(pos, 5, "mobs_flame.png", 8) - if check_for_death(self) then return end - end - - end - - local do_jump = function(self) - if self.fly then - return - end - - self.jumptimer = (self.jumptimer or 0) + 1 - if self.jumptimer < 3 then - local pos = self.object:getpos() - pos.y = (pos.y + self.collisionbox[2]) - 0.2 - local nod = minetest.get_node(pos) - --print ("standing on:", nod.name, pos.y) - if not nod - or not minetest.registered_nodes[nod.name] - or minetest.registered_nodes[nod.name].walkable == false then - return - end - if self.direction then - pos.y = pos.y + 0.5 - local nod = minetest.get_node_or_nil( - { - x = pos.x + self.direction.x, - y = pos.y, - z = pos.z + self.direction.z - }) - --print ("in front:", nod.name, pos.y) - if nod and nod.name and - (nod.name ~= "air" - or self.walk_chance == 0) then - local def = minetest.registered_items[nod.name] - if (def - and def.walkable - and not nod.name:find("fence")) - or self.walk_chance == 0 then - local v = self.object:getvelocity() - v.y = self.jump_height + 1 - v.x = v.x * 2.2 - v.z = v.z * 2.2 - self.object:setvelocity(v) - if self.sounds.jump then - minetest.sound_play( - self.sounds.jump, - { - object = self.object, - max_hear_distance = self.sounds.distance - }) - end - end - end - end - else - self.jumptimer = 0 - end - end - - -- environmental damage timer - self.env_damage_timer = self.env_damage_timer + dtime - if self.state == "attack" - and self.env_damage_timer > 1 then - self.env_damage_timer = 0 - do_env_damage(self) - elseif self.state ~= "attack" then - do_env_damage(self) - end - - -- find someone to attack - if self.type == "monster" - and damage_enabled - and self.state ~= "attack" then - - local s = self.object:getpos() - local p, sp, dist - local player = nil - local type = nil - local obj = nil - local min_dist = self.view_range + 1 - local min_player = nil - - for _,oir in ipairs(minetest.get_objects_inside_radius(s, self.view_range)) do - - if oir:is_player() then - player = oir - type = "player" - else - obj = oir:get_luaentity() - if obj then - player = obj.object - type = obj.type - end - end - - if type == "player" - or type == "npc" then - s = self.object:getpos() - p = player:getpos() - sp = s - p.y = p.y + 1 - sp.y = sp.y + 1 -- aim higher to make looking up hills more realistic - dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - if dist < self.view_range then - -- and self.in_fov(self,p) then - -- choose closest player to attack - if minetest.line_of_sight(sp, p, 2) == true - and dist < min_dist then - min_dist = dist - min_player = player - end - end - end - end - -- attack player - if min_player then - self.do_attack(self, min_player, min_dist) - end - end - - -- npc, find closest monster to attack - local min_dist = self.view_range + 1 - local min_player = nil - - if self.type == "npc" - and self.attacks_monsters - and self.state ~= "attack" then - local s = self.object:getpos() - local obj = nil - for _, oir in pairs(minetest.get_objects_inside_radius(s,self.view_range)) do - obj = oir:get_luaentity() - if obj - and obj.type == "monster" then - -- attack monster - p = obj.object:getpos() - dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - if dist < min_dist then - min_dist = dist - min_player = obj.object - end - end - end - if min_player then - self.do_attack(self, min_player, min_dist) - end - end - - -- horny animal can mate for 40 seconds, afterwards horny animal cannot mate again for 200 seconds - if self.horny == true - and self.hornytimer < 240 - and self.child == false then - self.hornytimer = self.hornytimer + 1 - if self.hornytimer >= 240 then - self.hornytimer = 0 - self.horny = false - end - end - - -- if animal is child take 240 seconds before growing into adult - if self.child == true then - self.hornytimer = self.hornytimer + 1 - if self.hornytimer > 240 then - self.child = false - self.hornytimer = 0 - self.object:set_properties( - { - textures = self.base_texture, - mesh = self.base_mesh, - visual_size = self.base_size, - collisionbox = self.base_colbox, - }) - end - end - - -- if animal is horny, find another same animal who is horny and mate - if self.horny == true - and self.hornytimer <= 40 then - local pos = self.object:getpos() - effect({x = pos.x, y = pos.y + 0.5, z = pos.z}, 4, "heart.png") - local ents = minetest.get_objects_inside_radius(pos, self.view_range) - local num = 0 - local ent = nil - for i,obj in ipairs(ents) do - ent = obj:get_luaentity() - - -- check for same animal with different colour - local canmate = false - if ent then - if ent.name == self.name then - canmate = true - else - local entname = string.split(ent.name,":") - local selfname = string.split(self.name,":") - if entname[1] == selfname[1] then - entname = string.split(entname[2],"_") - selfname = string.split(selfname[2],"_") - if entname[1] == selfname[1] then - canmate = true - end - end - end - end - - if ent - and canmate == true - and ent.horny == true - and ent.hornytimer <= 40 then - num = num + 1 - end - if num > 1 then - self.hornytimer = 41 - ent.hornytimer = 41 - minetest.after( - 7, - function(dtime) - local mob = minetest.add_entity(pos, self.name) - local ent2 = mob:get_luaentity() - local textures = self.base_texture - if def.child_texture then - textures = def.child_texture[1] - end - mob:set_properties({ - textures = textures, - visual_size = { - x = self.base_size.x / 2, - y = self.base_size.y / 2 - }, - collisionbox = { - self.base_colbox[1] / 2, - self.base_colbox[2] / 2, - self.base_colbox[3] / 2, - self.base_colbox[4] / 2, - self.base_colbox[5] / 2, - self.base_colbox[6] / 2 - }, - }) - ent2.child = true - ent2.tamed = true - --ent2.following = ent -- follow mother - end) - num = 0 - break - end - end - end - - -- find player to follow - if (self.follow ~= "" or self.order == "follow") and not self.following and self.state ~= "attack" then - local s, p, dist - for _,player in pairs(minetest.get_connected_players()) do - s = self.object:getpos() - p = player:getpos() - dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - if dist < self.view_range then - self.following = player - break - end - end - end - - -- custom function (defined in mob lua file) - if self.do_custom then - self.do_custom(self) - end - - if self.type == "npc" - and self.order == "follow" - and self.state ~= "attack" then - -- npc stop following player if not owner - if self.following - and self.type == "npc" - 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 - if self.following - and self.following.is_player - and self.following:get_wielded_item():get_name() ~= self.follow then - self.following = nil - end - end - - -- follow player or mob - if self.following then - local s = self.object:getpos() - local p - - if self.following.is_player - and self.following:is_player() then - p = self.following:getpos() - elseif self.following.object then - p = self.following.object:getpos() - end - - if p then - local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - if dist > self.view_range then - self.following = nil - else - local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} - yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate - if p.x > s.x then - yaw = yaw + math.pi - end - self.object:setyaw(yaw) - - -- anyone but standing npc's can move along - if dist > 2 and self.order ~= "stand" then - if (self.jump and self.get_velocity(self) <= 0.5 and self.object:getvelocity().y == 0) or (self.object:getvelocity().y == 0 and self.jump_chance > 0) then - self.direction = { - x = math.sin(yaw) * -1, - y = -20, - z = math.cos(yaw) - } - do_jump(self) - end - self.set_velocity(self, self.walk_velocity) - if self.walk_chance ~= 0 then - self:set_animation("walk") - end - else - self.set_velocity(self, 0) - self:set_animation("stand") - end - return - end - end - else - self:set_animation("stand") - end - - if self.state == "stand" then - -- randomly turn - if math.random(1, 4) == 1 then - -- if there is a player nearby look at them - local lp = nil - local s = self.object:getpos() - - if self.type == "npc" then - local o = minetest.get_objects_inside_radius(self.object:getpos(), 3) - - local yaw = 0 - for _,o in ipairs(o) do - if o:is_player() then - lp = o:getpos() - break - end - end - end - - if lp ~= nil then - local vec = {x = lp.x - s.x, y = lp.y - s.y, z = lp.z - s.z} - yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate - if lp.x > s.x then - yaw = yaw + math.pi - end - else - yaw = self.object:getyaw() + ((math.random(0, 360) - 180) / 180 * math.pi) - end - self.object:setyaw(yaw) - end - - self.set_velocity(self, 0) - self.set_animation(self, "stand") - - -- npc's ordered to stand stay standing - if self.type == "npc" - and self.order == "stand" then - self.set_velocity(self, 0) - self.state = "stand" - self:set_animation("stand") - else - if self.walk_chance ~= 0 and math.random(1, 100) <= self.walk_chance then - self.set_velocity(self, self.walk_velocity) - self.state = "walk" - self.set_animation(self, "walk") - end - - -- jumping mobs only - -- if self.jump and math.random(1, 100) <= self.jump_chance then - -- self.direction = {x = 0, y = 0, z = 0} - -- do_jump(self) - -- self.set_velocity(self, self.walk_velocity) - -- end - end - - elseif self.state == "walk" then - local s = self.object:getpos() - local lp = minetest.find_node_near(s, 1, {"group:water"}) - - -- water swimmers cannot move out of water - if self.fly and self.fly_in == "default:water_source" and not lp then - print ("out of water") - self.set_velocity(self, 0) - self.state = "flop" -- change to undefined state so nothing more happens - self:set_animation("stand") - return - end - -- if water nearby then turn away - if lp then - local vec = {x = lp.x - s.x, y = lp.y - s.y, z = lp.z - s.z} - yaw = math.atan(vec.z / vec.x) + 3 * math.pi / 2 - self.rotate - if lp.x > s.x then - yaw = yaw + math.pi - end - self.object:setyaw(yaw) - - -- otherwise randomly turn - elseif math.random(1, 100) <= 30 then - self.object:setyaw(self.object:getyaw() + ((math.random(0, 360) - 180) / 180 * math.pi)) - end - if self.jump and self.get_velocity(self) <= 0.5 and self.object:getvelocity().y == 0 then - self.direction = { - x = math.sin(yaw) * -1, - y = -20, - z = math.cos(yaw) - } - do_jump(self) - end - - self:set_animation("walk") - self.set_velocity(self, self.walk_velocity) - if math.random(1, 100) <= 30 then - self.set_velocity(self, 0) - self.state = "stand" - self:set_animation("stand") - end - - -- exploding mobs - elseif self.state == "attack" and self.attack_type == "explode" then - if not self.attack.player - or not self.attack.player:is_player() then - self.state = "stand" - self:set_animation("stand") - self.timer = 0 - self.blinktimer = 0 - return - end - local s = self.object:getpos() - local p = self.attack.player:getpos() - local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - if dist > self.view_range or self.attack.player:get_hp() <= 0 then - self.state = "stand" - self.v_start = false - self.set_velocity(self, 0) - self.timer = 0 - self.blinktimer = 0 - self.attack = {player = nil, dist = nil} - self:set_animation("stand") - return - else - self:set_animation("walk") - self.attack.dist = dist - end - - local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} - yaw = math.atan(vec.z / vec.x) + math.pi / 2 - self.rotate - if p.x > s.x then - yaw = yaw+math.pi - end - self.object:setyaw(yaw) - if self.attack.dist > 3 then - if not self.v_start then - self.v_start = true - self.set_velocity(self, self.run_velocity) - self.timer = 0 - self.blinktimer = 0 - else - self.timer = 0 - self.blinktimer = 0 - if self.get_velocity(self) <= 0.5 and self.object:getvelocity().y == 0 then - local v = self.object:getvelocity() - v.y = 5 - self.object:setvelocity(v) - end - self.set_velocity(self, self.run_velocity) - end - self:set_animation("run") - else - self.set_velocity(self, 0) - 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:settexturemod("") - else - self.object:settexturemod("^[brighten") - end - self.blinkstatus = not self.blinkstatus - end - if self.timer > 3 then - local pos = vector.round(self.object:getpos()) - entity_physics(pos, 3) -- hurt player/mobs caught in blast area - if minetest.find_node_near(pos, 1, {"group:water"}) or minetest.is_protected(pos, "") then - self.object:remove() - if self.sounds.explode ~= "" then - minetest.sound_play(self.sounds.explode, { - pos = pos, - gain = 1.0, - max_hear_distance = 16 - }) - end - effect(pos, 15, "tnt_smoke.png", 5) - return - end - self.object:remove() - pos.y = pos.y - 1 - mobs:explosion(pos, self.explode_radius, 0, 1, self.sounds.explode) - end - end - -- end of exploding mobs - - elseif self.state == "attack" - and self.attack_type == "dogfight" then - if not self.attack.player or not self.attack.player:getpos() then - print("stop attacking") - self.state = "stand" - self:set_animation("stand") - return - end - local s = self.object:getpos() - local p = self.attack.player:getpos() - local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - - -- fly bit modified from BlockMens creatures mod - if self.fly - and dist > 2 then - - local nod = minetest.get_node_or_nil(s) - local p1 = s - local me_y = math.floor(p1.y) - local p2 = p - local p_y = math.floor(p2.y + 1) - local v = self.object:getvelocity() - if nod - and nod.name == self.fly_in then - if me_y < p_y then - self.object:setvelocity({ - x = v.x, - y = 1 * self.walk_velocity, - z = v.z - }) - elseif me_y > p_y then - self.object:setvelocity({ - x = v.x, - y = -1 * self.walk_velocity, - z = v.z - }) - end - else - if me_y < p_y then - self.object:setvelocity({ - x = v.x, - y = 0.01, - z = v.z - }) - elseif me_y > p_y then - self.object:setvelocity({ - x = v.x, - y = -0.01, - z = v.z - }) - end - end - - end - -- end fly bit - - if dist > self.view_range or self.attack.player:get_hp() <= 0 then - self.state = "stand" - self.set_velocity(self, 0) - self.attack = {player = nil, dist = nil} - self:set_animation("stand") - return - else - self.attack.dist = dist - end - - local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} - yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate - if p.x > s.x then - yaw = yaw + math.pi - end - self.object:setyaw(yaw) - -- attack distance is 2 + half of mob width so the bigger mobs can attack (like slimes) - if self.attack.dist > ((-self.collisionbox[1] + self.collisionbox[4]) / 2) + 2 then - -- jump attack - if (self.jump and self.get_velocity(self) <= 0.5 and self.object:getvelocity().y == 0) or (self.object:getvelocity().y == 0 and self.jump_chance > 0) then - self.direction = { - x = math.sin(yaw) * -1, - y = -20, - z = math.cos(yaw) - } - do_jump(self) - end - self.set_velocity(self, self.run_velocity) - self:set_animation("run") - else - self.set_velocity(self, 0) - self:set_animation("punch") - if self.timer > 1 then - self.timer = 0 - local p2 = p - local s2 = s - p2.y = p2.y + 1.5 - s2.y = s2.y + 1.5 - if minetest.line_of_sight(p2, s2) == true then - if self.sounds.attack then - minetest.sound_play( - self.sounds.attack, - { - object = self.object, - max_hear_distance = self.sounds.distance - }) - end - self.attack.player:punch( - self.object, - 1.0, - { - full_punch_interval=1.0, - damage_groups = {fleshy=self.damage} - }, vec) - if self.attack.player:get_hp() <= 0 then - self.state = "stand" - self:set_animation("stand") - end - end - end - end - - elseif self.state == "attack" - and self.attack_type == "shoot" then - - local s = self.object:getpos() - local p = self.attack.player:getpos() - if not p then - self.state = "stand" - return - end - p.y = p.y - .5 - s.y = s.y + .5 - local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - if dist > self.view_range or self.attack.player:get_hp() <= 0 then - self.state = "stand" - self.set_velocity(self, 0) - self:set_animation("stand") - return - else - self.attack.dist = dist - end - - local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} - yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate - if p.x > s.x then - yaw = yaw + math.pi - end - self.object:setyaw(yaw) - self.set_velocity(self, 0) - - if self.shoot_interval and self.timer > self.shoot_interval and math.random(1, 100) <= 60 then - self.timer = 0 - - self:set_animation("punch") - - if self.sounds.attack then - minetest.sound_play( - self.sounds.attack, - { - object = self.object, - max_hear_distance = self.sounds.distance - }) - end - - local p = self.object:getpos() - p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2 - local obj = minetest.add_entity(p, self.arrow) - local amount = (vec.x ^ 2 + vec.y ^ 2 + vec.z ^ 2) ^ 0.5 - local v = obj:get_luaentity().velocity - vec.y = vec.y + self.shoot_offset -- this makes shoot aim accurate - vec.x = vec.x *v / amount - vec.y = vec.y *v / amount - vec.z = vec.z *v / amount - obj:setvelocity(vec) - end - end - end, - - on_activate = function(self, staticdata, dtime_s) - - if self.type == "monster" - and peaceful_only then - self.object:remove() - end - - -- load entity variables - if staticdata then - local tmp = minetest.deserialize(staticdata) - if tmp then - for _,stat in pairs(tmp) do - self[_] = stat - end - end - end - - -- select random texture, set model and size - if not self.base_texture then - self.base_texture = def.textures[math.random(1, #def.textures)] - self.base_mesh = def.mesh - self.base_size = self.visual_size - self.base_colbox = self.collisionbox - 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 - - -- 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 - - -- if object is child then set half size - if self.child == true then - vis_size = { - x = self.base_size.x / 2, - y = self.base_size.y / 2 - } - if def.child_texture then - textures = def.child_texture[1] - end - colbox = { - self.base_colbox[1] / 2, - self.base_colbox[2] / 2, - self.base_colbox[3] / 2, - self.base_colbox[4] / 2, - self.base_colbox[5] / 2, - self.base_colbox[6] / 2 - } - end - - if self.health == 0 then - self.health = math.random (self.hp_min, self.hp_max) - end - - self.object:set_hp( self.health ) - self.object:set_armor_groups({fleshy = self.armor}) - self.state = "stand" - self.order = "stand" - self.following = nil - self.old_y = self.object:getpos().y - self.object:setyaw(math.random(1, 360) / 180 * math.pi) - self.sounds.distance = (self.sounds.distance or 10) - self.textures = textures - self.mesh = mesh - self.collisionbox = colbox - self.visual_size = vis_size - -- set anything changed above - self.object:set_properties(self) - - if self.on_spawn then - self:on_spawn() - end - - end, - - get_staticdata = function(self) - - -- remove mob when out of range unless tamed - if mobs.remove == true and self.remove_ok and not self.tamed then - print ("REMOVED", self.remove_ok, self.name) - self.object:remove() - end - self.remove_ok = true - self.attack = nil - - local tmp = {} - for _,stat in pairs(self) do - local t = type(stat) - if t ~= 'function' - and t ~= 'nil' - and t ~= 'userdata' then - tmp[_] = self[_] - end - end - -- print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n') - return minetest.serialize(tmp) - end, - - on_punch = function(self, hitter, tflp, tool_capabilities, dir) - -- weapon wear - local weapon = hitter:get_wielded_item() - if weapon:get_definition().tool_capabilities ~= nil then - local wear = ( (weapon:get_definition().tool_capabilities.full_punch_interval or 1.4) / 75 ) * 9000 - weapon:add_wear(wear) - hitter:set_wielded_item(weapon) - end - - -- weapon sounds - if weapon:get_definition().sounds ~= nil then - local s = math.random(0, #weapon:get_definition().sounds) - minetest.sound_play(weapon:get_definition().sounds[s], { - object=hitter, - max_hear_distance = 8 - }) - else - minetest.sound_play("default_punch", { - object = hitter, - max_hear_distance = 5 - }) - end - - -- exit here if dead - if check_for_death(self) then - return - end - - -- blood_particles - if self.blood_amount > 0 then - local pos = self.object:getpos() - pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) / 3 - if enable_blood then - effect(pos, self.blood_amount, self.blood_texture) - else - effect(pos, self.blood_amount, "default_grass_clump_tall.png") - end - end - - -- knock back effect - if self.knock_back > 0 then - local kb = self.knock_back - local r = self.recovery_time - local v = self.object:getvelocity() - if tflp < tool_capabilities.full_punch_interval then - if kb > 0 then - kb = kb * ( tflp / tool_capabilities.full_punch_interval ) - end - r = r * ( tflp / tool_capabilities.full_punch_interval ) - end - self.object:setvelocity({x = dir.x * kb,y = 0,z = dir.z * kb}) - self.pause_timer = r - end - - -- attack puncher and call other mobs for help - if self.passive == false - and not self.tamed then - if self.state ~= "attack" then - self.do_attack(self, hitter, 1) - end - -- alert others to the attack - local obj = nil - for _, oir in pairs(minetest.get_objects_inside_radius(hitter:getpos(), 5)) do - obj = oir:get_luaentity() - if obj then - if obj.group_attack == true - and obj.state ~= "attack" then - obj.do_attack(obj, hitter, 1) - end - end - end - end - end, - }) + local v = self.object:getvelocity() + return (v.x ^ 2 + v.z ^ 2) ^ (0.5) + end, + in_fov = function(self,pos) + -- checks if POS is in self's FOV + local yaw = self.object:getyaw() + self.rotate + local vx = math.sin(yaw) + local vz = math.cos(yaw) + local ds = math.sqrt(vx ^ 2 + vz ^ 2) + local ps = math.sqrt(pos.x ^ 2 + pos.z ^ 2) + local d = { x = vx / ds, z = vz / ds } + local p = { x = pos.x / ps, z = pos.z / ps } + local an = ( d.x * p.x ) + ( d.z * p.z ) + + a = math.deg( math.acos( an ) ) + + if a > ( self.fov / 2 ) then + return false + else + return true + end + end, + set_animation = function(self, type) + if not self.animation then + return + end + + if not self.animation.current then + self.animation.current = "" + end + + if type == "stand" + and self.animation.current ~= "stand" then + if self.animation.stand_start + and self.animation.stand_end + and self.animation.speed_normal then + self.object:set_animation({ + x = self.animation.stand_start, + y = self.animation.stand_end}, + self.animation.speed_normal, 0) + self.animation.current = "stand" + end + elseif type == "walk" + and self.animation.current ~= "walk" then + if self.animation.walk_start + and self.animation.walk_end + and self.animation.speed_normal then + self.object:set_animation({ + x = self.animation.walk_start, + y = self.animation.walk_end}, + self.animation.speed_normal, 0) + self.animation.current = "walk" + end + elseif type == "run" + and self.animation.current ~= "run" then + if self.animation.run_start + and self.animation.run_end + and self.animation.speed_run then + self.object:set_animation({ + x = self.animation.run_start, + y = self.animation.run_end}, + self.animation.speed_run, 0) + self.animation.current = "run" + end + elseif type == "punch" + and self.animation.current ~= "punch" then + if self.animation.punch_start + and self.animation.punch_end + and self.animation.speed_normal then + self.object:set_animation({ + x = self.animation.punch_start, + y = self.animation.punch_end}, + self.animation.speed_normal, 0) + self.animation.current = "punch" + end + end + end, + on_step = function(self, dtime) + + if self.type == "monster" + and peaceful_only then + self.object:remove() + return + end + + -- if lifetimer run out and not npc; tamed or attacking then remove mob + if self.type ~= "npc" + and not self.tamed then + self.lifetimer = self.lifetimer - dtime + if self.lifetimer <= 0 + and self.state ~= "attack" then + minetest.log("action","lifetimer expired, removed "..self.name) + effect(self.object:getpos(), 15, "tnt_smoke.png") + self.object:remove() + return + end + end + + -- check for mob drop/replace (used for chicken egg and sheep eating grass/wheat) + if self.replace_rate + and self.child == false + and math.random(1, self.replace_rate) == 1 then + local pos = self.object:getpos() + pos.y = pos.y + self.replace_offset + -- print ("replace node = ".. minetest.get_node(pos).name, pos.y) + if self.replace_what and self.object:getvelocity().y == 0 and #minetest.find_nodes_in_area(pos, pos, self.replace_what) > 0 then + --and self.state == "stand" then + if self.on_replace ~= nil then + self.on_replace(self, pos) + else + minetest.set_node(pos, {name = self.replace_with}) + end + end + end + + local yaw = 0 + + if not self.fly then + -- floating in water (or falling) + local pos = self.object:getpos() + local nod = minetest.get_node_or_nil(pos) + if nod then nod = nod.name else nod = "default:dirt" end + local nodef = minetest.registered_nodes[nod] + + local v = self.object:getvelocity() + if v.y > 0.1 then + self.object:setacceleration({ + x = 0, + y= self.fall_speed, + z = 0 + }) + end + if nodef.groups.water then + if self.floats == 1 then + self.object:setacceleration( + { + x = 0, + y = -self.fall_speed / (math.max(1, v.y) ^ 2), + z = 0 + }) + end + else + self.object:setacceleration( + { + x = 0, + y = self.fall_speed, + z = 0 + }) + + -- fall damage + if self.fall_damage == 1 + and self.object:getvelocity().y == 0 then + local d = (self.old_y or 0) - self.object:getpos().y + if d > 5 then + self.object:set_hp(self.object:get_hp() - math.floor(d - 5)) + effect(self.object:getpos(), 5, "tnt_smoke.png") + check_for_death(self) + end + self.old_y = self.object:getpos().y + end + end + end + + -- knockback timer + if self.pause_timer > 0 then + self.pause_timer = self.pause_timer - dtime + if self.pause_timer < 1 then + self.pause_timer = 0 + end + return + end + + -- attack timer + self.timer = self.timer + dtime + if self.state ~= "attack" then + if self.timer < 1 then + return + end + self.timer = 0 + end + + if self.sounds.random + and math.random(1, 100) <= 1 then + minetest.sound_play( + self.sounds.random, + { + object = self.object, + max_hear_distance = self.sounds.distance + }) + end + + local do_env_damage = function(self) + + local pos = self.object:getpos() + local tod = minetest.get_timeofday() + + -- daylight above ground + if self.light_damage ~= 0 + and pos.y > 0 + and tod > 0.2 + and tod < 0.8 + and (minetest.get_node_light(pos) or 0) > 12 then + self.object:set_hp(self.object:get_hp() - self.light_damage) + effect(pos, 5, "tnt_smoke.png") + if check_for_death(self) then return end + end + + pos.y = pos.y + self.collisionbox[2] -- foot level + local nod = minetest.get_node_or_nil(pos) + if not nod then return end ; -- print ("standing in "..nod.name) + local nodef = minetest.registered_nodes[nod.name] + pos.y = pos.y + 1 + + -- water + if self.water_damage ~= 0 + and nodef.groups.water then + self.object:set_hp(self.object:get_hp() - self.water_damage) + effect(pos, 5, "bubble.png") + if check_for_death(self) then return end + end + + -- lava or fire + if self.lava_damage ~= 0 + and nodef.groups.lava then + self.object:set_hp(self.object:get_hp() - self.lava_damage) + effect(pos, 5, "mobs_flame.png", 8) + if check_for_death(self) then return end + end + + end + + local do_jump = function(self) + if self.fly then + return + end + + self.jumptimer = (self.jumptimer or 0) + 1 + if self.jumptimer < 3 then + local pos = self.object:getpos() + pos.y = (pos.y + self.collisionbox[2]) - 0.2 + local nod = minetest.get_node(pos) + --print ("standing on:", nod.name, pos.y) + if not nod + or not minetest.registered_nodes[nod.name] + or minetest.registered_nodes[nod.name].walkable == false then + return + end + if self.direction then + pos.y = pos.y + 0.5 + local nod = minetest.get_node_or_nil( + { + x = pos.x + self.direction.x, + y = pos.y, + z = pos.z + self.direction.z + }) + --print ("in front:", nod.name, pos.y) + if nod and nod.name and + (nod.name ~= "air" + or self.walk_chance == 0) then + local def = minetest.registered_items[nod.name] + if (def + and def.walkable + and not nod.name:find("fence")) + or self.walk_chance == 0 then + local v = self.object:getvelocity() + v.y = self.jump_height + 1 + v.x = v.x * 2.2 + v.z = v.z * 2.2 + self.object:setvelocity(v) + if self.sounds.jump then + minetest.sound_play( + self.sounds.jump, + { + object = self.object, + max_hear_distance = self.sounds.distance + }) + end + end + end + end + else + self.jumptimer = 0 + end + end + + -- environmental damage timer + self.env_damage_timer = self.env_damage_timer + dtime + if self.state == "attack" + and self.env_damage_timer > 1 then + self.env_damage_timer = 0 + do_env_damage(self) + elseif self.state ~= "attack" then + do_env_damage(self) + end + + -- find someone to attack + if self.type == "monster" + and damage_enabled + and self.state ~= "attack" then + + local s = self.object:getpos() + local p, sp, dist + local player = nil + local type = nil + local obj = nil + local min_dist = self.view_range + 1 + local min_player = nil + + for _,oir in ipairs(minetest.get_objects_inside_radius(s, self.view_range)) do + + if oir:is_player() then + player = oir + type = "player" + else + obj = oir:get_luaentity() + if obj then + player = obj.object + type = obj.type + end + end + + if type == "player" + or type == "npc" then + s = self.object:getpos() + p = player:getpos() + sp = s + p.y = p.y + 1 + sp.y = sp.y + 1 -- aim higher to make looking up hills more realistic + dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 + if dist < self.view_range then + -- and self.in_fov(self,p) then + -- choose closest player to attack + if minetest.line_of_sight(sp, p, 2) == true + and dist < min_dist then + min_dist = dist + min_player = player + end + end + end + end + -- attack player + if min_player then + self.do_attack(self, min_player, min_dist) + end + end + + -- npc, find closest monster to attack + local min_dist = self.view_range + 1 + local min_player = nil + + if self.type == "npc" + and self.attacks_monsters + and self.state ~= "attack" then + local s = self.object:getpos() + local obj = nil + for _, oir in pairs(minetest.get_objects_inside_radius(s,self.view_range)) do + obj = oir:get_luaentity() + if obj + and obj.type == "monster" then + -- attack monster + p = obj.object:getpos() + dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 + if dist < min_dist then + min_dist = dist + min_player = obj.object + end + end + end + if min_player then + self.do_attack(self, min_player, min_dist) + end + end + + -- horny animal can mate for 40 seconds, afterwards horny animal cannot mate again for 200 seconds + if self.horny == true + and self.hornytimer < 240 + and self.child == false then + self.hornytimer = self.hornytimer + 1 + if self.hornytimer >= 240 then + self.hornytimer = 0 + self.horny = false + end + end + + -- if animal is child take 240 seconds before growing into adult + if self.child == true then + self.hornytimer = self.hornytimer + 1 + if self.hornytimer > 240 then + self.child = false + self.hornytimer = 0 + self.object:set_properties( + { + textures = self.base_texture, + mesh = self.base_mesh, + visual_size = self.base_size, + collisionbox = self.base_colbox, + }) + end + end + + -- if animal is horny, find another same animal who is horny and mate + if self.horny == true + and self.hornytimer <= 40 then + local pos = self.object:getpos() + effect({x = pos.x, y = pos.y + 0.5, z = pos.z}, 4, "heart.png") + local ents = minetest.get_objects_inside_radius(pos, self.view_range) + local num = 0 + local ent = nil + for i,obj in ipairs(ents) do + ent = obj:get_luaentity() + + -- check for same animal with different colour + local canmate = false + if ent then + if ent.name == self.name then + canmate = true + else + local entname = string.split(ent.name,":") + local selfname = string.split(self.name,":") + if entname[1] == selfname[1] then + entname = string.split(entname[2],"_") + selfname = string.split(selfname[2],"_") + if entname[1] == selfname[1] then + canmate = true + end + end + end + end + + if ent + and canmate == true + and ent.horny == true + and ent.hornytimer <= 40 then + num = num + 1 + end + if num > 1 then + self.hornytimer = 41 + ent.hornytimer = 41 + minetest.after( + 7, + function(dtime) + local mob = minetest.add_entity(pos, self.name) + local ent2 = mob:get_luaentity() + local textures = self.base_texture + if def.child_texture then + textures = def.child_texture[1] + end + mob:set_properties({ + textures = textures, + visual_size = { + x = self.base_size.x / 2, + y = self.base_size.y / 2 + }, + collisionbox = { + self.base_colbox[1] / 2, + self.base_colbox[2] / 2, + self.base_colbox[3] / 2, + self.base_colbox[4] / 2, + self.base_colbox[5] / 2, + self.base_colbox[6] / 2 + }, + }) + ent2.child = true + ent2.tamed = true + --ent2.following = ent -- follow mother + end) + num = 0 + break + end + end + end + + -- find player to follow + if (self.follow ~= "" or self.order == "follow") and not self.following and self.state ~= "attack" then + local s, p, dist + for _,player in pairs(minetest.get_connected_players()) do + s = self.object:getpos() + p = player:getpos() + dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 + if dist < self.view_range then + self.following = player + break + end + end + end + + -- custom function (defined in mob lua file) + if self.do_custom then + self.do_custom(self) + end + + if self.type == "npc" + and self.order == "follow" + and self.state ~= "attack" then + -- npc stop following player if not owner + if self.following + and self.type == "npc" + 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 + if self.following + and self.following.is_player + and self.following:get_wielded_item():get_name() ~= self.follow then + self.following = nil + end + end + + -- follow player or mob + if self.following then + local s = self.object:getpos() + local p + + if self.following.is_player + and self.following:is_player() then + p = self.following:getpos() + elseif self.following.object then + p = self.following.object:getpos() + end + + if p then + local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 + if dist > self.view_range then + self.following = nil + else + local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} + yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate + if p.x > s.x then + yaw = yaw + math.pi + end + self.object:setyaw(yaw) + + -- anyone but standing npc's can move along + if dist > 2 and self.order ~= "stand" then + if (self.jump and self.get_velocity(self) <= 0.5 and self.object:getvelocity().y == 0) or (self.object:getvelocity().y == 0 and self.jump_chance > 0) then + self.direction = { + x = math.sin(yaw) * -1, + y = -20, + z = math.cos(yaw) + } + do_jump(self) + end + self.set_velocity(self, self.walk_velocity) + if self.walk_chance ~= 0 then + self:set_animation("walk") + end + else + self.set_velocity(self, 0) + self:set_animation("stand") + end + return + end + end + else + self:set_animation("stand") + end + + if self.state == "stand" then + -- randomly turn + if math.random(1, 4) == 1 then + -- if there is a player nearby look at them + local lp = nil + local s = self.object:getpos() + + if self.type == "npc" then + local o = minetest.get_objects_inside_radius(self.object:getpos(), 3) + + local yaw = 0 + for _,o in ipairs(o) do + if o:is_player() then + lp = o:getpos() + break + end + end + end + + if lp ~= nil then + local vec = {x = lp.x - s.x, y = lp.y - s.y, z = lp.z - s.z} + yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate + if lp.x > s.x then + yaw = yaw + math.pi + end + else + yaw = self.object:getyaw() + ((math.random(0, 360) - 180) / 180 * math.pi) + end + self.object:setyaw(yaw) + end + + self.set_velocity(self, 0) + self.set_animation(self, "stand") + + -- npc's ordered to stand stay standing + if self.type == "npc" + and self.order == "stand" then + self.set_velocity(self, 0) + self.state = "stand" + self:set_animation("stand") + else + if self.walk_chance ~= 0 and math.random(1, 100) <= self.walk_chance then + self.set_velocity(self, self.walk_velocity) + self.state = "walk" + self.set_animation(self, "walk") + end + + -- jumping mobs only + -- if self.jump and math.random(1, 100) <= self.jump_chance then + -- self.direction = {x = 0, y = 0, z = 0} + -- do_jump(self) + -- self.set_velocity(self, self.walk_velocity) + -- end + end + + elseif self.state == "walk" then + local s = self.object:getpos() + local lp = minetest.find_node_near(s, 1, {"group:water"}) + + -- water swimmers cannot move out of water + if self.fly and self.fly_in == "default:water_source" and not lp then + print ("out of water") + self.set_velocity(self, 0) + self.state = "flop" -- change to undefined state so nothing more happens + self:set_animation("stand") + return + end + -- if water nearby then turn away + if lp then + local vec = {x = lp.x - s.x, y = lp.y - s.y, z = lp.z - s.z} + yaw = math.atan(vec.z / vec.x) + 3 * math.pi / 2 - self.rotate + if lp.x > s.x then + yaw = yaw + math.pi + end + self.object:setyaw(yaw) + + -- otherwise randomly turn + elseif math.random(1, 100) <= 30 then + self.object:setyaw(self.object:getyaw() + ((math.random(0, 360) - 180) / 180 * math.pi)) + end + if self.jump and self.get_velocity(self) <= 0.5 and self.object:getvelocity().y == 0 then + self.direction = { + x = math.sin(yaw) * -1, + y = -20, + z = math.cos(yaw) + } + do_jump(self) + end + + self:set_animation("walk") + self.set_velocity(self, self.walk_velocity) + if math.random(1, 100) <= 30 then + self.set_velocity(self, 0) + self.state = "stand" + self:set_animation("stand") + end + + -- exploding mobs + elseif self.state == "attack" and self.attack_type == "explode" then + if not self.attack.player + or not self.attack.player:is_player() then + self.state = "stand" + self:set_animation("stand") + self.timer = 0 + self.blinktimer = 0 + return + end + local s = self.object:getpos() + local p = self.attack.player:getpos() + local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 + if dist > self.view_range or self.attack.player:get_hp() <= 0 then + self.state = "stand" + self.v_start = false + self.set_velocity(self, 0) + self.timer = 0 + self.blinktimer = 0 + self.attack = {player = nil, dist = nil} + self:set_animation("stand") + return + else + self:set_animation("walk") + self.attack.dist = dist + end + + local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} + yaw = math.atan(vec.z / vec.x) + math.pi / 2 - self.rotate + if p.x > s.x then + yaw = yaw+math.pi + end + self.object:setyaw(yaw) + if self.attack.dist > 3 then + if not self.v_start then + self.v_start = true + self.set_velocity(self, self.run_velocity) + self.timer = 0 + self.blinktimer = 0 + else + self.timer = 0 + self.blinktimer = 0 + if self.get_velocity(self) <= 0.5 and self.object:getvelocity().y == 0 then + local v = self.object:getvelocity() + v.y = 5 + self.object:setvelocity(v) + end + self.set_velocity(self, self.run_velocity) + end + self:set_animation("run") + else + self.set_velocity(self, 0) + 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:settexturemod("") + else + self.object:settexturemod("^[brighten") + end + self.blinkstatus = not self.blinkstatus + end + if self.timer > 3 then + local pos = vector.round(self.object:getpos()) + entity_physics(pos, 3) -- hurt player/mobs caught in blast area + if minetest.find_node_near(pos, 1, {"group:water"}) or minetest.is_protected(pos, "") then + self.object:remove() + if self.sounds.explode ~= "" then + minetest.sound_play(self.sounds.explode, { + pos = pos, + gain = 1.0, + max_hear_distance = 16 + }) + end + effect(pos, 15, "tnt_smoke.png", 5) + return + end + + self.object:remove() + + pos.y = pos.y - 1 + + tnt.explode(pos, self.explode_radius, self.sounds.explode) + end + end + -- end of exploding mobs + + elseif self.state == "attack" + and self.attack_type == "dogfight" then + if not self.attack.player or not self.attack.player:getpos() then + print("stop attacking") + self.state = "stand" + self:set_animation("stand") + return + end + local s = self.object:getpos() + local p = self.attack.player:getpos() + local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 + + -- fly bit modified from BlockMens creatures mod + if self.fly + and dist > 2 then + + local nod = minetest.get_node_or_nil(s) + local p1 = s + local me_y = math.floor(p1.y) + local p2 = p + local p_y = math.floor(p2.y + 1) + local v = self.object:getvelocity() + if nod + and nod.name == self.fly_in then + if me_y < p_y then + self.object:setvelocity({ + x = v.x, + y = 1 * self.walk_velocity, + z = v.z + }) + elseif me_y > p_y then + self.object:setvelocity({ + x = v.x, + y = -1 * self.walk_velocity, + z = v.z + }) + end + else + if me_y < p_y then + self.object:setvelocity({ + x = v.x, + y = 0.01, + z = v.z + }) + elseif me_y > p_y then + self.object:setvelocity({ + x = v.x, + y = -0.01, + z = v.z + }) + end + end + + end + -- end fly bit + + if dist > self.view_range or self.attack.player:get_hp() <= 0 then + self.state = "stand" + self.set_velocity(self, 0) + self.attack = {player = nil, dist = nil} + self:set_animation("stand") + return + else + self.attack.dist = dist + end + + local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} + yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate + if p.x > s.x then + yaw = yaw + math.pi + end + self.object:setyaw(yaw) + -- attack distance is 2 + half of mob width so the bigger mobs can attack (like slimes) + if self.attack.dist > ((-self.collisionbox[1] + self.collisionbox[4]) / 2) + 2 then + -- jump attack + if (self.jump and self.get_velocity(self) <= 0.5 and self.object:getvelocity().y == 0) or (self.object:getvelocity().y == 0 and self.jump_chance > 0) then + self.direction = { + x = math.sin(yaw) * -1, + y = -20, + z = math.cos(yaw) + } + do_jump(self) + end + self.set_velocity(self, self.run_velocity) + self:set_animation("run") + else + self.set_velocity(self, 0) + self:set_animation("punch") + if self.timer > 1 then + self.timer = 0 + local p2 = p + local s2 = s + p2.y = p2.y + 1.5 + s2.y = s2.y + 1.5 + if minetest.line_of_sight(p2, s2) == true then + if self.sounds.attack then + minetest.sound_play( + self.sounds.attack, + { + object = self.object, + max_hear_distance = self.sounds.distance + }) + end + self.attack.player:punch( + self.object, + 1.0, + { + full_punch_interval=1.0, + damage_groups = {fleshy=self.damage} + }, vec) + if self.attack.player:get_hp() <= 0 then + self.state = "stand" + self:set_animation("stand") + end + end + end + end + + elseif self.state == "attack" + and self.attack_type == "shoot" then + + local s = self.object:getpos() + local p = self.attack.player:getpos() + if not p then + self.state = "stand" + return + end + p.y = p.y - .5 + s.y = s.y + .5 + local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 + if dist > self.view_range or self.attack.player:get_hp() <= 0 then + self.state = "stand" + self.set_velocity(self, 0) + self:set_animation("stand") + return + else + self.attack.dist = dist + end + + local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} + yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate + if p.x > s.x then + yaw = yaw + math.pi + end + self.object:setyaw(yaw) + self.set_velocity(self, 0) + + if self.shoot_interval and self.timer > self.shoot_interval and math.random(1, 100) <= 60 then + self.timer = 0 + + self:set_animation("punch") + + if self.sounds.attack then + minetest.sound_play( + self.sounds.attack, + { + object = self.object, + max_hear_distance = self.sounds.distance + }) + end + + local p = self.object:getpos() + p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2 + local obj = minetest.add_entity(p, self.arrow) + local amount = (vec.x ^ 2 + vec.y ^ 2 + vec.z ^ 2) ^ 0.5 + local v = obj:get_luaentity().velocity + vec.y = vec.y + self.shoot_offset -- this makes shoot aim accurate + vec.x = vec.x *v / amount + vec.y = vec.y *v / amount + vec.z = vec.z *v / amount + obj:setvelocity(vec) + end + end + end, + + on_activate = function(self, staticdata, dtime_s) + + if self.type == "monster" + and peaceful_only then + self.object:remove() + end + + -- load entity variables + if staticdata then + local tmp = minetest.deserialize(staticdata) + if tmp then + for _,stat in pairs(tmp) do + self[_] = stat + end + end + end + + -- select random texture, set model and size + if not self.base_texture then + self.base_texture = def.textures[math.random(1, #def.textures)] + self.base_mesh = def.mesh + self.base_size = self.visual_size + self.base_colbox = self.collisionbox + 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 + + -- 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 + + -- if object is child then set half size + if self.child == true then + vis_size = { + x = self.base_size.x / 2, + y = self.base_size.y / 2 + } + if def.child_texture then + textures = def.child_texture[1] + end + colbox = { + self.base_colbox[1] / 2, + self.base_colbox[2] / 2, + self.base_colbox[3] / 2, + self.base_colbox[4] / 2, + self.base_colbox[5] / 2, + self.base_colbox[6] / 2 + } + end + + if self.health == 0 then + self.health = math.random (self.hp_min, self.hp_max) + end + + self.object:set_hp( self.health ) + self.object:set_armor_groups({fleshy = self.armor}) + self.state = "stand" + self.order = "stand" + self.following = nil + self.old_y = self.object:getpos().y + self.object:setyaw(math.random(1, 360) / 180 * math.pi) + self.sounds.distance = (self.sounds.distance or 10) + self.textures = textures + self.mesh = mesh + self.collisionbox = colbox + self.visual_size = vis_size + -- set anything changed above + self.object:set_properties(self) + + if self.on_spawn then + self:on_spawn() + end + + end, + + get_staticdata = function(self) + + -- remove mob when out of range unless tamed + if mobs.remove == true and self.remove_ok and not self.tamed then + print ("REMOVED", self.remove_ok, self.name) + self.object:remove() + end + self.remove_ok = true + self.attack = nil + + local tmp = {} + for _,stat in pairs(self) do + local t = type(stat) + if t ~= 'function' + and t ~= 'nil' + and t ~= 'userdata' then + tmp[_] = self[_] + end + end + -- print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n') + return minetest.serialize(tmp) + end, + + on_punch = function(self, hitter, tflp, tool_capabilities, dir) + -- weapon wear + local weapon = hitter:get_wielded_item() + if weapon:get_definition().tool_capabilities ~= nil then + local wear = ( (weapon:get_definition().tool_capabilities.full_punch_interval or 1.4) / 75 ) * 9000 + weapon:add_wear(wear) + hitter:set_wielded_item(weapon) + end + + -- weapon sounds + if weapon:get_definition().sounds ~= nil then + local s = math.random(0, #weapon:get_definition().sounds) + minetest.sound_play(weapon:get_definition().sounds[s], { + object=hitter, + max_hear_distance = 8 + }) + else + minetest.sound_play("default_punch", { + object = hitter, + max_hear_distance = 5 + }) + end + + -- exit here if dead + if check_for_death(self, hitter) then + return + end + + -- blood_particles + if self.blood_amount > 0 then + local pos = self.object:getpos() + pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) / 3 + if enable_blood then + effect(pos, self.blood_amount, self.blood_texture) + else + effect(pos, self.blood_amount, "default_grass_clump_tall.png") + end + end + + -- knock back effect + if self.knock_back > 0 then + local kb = self.knock_back + local r = self.recovery_time + local v = self.object:getvelocity() + if tflp < tool_capabilities.full_punch_interval then + if kb > 0 then + kb = kb * ( tflp / tool_capabilities.full_punch_interval ) + end + r = r * ( tflp / tool_capabilities.full_punch_interval ) + end + self.object:setvelocity({x = dir.x * kb,y = 0,z = dir.z * kb}) + self.pause_timer = r + end + + -- attack puncher and call other mobs for help + if self.passive == false + and not self.tamed then + if self.state ~= "attack" then + self.do_attack(self, hitter, 1) + end + -- alert others to the attack + local obj = nil + for _, oir in pairs(minetest.get_objects_inside_radius(hitter:getpos(), 5)) do + obj = oir:get_luaentity() + if obj then + if obj.group_attack == true + and obj.state ~= "attack" then + obj.do_attack(obj, hitter, 1) + end + end + end + end + end, + }) end mobs.spawning_mobs = {} @@ -1279,66 +1293,66 @@ function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, inter mobs.spawning_mobs[name] = true minetest.register_abm( { - nodenames = nodes, - neighbors = neighbors, - interval = interval, - chance = chance, - action = function(pos, node, _, active_object_count_wider) - -- do not spawn if too many active entities in area - if active_object_count_wider > active_object_count or not mobs.spawning_mobs[name] then - --or not pos then - return - end + nodenames = nodes, + neighbors = neighbors, + interval = interval, + chance = chance, + action = function(pos, node, _, active_object_count_wider) + -- do not spawn if too many active entities in area + if active_object_count_wider > active_object_count or not mobs.spawning_mobs[name] then + --or not pos then + return + end - -- spawn above node - pos.y = pos.y + 1 + -- spawn above node + pos.y = pos.y + 1 - -- mobs cannot spawn inside protected areas if enabled - if mobs.protected == 1 - and minetest.is_protected(pos, "") then - return - end + -- mobs cannot spawn inside protected areas if enabled + if mobs.protected == 1 + and minetest.is_protected(pos, "") then + return + end - -- check if light and height levels are ok to spawn - local light = minetest.get_node_light(pos) - if not light - or light > max_light - or light < min_light - or pos.y > max_height - or pos.y < min_height then - return - end + -- check if light and height levels are ok to spawn + local light = minetest.get_node_light(pos) + if not light + or light > max_light + or light < min_light + or pos.y > max_height + or pos.y < min_height then + return + end - -- are we spawning inside a solid node? - local nod = minetest.get_node_or_nil(pos) - if not nod - or not nod.name - or not minetest.registered_nodes[nod.name] - or minetest.registered_nodes[nod.name].walkable == true then - return - end + -- are we spawning inside a solid node? + local nod = minetest.get_node_or_nil(pos) + if not nod + or not nod.name + or not minetest.registered_nodes[nod.name] + or minetest.registered_nodes[nod.name].walkable == true then + return + end - pos.y = pos.y + 1 + pos.y = pos.y + 1 - nod = minetest.get_node_or_nil(pos) - if not nod - or not nod.name - or not minetest.registered_nodes[nod.name] - or minetest.registered_nodes[nod.name].walkable == true then - return - end + nod = minetest.get_node_or_nil(pos) + if not nod + or not nod.name + or not minetest.registered_nodes[nod.name] + or minetest.registered_nodes[nod.name].walkable == true then + return + end - if minetest.setting_getbool("display_mob_spawn") then - minetest.chat_send_all("[mobs] Add "..name.." at "..minetest.pos_to_string(pos)) - end + if minetest.setting_getbool("display_mob_spawn") then + minetest.chat_send_all("[mobs] Add "..name.." at "..minetest.pos_to_string(pos)) + end - -- spawn mob half block higher - pos.y = pos.y - 0.5 - minetest.add_entity(pos, name) - --print ("Spawned "..name.." at "..minetest.pos_to_string(pos).." on "..node.name.." near "..neighbors[1]) + -- spawn mob half block higher + pos.y = pos.y - 0.5 + minetest.add_entity(pos, name) + --print ("Spawned "..name.." at "..minetest.pos_to_string(pos).." on "..node.name.." near "..neighbors[1]) - end - }) + end + }) end -- compatibility with older mob registration @@ -1350,105 +1364,34 @@ end function effect(pos, amount, texture, max_size) minetest.add_particlespawner( { - amount = amount, - time = 0.25, - minpos = pos, - maxpos = pos, - minvel = {x = -0, y = 2, z = -0}, - maxvel = {x = 2, y = 6, z = 2}, - minacc = {x = -4, y = -16, z = -4}, - maxacc = {x = 4, y = -4, z = 4}, - minexptime = 0.1, - maxexptime = 1, - minsize = 1, - maxsize = (max_size or 2), - texture = texture, - }) -end - --- explosion -function mobs:explosion(pos, radius, fire, smoke, sound) - -- node hit, bursts into flame (cannot blast through obsidian or protection redo mod items) - if not fire then fire = 0 end - if not smoke then smoke = 0 end - local pos = vector.round(pos) - local radius = 1 - local vm = VoxelManip() - local minp, maxp = vm:read_from_map(vector.subtract(pos, radius), vector.add(pos, radius)) - local a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp}) - local data = vm:get_data() - local p = {} - local c_air = minetest.get_content_id("air") - local c_ignore = minetest.get_content_id("ignore") - local c_obsidian = minetest.get_content_id("default:obsidian") - local c_brick = minetest.get_content_id("default:obsidianbrick") - local c_chest = minetest.get_content_id("default:chest_locked") - if sound - and sound ~= "" then - minetest.sound_play(sound, { - pos = pos, - gain = 1.0, - max_hear_distance = 16 - }) - end - -- if area protected then no blast damage - if minetest.is_protected(pos, "") then - return - end - for z = -radius, radius do - for y = -radius, radius do - local vi = a:index(pos.x + (-radius), pos.y + y, pos.z + z) - for x = -radius, radius do - p.x = pos.x + x - p.y = pos.y + y - p.z = pos.z + z - if data[vi] ~= c_air and data[vi] ~= c_ignore - and data[vi] ~= c_obsidian and data[vi] ~= c_brick - and data[vi] ~= c_chest then - local n = minetest.get_node(p).name - if minetest.get_item_group(n, "unbreakable") ~= 1 then - -- if chest then drop items inside - if n == "default:chest" then - local meta = minetest.get_meta(p) - local inv = meta:get_inventory() - for i = 1,32 do - local m_stack = inv:get_stack("main", i) - local obj = minetest.add_item(pos, m_stack) - if obj then - obj:setvelocity({x = math.random(-2, 2), y = 7, z = math.random(-2, 2)}) - end - end - end - if fire > 0 - and (minetest.registered_nodes[n].groups.flammable - or math.random(1, 100) <= 30) then - -- minetest.set_node(p, {name = "fire:basic_flame"}) fire mod is disabled - else - minetest.remove_node(p) - end - if smoke > 0 then - effect(p, 2, "tnt_smoke.png", 5) - end - end - end - vi = vi + 1 - end - end - end + amount = amount, + time = 0.25, + minpos = pos, + maxpos = pos, + minvel = {x = -0, y = 2, z = -0}, + maxvel = {x = 2, y = 6, z = 2}, + minacc = {x = -4, y = -16, z = -4}, + maxacc = {x = 4, y = -4, z = 4}, + minexptime = 0.1, + maxexptime = 1, + minsize = 1, + maxsize = (max_size or 2), + texture = texture, + }) end -- on mob death drop items -function check_for_death(self) +function check_for_death(self, hitter) local hp = self.object:get_hp() if hp > 0 then self.health = hp if self.sounds.damage ~= nil then - minetest.sound_play( + minetest.sound_play( self.sounds.damage, { - object = self.object, - max_hear_distance = self.sounds.distance - }) + object = self.object, + max_hear_distance = self.sounds.distance + }) end return false end @@ -1457,27 +1400,27 @@ function check_for_death(self) local obj = nil for _,drop in ipairs(self.drops) do if math.random(1, drop.chance) == 1 then - obj = minetest.add_item(pos, ItemStack(drop.name.." "..math.random(drop.min, drop.max))) - if obj then + obj = minetest.add_item(pos, ItemStack(drop.name.." "..math.random(drop.min, drop.max))) + if obj then obj:setvelocity( - { - x = math.random(-1, 1), - y = 5, - z = math.random(-1, 1) - }) - end + { + x = math.random(-1, 1), + y = 5, + z = math.random(-1, 1) + }) + end end end if self.sounds.death ~= nil then minetest.sound_play( - self.sounds.death, - { + self.sounds.death, + { object = self.object, max_hear_distance = self.sounds.distance - }) + }) end if self.on_die then - self.on_die(self, pos) + self.on_die(self, pos, hitter) end return true end @@ -1504,7 +1447,7 @@ function entity_physics(pos, radius) obj_vel = obj:getvelocity() dist = math.max(1, vector.distance(pos, obj_pos)) if obj_vel ~= nil then - obj:setvelocity(calc_velocity(pos, obj_pos, obj_vel, radius * 10)) + obj:setvelocity(calc_velocity(pos, obj_pos, obj_vel, radius * 10)) end local damage = (4 / dist) * radius obj:set_hp(obj:get_hp() - damage) @@ -1517,60 +1460,60 @@ function mobs:register_arrow(name, def) minetest.register_entity( name, { - physical = false, - visual = def.visual, - visual_size = def.visual_size, - textures = def.textures, - velocity = def.velocity, - hit_player = def.hit_player, - hit_node = def.hit_node, - hit_mob = def.hit_mob, - drop = def.drop or false, - collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows + physical = false, + visual = def.visual, + visual_size = def.visual_size, + textures = def.textures, + velocity = def.velocity, + hit_player = def.hit_player, + hit_node = def.hit_node, + hit_mob = def.hit_mob, + drop = def.drop or false, + collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows - on_step = function(self, dtime) - self.timer = (self.timer or 0) + 1 - if self.timer > 150 then self.object:remove() return end + on_step = function(self, dtime) + self.timer = (self.timer or 0) + 1 + if self.timer > 150 then self.object:remove() return end - local engage = 10 - (self.velocity / 2) -- clear entity before arrow becomes active - local pos = self.object:getpos() - local node = minetest.get_node_or_nil(self.object:getpos()) - if node then node = node.name else node = "air" end + local engage = 10 - (self.velocity / 2) -- clear entity before arrow becomes active + local pos = self.object:getpos() + local node = minetest.get_node_or_nil(self.object:getpos()) + if node then node = node.name else node = "air" end - if self.hit_node - and minetest.registered_nodes[node] - and minetest.registered_nodes[node].walkable then - self.hit_node(self, pos, node) - if self.drop == true then - pos.y = pos.y + 1 - self.lastpos = (self.lastpos or pos) - minetest.add_item(self.lastpos, self.object:get_luaentity().name) - end - self.object:remove() ; -- print ("hit node") - return - end + if self.hit_node + and minetest.registered_nodes[node] + and minetest.registered_nodes[node].walkable then + self.hit_node(self, pos, node) + if self.drop == true then + pos.y = pos.y + 1 + self.lastpos = (self.lastpos or pos) + minetest.add_item(self.lastpos, self.object:get_luaentity().name) + end + self.object:remove() ; -- print ("hit node") + return + end - if (self.hit_player or self.hit_mob) - and self.timer > engage then - for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.0)) do - if self.hit_player - and player:is_player() then - self.hit_player(self, player) - self.object:remove() ; -- print ("hit player") - return - end - if self.hit_mob - and player:get_luaentity().name ~= self.object:get_luaentity().name - and player:get_luaentity().name ~= "__builtin:item" then - self.hit_mob(self, player) - self.object:remove() ; -- print ("hit mob") - return - end - end - end - self.lastpos = pos - end - }) + if (self.hit_player or self.hit_mob) + and self.timer > engage then + for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.0)) do + if self.hit_player + and player:is_player() then + self.hit_player(self, player) + self.object:remove() ; -- print ("hit player") + return + end + if self.hit_mob + and player:get_luaentity().name ~= self.object:get_luaentity().name + and player:get_luaentity().name ~= "__builtin:item" then + self.hit_mob(self, player) + self.object:remove() ; -- print ("hit mob") + return + end + end + end + self.lastpos = pos + end + }) end -- Spawn Egg @@ -1579,100 +1522,122 @@ function mobs:register_egg(mob, desc, background) minetest.register_craftitem( mob, { - description = desc, - inventory_image = invimg, - on_place = function(itemstack, placer, pointed_thing) - local pos = pointed_thing.above - if pointed_thing.above - and not minetest.is_protected(pos, placer:get_player_name()) then - pos.y = pos.y + 0.5 - local mob = minetest.add_entity(pos, mob) - local ent = mob:get_luaentity() - if ent.type ~= "monster" then - -- set owner - ent.owner = placer:get_player_name() - ent.tamed = true - end - itemstack:take_item() - end - return itemstack - end, - }) + description = desc, + inventory_image = invimg, + on_place = function(itemstack, placer, pointed_thing) + local pos = pointed_thing.above + if pointed_thing.above + and not minetest.is_protected(pos, placer:get_player_name()) then + pos.y = pos.y + 0.5 + local mob = minetest.add_entity(pos, mob) + local ent = mob:get_luaentity() + if ent.type ~= "monster" then + -- set owner + ent.owner = placer:get_player_name() + ent.tamed = true + end + itemstack:take_item() + end + return itemstack + end, + }) end --- capture critter (thanks to blert2112 for idea) -function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith) +-- Capture critter (thanks to blert2112 for idea) + +function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, + force_take, replacewith) if clicker:is_player() and clicker:get_inventory() and not self.child then - -- get name of clicked mob local mobname = self.name - -- if not nil change what will be added to inventory + + -- If not nil change what will be added to inventory + if replacewith then - mobname = replacewith + mobname = replacewith end local name = clicker:get_player_name() - -- is mob tamed? + -- Is mob tamed? + if self.tamed == false and force_take == false then - minetest.chat_send_player(name, "Not tamed!") - return + minetest.chat_send_player(name, "Not tamed!") + + return end - -- cannot pick up if not owner + -- Cannot pick up if not owner + if self.owner ~= name and force_take == false then - minetest.chat_send_player(name, self.owner.." is owner!") - return + minetest.chat_send_player(name, self.owner .. " is owner!") + + return end if clicker:get_inventory():room_for_item("main", mobname) then - -- was mob clicked with hand, net, or lasso? - local tool = clicker:get_wielded_item() - local chance = 0 - if tool:is_empty() then + -- Was mob clicked with hand, net, or lasso? + + local tool = clicker:get_wielded_item() + local chance = 0 + + if tool:is_empty() then chance = chance_hand - elseif tool:get_name() == "mobs:net" then + 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 + elseif tool:get_name() == "mobs:lasso" then chance = chance_lasso tool:add_wear(1500) -- 43 uses clicker:set_wielded_item(tool) - end - -- return if no chance - if chance == 0 then return end - -- calculate chance.. was capture successful? - if math.random(100) <= chance then - -- successful capture.. add to inventory + end + + -- Return if no chance + + if chance == 0 then return end + + -- Calculate chance.. was capture successful? + + if math.random(100) <= chance then + -- Cuccessful capture.. add to inventory clicker:get_inventory():add_item("main", mobname) + self.object:remove() - else + + achievements.trigger_achievement(clicker, "ranger") + else minetest.chat_send_player(name, "Missed!") - end + end end end end --- feeding, taming and breeding (thanks blert2112) +-- Feeding, taming and breeding (thanks blert2112) + function mobs:feed_tame(self, clicker, feed_count, breed) - -- feed a mob(mostly a wrapper to handle self.check_fed) + -- Feed a mob(mostly a wrapper to handle self.check_fed) + local item = nil local itemstring = clicker local name = nil if type(clicker) ~= "string" then item = clicker:get_wielded_item() + itemstring = item:get_name() + name = clicker:get_player_name() end if itemstring == self.follow then if name ~= nil then - -- take item - if not minetest.setting_getbool("creative_mode") then + -- Take item + + if not minetest.setting_getbool("creative_mode") then item:take_item() + clicker:set_wielded_item(item) - end + end end self:check_fed(name, feed_count, breed) diff --git a/mods/mobs/boar.lua b/mods/mobs/boar.lua index 1f6b237..5fb56a8 100644 --- a/mods/mobs/boar.lua +++ b/mods/mobs/boar.lua @@ -45,32 +45,54 @@ mobs:register_mob( punch_start = 90, punch_end = 101, }, - on_rightclick = function(self, clicker) - mobs:feed_tame(self, clicker, 8, true) - mobs:capture_mob(self, clicker, 0, 5, 40, false, nil) - end, - }) -mobs:register_spawn("mobs:boar", {"default:dirt_with_grass"}, 20, 10, 15000, 1, 31000) + on_rightclick = function(self, clicker) + mobs:feed_tame(self, clicker, 8, true) + + mobs:capture_mob(self, clicker, 0, 5, 40, false, nil) + end, + on_die = function(self, pos, hitter) + if hitter == nil or (hitter ~= nil and not hitter:is_player()) then + return + end + + achievements.trigger_achievement(hitter, "hunter") + end, +}) + +mobs:register_spawn( + "mobs:boar", + { + "default:dirt_with_grass" + }, + 20, + 10, + 15000, + 1, + 31000 +) + mobs:register_egg("mobs:boar", "Boar", "mobs_boar_inventory.png") --- raw porkchop +-- Raw porkchop + minetest.register_craftitem( "mobs:pork_raw", { description = "Raw Porkchop", inventory_image = "mobs_pork_raw.png", on_use = minetest.item_eat({hp = 4, sat = 30}), - }) +}) + +-- Cooked porkchop --- cooked porkchop minetest.register_craftitem( "mobs:pork", { description = "Cooked Porkchop", inventory_image = "mobs_pork_cooked.png", on_use = minetest.item_eat({hp = 8, sat = 50}), - }) +}) minetest.register_craft( { @@ -78,4 +100,4 @@ minetest.register_craft( output = "mobs:pork", recipe = "mobs:pork_raw", cooktime = 5, - }) \ No newline at end of file +}) diff --git a/mods/mobs/crafts.lua b/mods/mobs/crafts.lua index 4cf7db4..188f5da 100644 --- a/mods/mobs/crafts.lua +++ b/mods/mobs/crafts.lua @@ -1,4 +1,10 @@ --- wool + +-- +-- Crafts and items +-- + +-- Wool + minetest.register_node( "mobs:wool", { @@ -7,25 +13,27 @@ minetest.register_node( is_ground_content = false, groups = {snappy = 2, oddly_breakable_by_hand = 3, fall_damage_add_percent = -25, fuzzy = 1}, sounds = default.node_sound_leaves_defaults(), - }) +}) + +-- Raw meat --- raw meat minetest.register_craftitem( "mobs:meat_raw", { description = "Raw Meat", inventory_image = "mobs_meat_raw.png", on_use = minetest.item_eat({hp = 3, sat = 30}), - }) +}) + +-- Cooked meat --- cooked meat minetest.register_craftitem( - "mobs:meat", + "mobs:meat", { description = "Cooked Meat", inventory_image = "mobs_meat_cooked.png", on_use = minetest.item_eat({hp = 7, sat = 70}), - }) +}) minetest.register_craft( { @@ -33,40 +41,40 @@ minetest.register_craft( output = "mobs:meat", recipe = "mobs:meat_raw", cooktime = 5, - }) +}) + +-- Net --- net (right click to capture animal) minetest.register_tool( "mobs:net", { description = "Net (Right-click to capture)", inventory_image = "mobs_net.png", - }) +}) -minetest.register_craft( +crafting.register_craft( { output = "mobs:net", - recipe = { - {"", "", "default:fiber"}, - {"", "default:fiber", "default:fiber"}, - {"group:stick", "", ""}, + items= { + "default:fiber 3", + "default:stick", } - }) +}) + +-- Lasso --- lasso (right click to capture animal) minetest.register_tool( "mobs:lasso", { description = "Lasso (Right-click to capture)", inventory_image = "mobs_lasso.png", - }) +}) -minetest.register_craft( +crafting.register_craft( { output = "mobs:lasso", - recipe = { - {"", "default:rope", ""}, - {"default:rope", "", "default:rope"}, - {"group:stick", "default:rope", ""}, + items = { + "default:rope 4", + "default:stick", } - }) \ No newline at end of file +}) diff --git a/mods/mobs/depends.txt b/mods/mobs/depends.txt index b7cc57f..acb3fc8 100644 --- a/mods/mobs/depends.txt +++ b/mods/mobs/depends.txt @@ -1,2 +1,3 @@ default util +achievements diff --git a/mods/mobs/init.lua b/mods/mobs/init.lua index faa743f..0768245 100644 --- a/mods/mobs/init.lua +++ b/mods/mobs/init.lua @@ -1,3 +1,4 @@ + -- -- Mobs mod -- By PilzAdam, KrupnovPavel, Zeg9, TenPlus1 @@ -6,10 +7,12 @@ local path = minetest.get_modpath("mobs") --- Mob Api +-- Mob API + dofile(path.."/api.lua") -- Animals + dofile(path.."/sheep.lua") -- PilzAdam dofile(path.."/boar.lua") -- KrupnoPavel dofile(path.."/skunk.lua") -- Kaadmy @@ -17,9 +20,15 @@ dofile(path.."/mineturtle.lua") -- Kaadmy dofile(path.."/walker.lua") -- Kaadmy -- NPC + dofile(path.."/npc.lua") -- TenPlus1 --- Mob Items +-- Mob items and crafts + dofile(path.."/crafts.lua") +-- Achievements + +dofile(path.."/achievements.lua") + default.log("mod:mobs", "loaded") diff --git a/mods/mobs/mineturtle.lua b/mods/mobs/mineturtle.lua index 2c3d93f..c5a0df4 100644 --- a/mods/mobs/mineturtle.lua +++ b/mods/mobs/mineturtle.lua @@ -1,3 +1,4 @@ + -- Mineturtle by Kaadmy mobs:register_mob( @@ -46,10 +47,28 @@ mobs:register_mob( punch_end = 60, }, on_rightclick = function(self, clicker) - mobs:feed_tame(self, clicker, 4, false) - mobs:capture_mob(self, clicker, 0, 20, 40, false, nil) - end, - }) + mobs:feed_tame(self, clicker, 4, false) + mobs:capture_mob(self, clicker, 0, 20, 40, false, nil) + end, + on_die = function(self, pos, hitter) + if hitter == nil or (hitter ~= nil and not hitter:is_player()) then + return + end -mobs:register_spawn("mobs:mineturtle", {"default:dirt_with_grass"}, 20, 5, 200000, 1, 31000) -mobs:register_egg("mobs:mineturtle", "Mine Turtle", "mobs_mineturtle_inventory.png") \ No newline at end of file + achievements.trigger_achievement(hitter, "bomb_has_been_defused") + end, +}) + +mobs:register_spawn( + "mobs:mineturtle", + { + "default:dirt_with_grass" + }, + 20, + 5, + 200000, + 1, + 31000 +) + +mobs:register_egg("mobs:mineturtle", "Mine Turtle", "mobs_mineturtle_inventory.png") diff --git a/mods/mobs/npc.lua b/mods/mobs/npc.lua index 3a68fb8..77b672a 100644 --- a/mods/mobs/npc.lua +++ b/mods/mobs/npc.lua @@ -1,3 +1,4 @@ + -- Npc by TenPlus1 -- Modded by Kaadmy @@ -63,56 +64,58 @@ for _, npc_type in pairs(npc_types) do punch_end = 219, }, on_spawn = function(self) - self.npc_type = npc_type - end, + self.npc_type = npc_type + end, on_rightclick = function(self, clicker) - local item = clicker:get_wielded_item() - local name = clicker:get_player_name() + local item = clicker:get_wielded_item() + local name = clicker:get_player_name() - -- feed to heal npc - if item:get_name() == "mobs:meat" or item:get_name() == "mobs:pork" or item:get_name() == "farming:bread" then - - local hp = self.object:get_hp() - -- return if full health - if hp >= self.hp_max then - minetest.chat_send_player(name, "Villager is no longer hungry.") - return - end + -- Feed to heal npc - hp = hp + 4 - if hp > self.hp_max then hp = self.hp_max end - self.object:set_hp(hp) + if item:get_name() == "mobs:meat" or item:get_name() == "mobs:pork" + or item:get_name() == "farming:bread" then - -- take item - if not minetest.setting_getbool("creative_mode") then - item:take_item() - clicker:set_wielded_item(item) - end - - -- right clicking with trading book trades, else changes order if tame - -- trading is done in the gold mod - else - -- if owner switch between follow and stand - if not self.npc_trade then - self.npc_trade = util.choice_element(gold.trades[self.npc_type], gold.pr) - end + local hp = self.object:get_hp() + -- return if full health + if hp >= self.hp_max then + minetest.chat_send_player(name, "Villager is no longer hungry.") + return + end - if not gold.trade(self.npc_trade, self.npc_type, clicker) then - if self.owner and self.owner == clicker:get_player_name() then - if self.order == "follow" then - self.order = "stand" - else - self.order = "follow" - end - end - end - end + hp = hp + 4 + if hp > self.hp_max then hp = self.hp_max end + self.object:set_hp(hp) - mobs:feed_tame(self, clicker, 8, false) - -- mobs:capture_mob(self, clicker, 20, 5, 10, false, nil) - end, - }) + -- take item + if not minetest.setting_getbool("creative_mode") then + item:take_item() + clicker:set_wielded_item(item) + end + + -- Right clicking with trading book trades, else changes order if tame + -- Trading is done in the gold mod + else + -- If owner switch between follow and stand + + if not self.npc_trade then + self.npc_trade = util.choice_element( + gold.trades[self.npc_type], gold.pr) + end + + if not gold.trade(self.npc_trade, self.npc_type, clicker) then + if self.owner and self.owner == clicker:get_player_name() then + if self.order == "follow" then + self.order = "stand" + else + self.order = "follow" + end + end + end + end + + mobs:feed_tame(self, clicker, 8, false) + end, + }) - --mobs:register_spawn("mobs:npc", {"default:dirt_with_grass"}, 20, 0, 7000, 1, 31000) mobs:register_egg("mobs:npc_" .. npc_type, "NPC", "default_brick.png^mobs_egg.png") -end \ No newline at end of file +end diff --git a/mods/mobs/sheep.lua b/mods/mobs/sheep.lua index 7f30438..877d74d 100644 --- a/mods/mobs/sheep.lua +++ b/mods/mobs/sheep.lua @@ -1,3 +1,4 @@ + -- Sheep by PilzAdam; tweaked for Pixture by Kaadmy mobs:register_mob( @@ -16,7 +17,7 @@ mobs:register_mob( {"mobs_sheep.png"}, }, gotten_texture = {"mobs_sheep_shaved.png"}, --- gotten_mesh = "mobs_sheep.x", + -- gotten_mesh = "mobs_sheep.x", makes_footstep_sound = true, sounds = { random = "mobs_sheep", @@ -46,68 +47,100 @@ mobs:register_mob( follow = "farming:wheat", view_range = 5, replace_rate = 50, - replace_what = {"default:grass", "default:tall_grass", "farming:wheat_3", "farming:wheat_4"}, + replace_what = { + "default:grass", + "default:tall_grass", + "farming:wheat_3", + "farming:wheat_4" + }, replace_with = "air", replace_offset = -1, + on_replace = function(self, pos) - minetest.set_node(pos, {name = self.replace_with}) - if mobs:feed_tame(self, self.follow, 8, true) then - if self.gotten == false then - self.object:set_properties( - { - textures = {"mobs_sheep.png"}, - mesh = "mobs_sheep.x", - }) - end - end - end, + minetest.set_node(pos, {name = self.replace_with}) + + if mobs:feed_tame(self, self.follow, 8, true) then + if self.gotten == false then + self.object:set_properties( + { + textures = {"mobs_sheep.png"}, + mesh = "mobs_sheep.x", + }) + end + end + end, on_rightclick = function(self, clicker) - --are we feeding? - if mobs:feed_tame(self, clicker, 8, true) then - --if full grow fuzz - if self.gotten == false then - self.object:set_properties( - { - textures = {"mobs_sheep.png"}, - mesh = "mobs_sheep.x", - }) - end - return - end + -- Are we feeding? - local item = clicker:get_wielded_item() - local itemname = item:get_name() + if mobs:feed_tame(self, clicker, 8, true) then + -- If full grow, add fuzz - --are we giving a haircut> - if itemname == "default:shears" then - if self.gotten == false and self.child == false then - self.gotten = true -- shaved - local pos = self.object:getpos() - pos.y = pos.y + 0.5 - local obj = minetest.add_item(pos, ItemStack("mobs:wool")) - if obj then - obj:setvelocity( - { - x = math.random(-1,1), - y = 5, - z = math.random(-1,1) - }) - end - item:add_wear(650) -- 100 uses - clicker:set_wielded_item(item) - self.object:set_properties( - { - textures = {"mobs_sheep_shaved.png"}, - mesh = "mobs_sheep.x", - }) - end - return - end + if self.gotten == false then + self.object:set_properties( + { + textures = {"mobs_sheep.png"}, + mesh = "mobs_sheep.x", + }) + end - --are we capturing? - mobs:capture_mob(self, clicker, 0, 5, 60, false, nil) - end - }) + return + end + + local item = clicker:get_wielded_item() + local itemname = item:get_name() + + -- Are we giving a haircut? + + if itemname == "default:shears" then + if self.gotten == false and self.child == false then + self.gotten = true -- shaved + local pos = self.object:getpos() + pos.y = pos.y + 0.5 + local obj = minetest.add_item(pos, ItemStack("mobs:wool")) + if obj then + obj:setvelocity( + { + x = math.random(-1,1), + y = 5, + z = math.random(-1,1) + }) + end + item:add_wear(650) -- 100 uses + clicker:set_wielded_item(item) + self.object:set_properties( + { + textures = {"mobs_sheep_shaved.png"}, + mesh = "mobs_sheep.x", + }) + end + + return + end + + -- Are we capturing? + + mobs:capture_mob(self, clicker, 0, 5, 60, false, nil) + end, + on_die = function(self, pos, hitter) + if hitter == nil or (hitter ~= nil and not hitter:is_player()) then + return + end + + achievements.trigger_achievement(hitter, "hunter") + end, + +}) + +mobs:register_spawn( + "mobs:sheep", + { + "default:dirt_with_grass" + }, + 20, + 10, + 15000, + 1, + 31000 +) mobs:register_egg("mobs:sheep", "Sheep", "mobs_sheep_inventory.png") -mobs:register_spawn("mobs:sheep", {"default:dirt_with_grass"}, 20, 10, 15000, 1, 31000) diff --git a/mods/mobs/skunk.lua b/mods/mobs/skunk.lua index 4987959..1780a36 100644 --- a/mods/mobs/skunk.lua +++ b/mods/mobs/skunk.lua @@ -44,10 +44,29 @@ mobs:register_mob( punch_end = 101, }, on_rightclick = function(self, clicker) - mobs:feed_tame(self, clicker, 6, true) - mobs:capture_mob(self, clicker, 10, 40, 20, false, nil) - end, - }) + mobs:feed_tame(self, clicker, 6, true) + mobs:capture_mob(self, clicker, 10, 40, 20, false, nil) + end, + on_die = function(self, pos, hitter) + if hitter == nil or (hitter ~= nil and not hitter:is_player()) then + return + end -mobs:register_spawn("mobs:skunk", {"default:dirt_with_swamp_grass", "default:dirt_with_dry_grass"}, 20, 7, 12000, 2, 50) -mobs:register_egg("mobs:skunk", "Skunk", "mobs_skunk_inventory.png") \ No newline at end of file + achievements.trigger_achievement(hitter, "hunter") + end, +}) + +mobs:register_spawn( + "mobs:skunk", + { + "default:dirt_with_swamp_grass", + "default:dirt_with_dry_grass" + }, + 20, + 7, + 12000, + 2, + 50 +) + +mobs:register_egg("mobs:skunk", "Skunk", "mobs_skunk_inventory.png") diff --git a/mods/mobs/walker.lua b/mods/mobs/walker.lua index 4a841f7..87a4bc3 100644 --- a/mods/mobs/walker.lua +++ b/mods/mobs/walker.lua @@ -55,6 +55,17 @@ mobs:register_mob( punch_start = 25, punch_end = 34, }, - }) +}) -mobs:register_spawn("mobs:walker", {"default:dry_dirt"}, 20, 14, 200000, 1, 31000) \ No newline at end of file +mobs:register_spawn( + "mobs:walker", + { + "default:dry_dirt", + "default:dirt_with_dry_grass" + }, + 20, + 14, + 200000, + 1, + 31000 +) diff --git a/mods/tnt/init.lua b/mods/tnt/init.lua index 85f6758..9ee60ad 100644 --- a/mods/tnt/init.lua +++ b/mods/tnt/init.lua @@ -34,7 +34,7 @@ minetest.after( on_blast = def.on_blast, } end - end) +end) local function rand_pos(center, pos, radius) pos.x = center.x + math.random(-radius, radius) @@ -90,7 +90,7 @@ local function destroy(drops, pos, cid) def.on_blast(vector.new(pos), 1) return end - + minetest.remove_node(pos) if def then local node_drops = minetest.get_node_drops(def.name, "") @@ -156,7 +156,7 @@ local function add_effects(pos, radius) minsize = 16, maxsize = 24, texture = "tnt_smoke.png", - }) + }) end function tnt.burn(pos) @@ -168,7 +168,15 @@ function tnt.burn(pos) end end -local function explode(pos, radius) +function tnt.explode(pos, radius, sound) + minetest.sound_play( + sound, + { + pos = pos, + gain = 1.5, + max_hear_distance = 128 + }) + local pos = vector.round(pos) local vm = VoxelManip() local pr = PseudoRandom(os.time()) @@ -204,12 +212,10 @@ local function explode(pos, radius) return drops end - function tnt.boom(pos) - minetest.sound_play("tnt_explode", {pos = pos, gain = 1.5, max_hear_distance = 128}) minetest.remove_node(pos) - local drops = explode(pos, radius) + local drops = tnt.explode(pos, radius, "tnt_explode") entity_physics(pos, radius) eject_drops(drops, pos, radius) add_effects(pos, radius) @@ -286,4 +292,4 @@ achievements.register_achievement( craftitem = "tnt:tnt", }) -default.log("mod:tnt", "loaded") \ No newline at end of file +default.log("mod:tnt", "loaded")