From 8d1ffbe2cdb09b5b1f0c2596e391e4a8ec7fbbca Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Thu, 25 May 2017 10:33:19 +0200 Subject: [PATCH] Update to Mobs Redo 1.35 --- mods/ENTITIES/mobs/api.lua | 1602 ++++++++++++++---------- mods/ENTITIES/mobs/api.txt | 250 +++- mods/ENTITIES/mobs/depends.txt | 2 +- mods/ENTITIES/mobs/init.lua | 5 +- mods/ENTITIES/mobs/locale/de.txt | 6 +- mods/ENTITIES/mobs/locale/pt.txt | 4 +- mods/ENTITIES/mobs/locale/template.txt | 6 +- mods/ENTITIES/mobs/locale/tr.txt | 39 + mods/ENTITIES/mobs/lucky_block.lua | 7 + mods/ENTITIES/mobs/mount.lua | 437 +++++++ mods/ENTITIES/mobs/readme.MD | 4 +- 11 files changed, 1635 insertions(+), 727 deletions(-) create mode 100644 mods/ENTITIES/mobs/locale/tr.txt create mode 100644 mods/ENTITIES/mobs/mount.lua diff --git a/mods/ENTITIES/mobs/api.lua b/mods/ENTITIES/mobs/api.lua index a9c290e7b3..e47bf9ae61 100644 --- a/mods/ENTITIES/mobs/api.lua +++ b/mods/ENTITIES/mobs/api.lua @@ -1,52 +1,35 @@ --- Mobs Api (1st December 2016) +-- Mobs Api (12th May 2017) mobs = {} mobs.mod = "redo" + -- Intllib local S + if minetest.get_modpath("intllib") then S = intllib.Getter() else - S = function(s, a, ...) - if a == nil then - return s - end - a = {a, ...} - return s:gsub("(@?)@(%(?)(%d+)(%)?)", - function(e, o, n, c) - if e == "" then - return a[tonumber(n)] .. (o == "" and c or "") - else - return "@" .. o .. n .. c - end - end) + S = function(s, a, ...) a = {a, ...} + return s:gsub("@(%d+)", function(n) + return a[tonumber(n)] + end) end + end + mobs.intllib = S + -- Invisibility mod check mobs.invis = {} if rawget(_G, "invisibility") then mobs.invis = invisibility end --- Load settings -local damage_enabled = minetest.setting_getbool("enable_damage") -local peaceful_only = minetest.setting_getbool("only_peaceful_mobs") -local disable_blood = minetest.setting_getbool("mobs_disable_blood") -local creative = minetest.setting_getbool("creative_mode") -local spawn_protected = tonumber(minetest.setting_get("mobs_spawn_protected")) or 1 -local remove_far = minetest.setting_getbool("remove_far_mobs") -local difficulty = tonumber(minetest.setting_get("mob_difficulty")) or 1.0 --- pathfinding settings -local enable_pathfinding = true -local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching -local stuck_path_timeout = 10 -- how long will mob follow path before giving up - --- localize functions +-- localize math functions local pi = math.pi local square = math.sqrt local sin = math.sin @@ -58,37 +41,70 @@ local atann = math.atan local random = math.random local floor = math.floor local atan = function(x) - - if x ~= x then + if not x or x ~= x then --error("atan bassed NaN") - --print ("atan based NaN") return 0 else return atann(x) end end -local do_attack = function(self, player) - if self.state ~= "attack" then +-- Load settings +local damage_enabled = minetest.setting_getbool("enable_damage") +local peaceful_only = minetest.setting_getbool("only_peaceful_mobs") +local disable_blood = minetest.setting_getbool("mobs_disable_blood") +local creative = minetest.setting_getbool("creative_mode") +local spawn_protected = tonumber(minetest.setting_get("mobs_spawn_protected")) or 1 +local remove_far = minetest.setting_getbool("remove_far_mobs") +local difficulty = tonumber(minetest.setting_get("mob_difficulty")) or 1.0 +local show_health = false +local max_per_block = tonumber(minetest.setting_get("max_objects_per_block") or 99) - if random(0,100) < 90 - and self.sounds.war_cry then +-- calculate aoc range for mob count +local aosrb = tonumber(minetest.setting_get("active_object_send_range_blocks")) +local abr = tonumber(minetest.setting_get("active_block_range")) +local aoc_range = max(aosrb, abr) * 16 - minetest.sound_play(self.sounds.war_cry, { - object = self.object, - max_hear_distance = self.sounds.distance - }) - end +-- pathfinding settings +local enable_pathfinding = true +local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching +local stuck_path_timeout = 10 -- how long will mob follow path before giving up - self.state = "attack" - self.attack = player + +-- play sound +mob_sound = function(self, sound) + + if sound then + minetest.sound_play(sound, { + object = self.object, + gain = 1.0, + max_hear_distance = self.sounds.distance + }) end end -local set_velocity = function(self, v) - local yaw = self.object:getyaw() + self.rotate or 0 +-- attack player/mob +do_attack = function(self, player) + + if self.state == "attack" then + return + end + + self.attack = player + self.state = "attack" + + if random(0, 100) < 90 then + mob_sound(self, self.sounds.war_cry) + end +end + + +-- move mob in facing direction +set_velocity = function(self, v) + + local yaw = (self.object:getyaw() or 0) + self.rotate self.object:setvelocity({ x = sin(yaw) * -v, @@ -97,148 +113,159 @@ local set_velocity = function(self, v) }) end -local get_velocity = function(self) + +-- get overall speed of mob +get_velocity = function(self) local v = self.object:getvelocity() return (v.x * v.x + v.z * v.z) ^ 0.5 end -local set_animation = function(self, type) - if not self.animation then - return +-- set yaw +set_yaw = function(self, yaw) + + if not yaw or yaw ~= yaw then + yaw = 0 end + self:setyaw(yaw) + + return yaw +end + + +-- set defined animation +set_animation = function(self, anim) + + if not self.animation then return end + self.animation.current = self.animation.current or "" - self.animation.speed_normal = self.animation.speed_normal or 15 - - if type == "stand" - and self.animation.current ~= "stand" then - - if self.animation.stand_start - and self.animation.stand_end then - - self.object:set_animation({ - x = self.animation.stand_start, - y = self.animation.stand_end}, - (self.animation.speed_stand or 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 then - - self.object:set_animation({ - x = self.animation.walk_start, - y = self.animation.walk_end}, - (self.animation.speed_walk or 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 then - - self.object:set_animation({ - x = self.animation.run_start, - y = self.animation.run_end}, - (self.animation.speed_run or self.animation.speed_normal), 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 then - - self.object:set_animation({ - x = self.animation.punch_start, - y = self.animation.punch_end}, - (self.animation.speed_punch or self.animation.speed_normal), 0) - - self.animation.current = "punch" - end - elseif type == "punch2" - and self.animation.current ~= "punch2" then - - if self.animation.punch2_start - and self.animation.punch2_end then - - self.object:set_animation({ - x = self.animation.punch2_start, - y = self.animation.punch2_end}, - (self.animation.speed_punch2 or self.animation.speed_normal), 0) - - self.animation.current = "punch2" - end - elseif type == "shoot" - and self.animation.current ~= "shoot" then - - if self.animation.shoot_start - and self.animation.shoot_end then - - self.object:set_animation({ - x = self.animation.shoot_start, - y = self.animation.shoot_end}, - (self.animation.speed_shoot or self.animation.speed_normal), 0) - - self.animation.current = "shoot" - end + if anim == self.animation.current + or not self.animation[anim .. "_start"] + or not self.animation[anim .. "_end"] then + return end + + self.animation.current = anim + + self.object:set_animation({ + x = self.animation[anim .. "_start"], + y = self.animation[anim .. "_end"] + }, self.animation[anim .. "_speed"] or self.animation.speed_normal or 15) + end --- check line of sight for walkers and swimmers alike -local function line_of_sight_water(self, pos1, pos2, stepsize) - local s, pos_w = minetest.line_of_sight(pos1, pos2, stepsize) +-- this is a faster way to calculate distance +local get_distance = function(a, b) + + local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z + + return square(x * x + y * y + z * z) +end + + +-- check line of sight (BrunoMine) +function line_of_sight(self, pos1, pos2, stepsize) + + stepsize = stepsize or 1 + + local s, pos = minetest.line_of_sight(pos1, pos2, stepsize) -- normal walking and flying mobs can see you through air if s == true then return true end - -- swimming mobs can see you through water - if s == false - and self.fly - and self.fly_in == "mcl_core:water_source" then + -- New pos1 to be analyzed + local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z} - local nod = minetest.get_node(pos_w).name + local r, pos = minetest.line_of_sight(npos1, pos2, stepsize) - if nod == "mcl_core:water_source" - or nod == "mcl_core:water_flowing" then + -- Checks the return + if r == true then return true end - return true + -- Nodename found + local nn = minetest.get_node(pos).name + + -- Target Distance (td) to travel + local td = get_distance(pos1, pos2) + + -- Actual Distance (ad) traveled + local ad = 0 + + -- It continues to advance in the line of sight in search of a real + -- obstruction which counts as 'normal' nodebox. + while minetest.registered_nodes[nn] + and (minetest.registered_nodes[nn].walkable == false + or minetest.registered_nodes[nn].drawtype == "nodebox") do + + -- Check if you can still move forward + if td < ad + stepsize then + return true -- Reached the target end - -- just incase we have a special node for flying/swimming mobs - elseif s == false - and self.fly - and self.fly_in then + -- Moves the analyzed pos + local d = get_distance(pos1, pos2) - local nod = minetest.get_node(pos_w).name + npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x + npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y + npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z - if nod == self.fly_in then - return true + -- NaN checks + if d == 0 + or npos1.x ~= npos1.x + or npos1.y ~= npos1.y + or npos1.z ~= npos1.z then + return false + end + + ad = ad + stepsize + + -- scan again + r, pos = minetest.line_of_sight(npos1, pos2, stepsize) + + if r == true then return true end + + -- New Nodename found + nn = minetest.get_node(pos).name + + end + + return false +end + + +-- are we flying in what we are suppose to? (taikedz) +local function flight_check(self, pos_w) + + local nod = self.standing_in + + if type(self.fly_in) == "string" + and (nod == self.fly_in or nod == self.fly_in:gsub("_source", "_flowing")) then + + return true + + elseif type(self.fly_in) == "table" then + + for _,fly_in in pairs(self.fly_in) do + + if nod == fly_in or nod == fly_in:gsub("_source", "_flowing") then + + return true + end end end return false - end + -- particle effects -local function effect(pos, amount, texture, min_size, max_size, radius, gravity) +function effect(pos, amount, texture, min_size, max_size, radius, gravity) radius = radius or 2 min_size = min_size or 0.5 @@ -262,14 +289,35 @@ local function effect(pos, amount, texture, min_size, max_size, radius, gravity) }) end -local function update_tag(self) + +-- update nametag colour +function update_tag(self) + + local col = "#00FF00" + local qua = self.hp_max / 4 + + if self.health <= floor(qua * 3) then + col = "#FFFF00" + end + + if self.health <= floor(qua * 2) then + col = "#FF6600" + end + + if self.health <= floor(qua) then + col = "#FF0000" + end + self.object:set_properties({ - nametag = self.nametag + nametag = self.nametag, + nametag_color = col }) + end + -- check if mob is dead or only hurt -local function check_for_death(self) +function check_for_death(self) -- has health actually changed? if self.health == self.old_health then @@ -281,14 +329,7 @@ local function check_for_death(self) -- still got some health? play hurt sound if self.health > 0 then - if self.sounds.damage then - - minetest.sound_play(self.sounds.damage, { - object = self.object, - gain = 1.0, - max_hear_distance = self.sounds.distance - }) - end + mob_sound(self, self.sounds.damage) -- make sure health isn't higher than max if self.health > self.hp_max then @@ -300,9 +341,13 @@ local function check_for_death(self) self.nametag2 = self.nametag or "" end - self.htimer = 2 + if show_health then - update_tag(self) + self.htimer = 2 + self.nametag = "♥ " .. self.health .. " / " .. self.hp_max + + update_tag(self) + end return false end @@ -310,6 +355,7 @@ local function check_for_death(self) -- drop items when dead local obj local pos = self.object:getpos() + self.drops = self.drops or {} -- nil check for n = 1, #self.drops do @@ -323,22 +369,14 @@ local function check_for_death(self) obj:setvelocity({ x = random(-10, 10) / 9, - y = 5, + y = 6, z = random(-10, 10) / 9, }) end end end - -- play death sound - if self.sounds.death then - - minetest.sound_play(self.sounds.death, { - object = self.object, - gain = 1.0, - max_hear_distance = self.sounds.distance - }) - end + mob_sound(self, self.sounds.death) -- execute custom death function if self.on_die then @@ -349,16 +387,35 @@ local function check_for_death(self) return true end - -- default death function - self.object:remove() + -- default death function and die animation (if defined) + if self.animation + and self.animation.die_start + and self.animation.die_end then + + self.attack = nil + self.v_start = false + self.timer = 0 + self.blinktimer = 0 + self.passive = true + self.state = "die" + set_velocity(self, 0) + set_animation(self, "die") + + minetest.after(2, function(self) + self.object:remove() + end, self) + else + self.object:remove() + end effect(pos, 20, "tnt_smoke.png") return true end + -- check if within physical map limits (-30911 to 30927) -local function within_limits(pos, radius) +function within_limits(pos, radius) if (pos.x - radius) > -30913 and (pos.x + radius) < 30928 @@ -372,6 +429,7 @@ local function within_limits(pos, radius) return false -- beyond limits end + -- is mob facing a cliff local function is_at_cliff(self) @@ -396,6 +454,7 @@ local function is_at_cliff(self) return false end + -- get node but use fallback for nil or unknown local function node_ok(pos, fallback) @@ -414,8 +473,9 @@ local function node_ok(pos, fallback) return minetest.registered_nodes[fallback] end + -- environmental damage (water, lava, fire, light) -local do_env_damage = function(self) +do_env_damage = function(self) -- feed/tame text timer (so mob 'full' messages dont spam chat) if self.htimer > 0 then @@ -427,6 +487,8 @@ local do_env_damage = function(self) self.nametag = self.nametag2 self.nametag2 = nil + + update_tag(self) end local pos = self.object:getpos() @@ -456,6 +518,12 @@ local do_env_damage = function(self) self.standing_in = node_ok(pos, "air").name --print ("standing in " .. self.standing_in) + -- don't fall when on ignore, just stand still + if self.standing_in == "ignore" then + self.object:setvelocity({x = 0, y = 0, z = 0}) + --print ("--- stopping on ignore") + end + if self.water_damage ~= 0 or self.lava_damage ~= 0 then @@ -469,33 +537,52 @@ local do_env_damage = function(self) self.health = self.health - self.water_damage - effect(pos, 5, "bubble.png") - end + effect(pos, 5, "bubble.png", nil, nil, 1, nil) -- lava or fire - if self.lava_damage ~= 0 + elseif self.lava_damage ~= 0 and (nodef.groups.lava - or self.standing_in == "mcl_fire:fire" - or self.standing_in == "mcl_fire:eternal_fire") then + or self.standing_in == "fire:basic_flame" + or self.standing_in == "fire:permanent_flame") then self.health = self.health - self.lava_damage - effect(pos, 5, "fire_basic_flame.png") + effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil) + + -- damage_per_second node check +-- elseif minetest.registered_nodes[self.standing_in].damage_per_second ~= 0 then + +-- local dps = minetest.registered_nodes[self.standing_in].damage_per_second + +-- self.health = self.health - dps + +-- effect(pos, 5, "tnt_smoke.png") end end check_for_death(self) end --- jump if facing a solid node (not fences or gates) -local do_jump = function(self) - if self.fly +-- jump if facing a solid node (not fences or gates) +do_jump = function(self) + + if not self.jump + or self.jump_height == 0 + or self.fly or self.child then - return + return false + end + + -- something stopping us while moving? + if self.state ~= "stand" + and get_velocity(self) > 0.5 + and self.object:getvelocity().y ~= 0 then + return false end local pos = self.object:getpos() + local yaw = self.object:getyaw() -- what is mob standing on? pos.y = pos.y + self.collisionbox[2] - 0.2 @@ -505,11 +592,10 @@ local do_jump = function(self) --print ("standing on:", nod.name, pos.y) if minetest.registered_nodes[nod.name].walkable == false then - return + return false end -- where is front - local yaw = self.object:getyaw() local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5) local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5) @@ -522,7 +608,7 @@ local do_jump = function(self) -- thin blocks that do not need to be jumped if nod.name == "mcl_core:snow" then - return + return false end --print ("in front:", nod.name, pos.y + 0.5) @@ -534,31 +620,23 @@ local do_jump = function(self) local v = self.object:getvelocity() - v.y = self.jump_height + 1 + v.y = self.jump_height -- + 1 + + set_animation(self, "jump") -- only when defined self.object:setvelocity(v) - if self.sounds.jump then + mob_sound(self, self.sounds.jump) - minetest.sound_play(self.sounds.jump, { - object = self.object, - gain = 1.0, - max_hear_distance = self.sounds.distance - }) - end + return true end + + return false end --- this is a faster way to calculate distance -local get_distance = function(a, b) - - local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z - - return square(x * x + y * y + z * z) -end -- blast damage to entities nearby (modified from TNT mod) -local function entity_physics(pos, radius) +function entity_physics(pos, radius) radius = radius * 2 @@ -575,23 +653,17 @@ local function entity_physics(pos, radius) local damage = floor((4 / dist) * radius) local ent = objs[n]:get_luaentity() - if objs[n]:is_player() then - objs[n]:set_hp(objs[n]:get_hp() - damage) - mcl_hunger.exhaust(objs[n]:get_player_name(), mcl_hunger.EXHAUST_DAMAGE) - - else --if ent.health then - - objs[n]:punch(objs[n], 1.0, { - full_punch_interval = 1.0, - damage_groups = {fleshy = damage}, - }, nil) - - end + -- punches work on entities AND players + objs[n]:punch(objs[n], 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = damage}, + }, nil) end end + -- should mob follow what I'm holding ? -local function follow_holding(self, clicker) +function follow_holding(self, clicker) if mobs.invis[clicker:get_player_name()] then return false @@ -619,6 +691,7 @@ local function follow_holding(self, clicker) return false end + -- find two animals of same type and breed if nearby and horny local function breed(self) @@ -753,37 +826,52 @@ local function breed(self) end end + -- find and replace what mob is looking for (grass, wheat etc.) -local function replace(self, pos) +function replace(self, pos) - if self.replace_rate - and self.child == false - and random(1, self.replace_rate) == 1 then + if not self.replace_rate + or not self.replace_what + or self.child == true + or self.object:getvelocity().y ~= 0 + or random(1, self.replace_rate) > 1 then + return + end - local pos = self.object:getpos() + local what, with, y_offset - pos.y = pos.y + self.replace_offset + if type(self.replace_what[1]) == "table" then + + local num = random(#self.replace_what) + + what = self.replace_what[num][1] or "" + with = self.replace_what[num][2] or "" + y_offset = self.replace_what[num][3] or 0 + else + what = self.replace_what + with = self.replace_with or "" + y_offset = self.replace_offset or 0 + end + + pos.y = pos.y + y_offset + + if #minetest.find_nodes_in_area(pos, pos, what) > 0 then -- print ("replace node = ".. minetest.get_node(pos).name, pos.y) - if self.replace_what - and self.replace_with - and self.object:getvelocity().y == 0 - and #minetest.find_nodes_in_area(pos, pos, self.replace_what) > 0 then + minetest.set_node(pos, {name = with}) - minetest.set_node(pos, {name = self.replace_with}) - - -- when cow/sheep eats grass, replace wool and milk - if self.gotten == true then - self.gotten = false - self.object:set_properties(self) - end + -- when cow/sheep eats grass, replace wool and milk + if self.gotten == true then + self.gotten = false + self.object:set_properties(self) end end end + -- check if daytime and also if mob is docile during daylight hours -local function day_docile(self) +function day_docile(self) if self.docile_by_day == false then @@ -797,8 +885,9 @@ local function day_docile(self) end end + -- path finding and smart mob routine by rnd -local function smart_mobs(self, s, p, dist, dtime) +function smart_mobs(self, s, p, dist, dtime) local s1 = self.path.lastpos @@ -813,8 +902,7 @@ local function smart_mobs(self, s, p, dist, dtime) -- im stuck, search for path if (self.path.stuck_timer > stuck_timeout and not self.path.following) - or (self.path.stuck_timer > stuck_path_timeout - and self.path.following) then + or (self.path.stuck_timer > stuck_path_timeout and self.path.following) then self.path.stuck_timer = 0 @@ -842,17 +930,7 @@ local function smart_mobs(self, s, p, dist, dtime) p1.y = floor(p1.y + 0.5) p1.z = floor(p1.z + 0.5) - local drop_height = self.fear_height - if not drop_height then - drop_height = 4 - end - local jump_height - if self.jump_height then - jump_height = math.max(1, self.jump_height/3) - else - jump_height = 1 - end - self.path.way = minetest.find_path(s, p1, self.view_range + 1, jump_height, drop_height, "A*_noprefetch") + self.path.way = minetest.find_path(s, p1, 16, 2, 6, "Dijkstra") -- attempt to unstick mob that is "daydreaming" self.object:setpos({ @@ -872,12 +950,18 @@ local function smart_mobs(self, s, p, dist, dtime) -- lets make way by digging/building if not accessible if self.pathfinding == 2 then - -- add block and remove one block above so - -- there is room to jump if needed + -- is player higher than mob? if s.y < p1.y then + -- build upwards if not minetest.is_protected(s, "") then - minetest.set_node(s, {name = "mcl_core:dirt"}) + + local ndef1 = minetest.registered_nodes[self.standing_in] + + if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then + + minetest.set_node(s, {name = "mcl_core:dirt"}) + end end local sheight = math.ceil(self.collisionbox[5]) + 1 @@ -885,14 +969,21 @@ local function smart_mobs(self, s, p, dist, dtime) -- assume mob is 2 blocks high so it digs above its head s.y = s.y + sheight + -- remove one block above to make room to jump if not minetest.is_protected(s, "") then - local node1 = minetest.get_node(s).name + local node1 = node_ok(s, "air").name + local ndef1 = minetest.registered_nodes[node1] if node1 ~= "air" - and node1 ~= "ignore" then + and node1 ~= "ignore" + and ndef1 + and not ndef1.groups.level + and not ndef1.groups.unbreakable then + minetest.set_node(s, {name = "air"}) minetest.add_item(s, ItemStack(node1)) + end end @@ -902,7 +993,6 @@ local function smart_mobs(self, s, p, dist, dtime) else -- dig 2 blocks to make door toward player direction local yaw1 = self.object:getyaw() + pi / 2 - local p1 = { x = s.x + cos(yaw1), y = s.y, @@ -911,19 +1001,29 @@ local function smart_mobs(self, s, p, dist, dtime) if not minetest.is_protected(p1, "") then - local node1 = minetest.get_node(p1).name + local node1 = node_ok(p1, "air").name + local ndef1 = minetest.registered_nodes[node1] if node1 ~= "air" - and node1 ~= "ignore" then + and node1 ~= "ignore" + and ndef1 + and not ndef1.groups.level + and not ndef1.groups.unbreakable then + minetest.add_item(p1, ItemStack(node1)) minetest.set_node(p1, {name = "air"}) end p1.y = p1.y + 1 - node1 = minetest.get_node(p1).name + node1 = node_ok(p1, "air").name + ndef1 = minetest.registered_nodes[node1] if node1 ~= "air" - and node1 ~= "ignore" then + and node1 ~= "ignore" + and ndef1 + and not ndef1.groups.level + and not ndef1.groups.unbreakable then + minetest.add_item(p1, ItemStack(node1)) minetest.set_node(p1, {name = "air"}) end @@ -936,26 +1036,12 @@ local function smart_mobs(self, s, p, dist, dtime) self.path.stuck_timer = stuck_timeout - 2 -- frustration! cant find the damn path :( - if self.sounds.random then - - minetest.sound_play(self.sounds.random, { - object = self.object, - max_hear_distance = self.sounds.distance - }) - end - + mob_sound(self, self.sounds.random) else - -- yay i found path - if self.sounds.attack then + mob_sound(self, self.sounds.attack) - set_velocity(self, self.walk_velocity) - - minetest.sound_play(self.sounds.attack, { - object = self.object, - max_hear_distance = self.sounds.distance - }) - end + set_velocity(self, self.walk_velocity) -- follow path now that it has it self.path.following = true @@ -963,6 +1049,7 @@ local function smart_mobs(self, s, p, dist, dtime) end end + -- specific attacks local specific_attack = function(list, what) @@ -982,6 +1069,7 @@ local specific_attack = function(list, what) return false end + -- monster find someone to attack local monster_attack = function(self) @@ -1024,7 +1112,7 @@ local monster_attack = function(self) -- find specific mob to attack, failing that attack player/npc/animal if specific_attack(self.specific_attack, name) and (type == "player" or type == "npc" - or (type == "animal" and self.attack_animals == true)) then + or (type == "animal" and self.attack_animals == true)) then s = self.object:getpos() p = player:getpos() @@ -1040,7 +1128,7 @@ local monster_attack = function(self) -- field of view check goes here -- choose closest player to attack - if line_of_sight_water(self, sp, p, 2) == true + if line_of_sight(self, sp, p, 2) == true and dist < min_dist then min_dist = dist min_player = player @@ -1055,6 +1143,7 @@ local monster_attack = function(self) end end + -- npc, find closest monster to attack local npc_attack = function(self) @@ -1073,12 +1162,11 @@ local npc_attack = function(self) obj = objs[n]:get_luaentity() - if obj - and obj.type == "monster" then + if obj and obj.type == "monster" then - local p = obj.object:getpos() + p = obj.object:getpos() - local dist = get_distance(p, s) + dist = get_distance(p, s) if dist < min_dist then min_dist = dist @@ -1092,6 +1180,7 @@ local npc_attack = function(self) end end + -- follow player if owner or holding item, if fish outta water then flop local follow_flop = function(self) @@ -1163,31 +1252,19 @@ local follow_flop = function(self) else local vec = { x = p.x - s.x, - y = p.y - s.y, z = p.z - s.z } local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate - if p.x > s.x then - yaw = yaw + pi - end + if p.x > s.x then yaw = yaw + pi end - self.object:setyaw(yaw) + yaw = set_yaw(self.object, yaw) -- anyone but standing npc's can move along if dist > self.reach and self.order ~= "stand" then - if (self.jump - and get_velocity(self) <= 0.5 - and self.object:getvelocity().y == 0) - or (self.object:getvelocity().y == 0 - and self.jump_chance > 0) then - - do_jump(self) - end - set_velocity(self, self.walk_velocity) if self.walk_chance ~= 0 then @@ -1203,20 +1280,24 @@ local follow_flop = function(self) end end - -- water swimmers flop when on land - if self.fly - and self.fly_in == "mcl_core:water_source" - and self.standing_in ~= self.fly_in then + -- swimmers flop when out of their element, and swim again when back in + if self.fly then + local s = self.object:getpos() + if not flight_check(self, s) then - self.state = "flop" - self.object:setvelocity({x = 0, y = -5, z = 0}) + self.state = "flop" + self.object:setvelocity({x = 0, y = -5, z = 0}) - set_animation(self, "stand") + set_animation(self, "stand") - return + return + elseif self.state == "flop" then + self.state = "stand" + end end end + -- dogshoot attack switch and counter function local dogswitch = function(self, dtime) @@ -1228,7 +1309,10 @@ local dogswitch = function(self, dtime) self.dogshoot_count = self.dogshoot_count + dtime - if self.dogshoot_count > self.dogshoot_count_max then + if (self.dogshoot_switch == 1 + and self.dogshoot_count > self.dogshoot_count_max) + or (self.dogshoot_switch == 2 + and self.dogshoot_count > self.dogshoot_count2_max) then self.dogshoot_count = 0 @@ -1242,6 +1326,7 @@ local dogswitch = function(self, dtime) return self.dogshoot_switch end + -- execute current state (stand, walk, run, attacks) local do_states = function(self, dtime) @@ -1253,17 +1338,13 @@ local do_states = function(self, dtime) local lp = nil local s = self.object:getpos() + local objs = minetest.get_objects_inside_radius(s, 3) - if self.type == "npc" then + for n = 1, #objs do - local objs = minetest.get_objects_inside_radius(s, 3) - - for n = 1, #objs do - - if objs[n]:is_player() then - lp = objs[n]:getpos() - break - end + if objs[n]:is_player() then + lp = objs[n]:getpos() + break end end @@ -1272,20 +1353,17 @@ local do_states = function(self, dtime) local vec = { x = lp.x - s.x, - y = lp.y - s.y, z = lp.z - s.z } yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate - if lp.x > s.x then - yaw = yaw + pi - end + if lp.x > s.x then yaw = yaw + pi end else yaw = (random(0, 360) - 180) / 180 * pi end - self.object:setyaw(yaw) + yaw = set_yaw(self.object, yaw) end set_velocity(self, 0) @@ -1302,6 +1380,15 @@ local do_states = function(self, dtime) set_velocity(self, self.walk_velocity) self.state = "walk" set_animation(self, "walk") + + -- fly up/down randombly for flying mobs + if self.fly and random(1, 100) <= self.walk_chance then + + local v = self.object:getvelocity() + local ud = random(-1, 2) / 9 + + self.object:setvelocity({x = v.x, y = ud, z = v.z}) + end end end @@ -1325,43 +1412,62 @@ local do_states = function(self, dtime) lp = minetest.find_node_near(s, 1, {"group:lava"}) end - -- if something then avoid if lp then - local vec = { - x = lp.x - s.x, - y = lp.y - s.y, - z = lp.z - s.z - } + -- if mob in water or lava then look for land + if (self.lava_damage + and minetest.registered_nodes[self.standing_in].groups.lava) + or (self.water_damage + and minetest.registered_nodes[self.standing_in].groups.water) then - yaw = atan(vec.z / vec.x) + 3 * pi / 2 - self.rotate + lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone", + "group:sand", "mcl_core:ice", "mcl_core:snowblock"}) - if lp.x > s.x then - yaw = yaw + pi + -- did we find land? + if lp then + + local vec = { + x = lp.x - s.x, + z = lp.z - s.z + } + + yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate + + if lp.x > s.x then yaw = yaw + pi end + + -- look towards land and jump/move in that direction + yaw = set_yaw(self.object, yaw) + do_jump(self) + set_velocity(self, self.walk_velocity) + else + yaw = (random(0, 360) - 180) / 180 * pi + end + + else + + local vec = { + x = lp.x - s.x, + z = lp.z - s.z + } + + yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate + + if lp.x > s.x then yaw = yaw + pi end end - self.object:setyaw(yaw) + yaw = set_yaw(self.object, yaw) -- otherwise randomly turn elseif random(1, 100) <= 30 then - local yaw = (random(0, 360) - 180) / 180 * pi + yaw = random() * 2 * pi - self.object:setyaw(yaw) + yaw = set_yaw(self.object, yaw) end -- stand for great fall in front local temp_is_cliff = is_at_cliff(self) - -- jump when walking comes to a halt - if temp_is_cliff == false - and self.jump - and get_velocity(self) <= 0.5 - and self.object:getvelocity().y == 0 then - - do_jump(self) - end - if temp_is_cliff or random(1, 100) <= 30 then @@ -1370,7 +1476,15 @@ local do_states = function(self, dtime) set_animation(self, "stand") else set_velocity(self, self.walk_velocity) - set_animation(self, "walk") + + if flight_check(self) + and self.animation + and self.animation.fly_start + and self.animation.fly_end then + set_animation(self, "fly") + else + set_animation(self, "walk") + end end -- runaway when punched @@ -1390,14 +1504,6 @@ local do_states = function(self, dtime) set_animation(self, "walk") end - -- jump when walking comes to a halt - if self.jump - and get_velocity(self) <= 0.5 - and self.object:getvelocity().y == 0 then - - do_jump(self) - end - -- attack routines (explode, dogfight, shoot, dogshoot) elseif self.state == "attack" then @@ -1429,17 +1535,14 @@ local do_states = function(self, dtime) local vec = { x = p.x - s.x, - y = p.y - s.y, z = p.z - s.z } - yaw = atan(vec.z / vec.x) + pi / 2 - self.rotate + yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate - if p.x > s.x then - yaw = yaw + pi - end + if p.x > s.x then yaw = yaw + pi end - self.object:setyaw(yaw) + yaw = set_yaw(self.object, yaw) if dist > self.reach then @@ -1453,12 +1556,6 @@ local do_states = function(self, dtime) self.timer = 0 self.blinktimer = 0 - if get_velocity(self) <= 0.5 - and self.object:getvelocity().y == 0 then - - do_jump(self) - end - set_velocity(self, self.run_velocity) end @@ -1488,35 +1585,30 @@ local do_states = function(self, dtime) local pos = self.object:getpos() local radius = self.explosion_radius or 1 - -- hurt player/mobs caught in blast area - entity_physics(pos, radius) - -- dont damage anything if area protected or next to water if minetest.find_node_near(pos, 1, {"group:water"}) or minetest.is_protected(pos, "") then - if self.sounds.explode then - - minetest.sound_play(self.sounds.explode, { - object = self.object, - gain = 1.0, - max_hear_distance = 16 - }) - end + mob_sound(self, self.sounds.explode) self.object:remove() effect(pos, 15, "tnt_smoke.png") + -- hurt player/mobs caught in blast area + entity_physics(pos, radius) + return end pos.y = pos.y - 1 - mobs:explosion(pos, radius, 0, 1, self.sounds.explode) + mobs:explosion(pos, radius, 1, 1, self.sounds.explode) self.object:remove() + entity_physics(pos, radius) + return end end @@ -1528,14 +1620,13 @@ local do_states = function(self, dtime) if self.fly and dist > self.reach then - local nod = node_ok(s) local p1 = s local me_y = floor(p1.y) local p2 = p local p_y = floor(p2.y + 1) local v = self.object:getvelocity() - if nod.name == self.fly_in then + if flight_check(self, s) then if me_y < p_y then @@ -1604,17 +1695,14 @@ local do_states = function(self, dtime) local vec = { x = p.x - s.x, - y = p.y - s.y, z = p.z - s.z } yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate - if p.x > s.x then - yaw = yaw + pi - end + if p.x > s.x then yaw = yaw + pi end - self.object:setyaw(yaw) + yaw = set_yaw(self.object, yaw) -- move towards enemy if beyond mob reach if dist > self.reach then @@ -1626,16 +1714,6 @@ local do_states = function(self, dtime) smart_mobs(self, s, p, dist, dtime) end - -- jump attack - if (self.jump - and get_velocity(self) <= 0.5 - and self.object:getvelocity().y == 0) - or (self.object:getvelocity().y == 0 - and self.jump_chance > 0) then - - do_jump(self) - end - if is_at_cliff(self) then set_velocity(self, 0) @@ -1675,26 +1753,23 @@ local do_states = function(self, dtime) local p2 = p local s2 = s - p2.y = p2.y + 1.5 - s2.y = s2.y + 1.5 + p2.y = p2.y + .5 + s2.y = s2.y + .5 - if line_of_sight_water(self, p2, s2) == true then + if line_of_sight(self, p2, s2) == true then -- play attack sound - if self.sounds.attack then + mob_sound(self, self.sounds.attack) - minetest.sound_play(self.sounds.attack, { - object = self.object, - max_hear_distance = self.sounds.distance - }) + -- punch player (or what player is attached to) + local attached = self.attack:get_attach() + if attached then + self.attack = attached end - - -- punch player self.attack:punch(self.object, 1.0, { full_punch_interval = 1.0, damage_groups = {fleshy = self.damage} }, nil) - mcl_hunger.exhaust(self.attack:get_player_name(), mcl_hunger.EXHAUST_DAMAGE) end end else -- call custom attack every second @@ -1724,11 +1799,9 @@ local do_states = function(self, dtime) yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate - if p.x > s.x then - yaw = yaw + pi - end + if p.x > s.x then yaw = yaw + pi end - self.object:setyaw(yaw) + yaw = set_yaw(self.object, yaw) set_velocity(self, 0) @@ -1740,13 +1813,7 @@ local do_states = function(self, dtime) set_animation(self, "shoot") -- play shoot attack sound - if self.sounds.shoot_attack then - - minetest.sound_play(self.sounds.shoot_attack, { - object = self.object, - max_hear_distance = self.sounds.distance - }) - end + mob_sound(self, self.sounds.shoot_attack) local p = self.object:getpos() @@ -1754,23 +1821,27 @@ local do_states = function(self, dtime) local obj = minetest.add_entity(p, self.arrow) local ent = obj:get_luaentity() - local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5 - local v = ent.velocity or 1 -- or set to default - ent.switch = 1 - ent.owner_id = tostring(self.object) -- add unique owner id to arrow - -- offset makes shoot aim accurate - vec.y = vec.y + self.shoot_offset - vec.x = vec.x * (v / amount) - vec.y = vec.y * (v / amount) - vec.z = vec.z * (v / amount) + if ent then + local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5 + local v = ent.velocity or 1 -- or set to default + ent.switch = 1 + ent.owner_id = tostring(self.object) -- add unique owner id to arrow - obj:setvelocity(vec) + -- offset makes shoot aim accurate + vec.y = vec.y + self.shoot_offset + vec.x = vec.x * (v / amount) + vec.y = vec.y * (v / amount) + vec.z = vec.z * (v / amount) + + obj:setvelocity(vec) + end end end end end + -- falling and fall damage local falling = function(self, pos) @@ -1814,13 +1885,13 @@ local falling = function(self, pos) if self.fall_damage == 1 and self.object:getvelocity().y == 0 then - local d = self.old_y - self.object:getpos().y + local d = (self.old_y or 0) - self.object:getpos().y if d > 5 then self.health = self.health - floor(d - 5) - effect(pos, 5, "tnt_smoke.png") + effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil) if check_for_death(self) then return @@ -1832,20 +1903,27 @@ local falling = function(self, pos) end end + +-- deal damage and effects when mob punched local mob_punch = function(self, hitter, tflp, tool_capabilities, dir) + -- mob health check + if self.health <= 0 then + return + end + -- error checking when mod profiling is enabled if not tool_capabilities then print (S("[MOBS] mod profiling enabled, damage not enabled")) return end --- is mob protected? -if self.protected and hitter:is_player() -and minetest.is_protected(self.object:getpos(), hitter:get_player_name()) then - minetest.chat_send_player(hitter:get_player_name(), "Mob has been protected!") - return -end + -- is mob protected? + if self.protected and hitter:is_player() + and minetest.is_protected(self.object:getpos(), hitter:get_player_name()) then + minetest.chat_send_player(hitter:get_player_name(), "Mob has been protected!") + return + end -- weapon wear @@ -1876,11 +1954,6 @@ end * tmp * ((armor[group] or 0) / 100.0) end - -- Exhaust attacker - if hitter:is_player() then - mcl_hunger.exhaust(hitter:get_player_name(), mcl_hunger.EXHAUST_ATTACK) - end - -- check for tool immunity or special damage for n = 1, #self.immune_to do @@ -1904,79 +1977,87 @@ end punch_interval = tool_capabilities.full_punch_interval or 1.4 end - if (not minetest.setting_getbool("creative_mode")) - and weapon:get_definition() + if weapon:get_definition() and weapon:get_definition().tool_capabilities then + weapon:add_wear(floor((punch_interval / 75) * 9000)) hitter:set_wielded_item(weapon) - end --- only play hit sound and show blood effects if damage is 1 or over -if damage >= 1 then + -- only play hit sound and show blood effects if damage is 1 or over + if damage >= 1 then - -- TODO (maybe): Support for custom weapon sounds - minetest.sound_play("default_punch", { - object = hitter, - max_hear_distance = 5 - }) + -- weapon sounds + if weapon:get_definition().sounds ~= nil then - -- blood_particles - if self.blood_amount > 0 - and not disable_blood then + local s = random(0, #weapon:get_definition().sounds) - local pos = self.object:getpos() - - pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5 - - effect(pos, self.blood_amount, self.blood_texture) - end - - -- do damage - self.health = self.health - floor(damage) - - -- exit here if dead - if check_for_death(self) then - return - end - - --[[ add healthy afterglow when hit (can cause hit lag with larger textures) - core.after(0.1, function() - self.object:settexturemod("^[colorize:#c9900070") - - core.after(0.3, function() - self.object:settexturemod("") - end) - end) ]] - - -- knock back effect (only on full punch) - if self.knock_back > 0 - and tflp > punch_interval then - - local v = self.object:getvelocity() - local r = 1.4 - min(punch_interval, 1.4) - local kb = r * 5 - local up = 2 - - -- if already in air then dont go up anymore when hit - if v.y > 0 - or self.fly then - up = 0 + 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 - -- direction error check - dir = dir or {x = 0, y = 0, z = 0} + -- blood_particles + if self.blood_amount > 0 + and not disable_blood then - self.object:setvelocity({ - x = dir.x * kb, - y = up, - z = dir.z * kb - }) + local pos = self.object:getpos() - self.pause_timer = r - end + pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5 -end -- END if damage + effect(pos, self.blood_amount, self.blood_texture, nil, nil, 1, nil) + end + + -- do damage + self.health = self.health - floor(damage) + + -- exit here if dead + if check_for_death(self) then + return + end + + --[[ add healthy afterglow when hit (can cause hit lag with larger textures) + core.after(0.1, function() + self.object:settexturemod("^[colorize:#c9900070") + + core.after(0.3, function() + self.object:settexturemod("") + end) + end) ]] + + -- knock back effect (only on full punch) + if self.knock_back > 0 + and tflp >= punch_interval then + + local v = self.object:getvelocity() + local r = 1.4 - min(punch_interval, 1.4) + local kb = r * 5 + local up = 2 + + -- if already in air then dont go up anymore when hit + if v.y > 0 + or self.fly then + up = 0 + end + + -- direction error check + dir = dir or {x = 0, y = 0, z = 0} + + self.object:setvelocity({ + x = dir.x * kb, + y = up, + z = dir.z * kb + }) + + self.pause_timer = r + end + end -- END if damage -- if skittish then run away if self.runaway == true then @@ -1990,24 +2071,26 @@ end -- END if damage z = lp.z - s.z } - local yaw = atan(vec.z / vec.x) + 3 * pi / 2 - self.rotate + local yaw = atan(vec.z / vec.x) + 3 * pi / 2 if lp.x > s.x then yaw = yaw + pi end - self.object:setyaw(yaw) + yaw = set_yaw(self.object, yaw) self.state = "runaway" self.runaway_timer = 0 self.following = nil end + local name = hitter:get_player_name() or "" + -- attack puncher and call other mobs for help if self.passive == false and self.state ~= "flop" and self.child == false and hitter:get_player_name() ~= self.owner - and not mobs.invis[ hitter:get_player_name() ] then + and not mobs.invis[ name ] then -- attack whoever punched mob self.state = "" @@ -2023,20 +2106,74 @@ end -- END if damage if obj then + -- only alert members of same mob if obj.group_attack == true - and obj.state ~= "attack" then + and obj.state ~= "attack" + and obj.owner ~= name + and obj.name == self.name then do_attack(obj, hitter) end + + -- have owned mobs attack player threat + if obj.owner == name and obj.owner_loyal then + do_attack(obj, self.object) + end end end end end -local mob_activate = function(self, staticdata, dtime_s, def) + +-- get entity staticdata +local mob_staticdata = function(self) + + -- remove mob when out of range unless tamed + if remove_far + and self.remove_ok + and not self.tamed + and self.lifetimer < 20000 then + + --print ("REMOVED " .. self.name) + + self.object:remove() + + return ""-- nil + end + + self.remove_ok = true + self.attack = nil + self.following = nil + self.state = "stand" + + -- used to rotate older mobs + if self.drawtype + and self.drawtype == "side" then + self.rotate = math.rad(90) + end + + 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 + + +-- activate mob and reload settings +local mob_activate = function(self, staticdata, def) -- remove monsters in peaceful mode, or when no data - if (self.type == "monster" and peaceful_only) - or not staticdata then + if (self.type == "monster" and peaceful_only) then self.object:remove() @@ -2047,7 +2184,6 @@ local mob_activate = function(self, staticdata, dtime_s, def) local tmp = minetest.deserialize(staticdata) if tmp then - for _,stat in pairs(tmp) do self[_] = stat end @@ -2056,6 +2192,11 @@ local mob_activate = function(self, staticdata, dtime_s, def) -- select random texture, set model and size if not self.base_texture then + -- compatiblity with old simple mobs textures + if type(def.textures[1]) == "string" then + def.textures = {def.textures} + end + self.base_texture = def.textures[random(1, #def.textures)] self.base_mesh = def.mesh self.base_size = self.visual_size @@ -2118,7 +2259,6 @@ local mob_activate = function(self, staticdata, dtime_s, def) self.object:set_armor_groups({immortal = 1, fleshy = self.armor}) self.old_y = self.object:getpos().y self.old_health = self.health - self.object:setyaw((random(0, 360) - 180) / 180 * pi) self.sounds.distance = self.sounds.distance or 10 self.textures = textures self.mesh = mesh @@ -2128,13 +2268,16 @@ local mob_activate = function(self, staticdata, dtime_s, def) -- set anything changed above self.object:set_properties(self) + set_yaw(self.object, (random(0, 360) - 180) / 180 * pi) update_tag(self) end + +-- main mob function local mob_step = function(self, dtime) local pos = self.object:getpos() - local yaw = self.object:getyaw() or 0 + local yaw = 0 -- when lifetimer expires remove mob (except npc and tamed) if self.type ~= "npc" @@ -2163,7 +2306,7 @@ local mob_step = function(self, dtime) -- minetest.log("action", -- S("lifetimer expired, removed @1", self.name)) - effect(pos, 15, "tnt_smoke.png") + effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0) self.object:remove() @@ -2215,13 +2358,8 @@ local mob_step = function(self, dtime) replace(self, pos) -- mob plays random sound at times - if self.sounds.random - and random(1, 100) == 1 then - - minetest.sound_play(self.sounds.random, { - object = self.object, - max_hear_distance = self.sounds.distance - }) + if random(1, 100) == 1 then + mob_sound(self, self.sounds.random) end -- environmental damage timer (every 1 second) @@ -2245,8 +2383,11 @@ local mob_step = function(self, dtime) do_states(self, dtime) + do_jump(self) + end + -- default function when mobs are blown up with TNT local do_tnt = function(obj, damage) @@ -2256,23 +2397,21 @@ local do_tnt = function(obj, damage) full_punch_interval = 1.0, damage_groups = {fleshy = damage}, }, nil) - if obj.object:is_player() then - mcl_hunger.exhaust(obj.object:get_player_name(), mcl_hunger.EXHAUST_DAMAGE) - end return false, true, {} end + mobs.spawning_mobs = {} --- register mob function +-- register mob entity function mobs:register_mob(name, def) mobs.spawning_mobs[name] = true minetest.register_entity(name, { - stepheight = def.stepheight or 0.6, + stepheight = def.stepheight or 1.1, -- was 0.6 name = name, type = def.type, attack_type = def.attack_type, @@ -2282,8 +2421,7 @@ minetest.register_entity(name, { order = def.order or "", on_die = def.on_die, do_custom = def.do_custom, - jump_height = def.jump_height or 6, - jump_chance = def.jump_chance or 0, + jump_height = def.jump_height or 4, -- was 6 drawtype = def.drawtype, -- DEPRECATED, use rotate instead rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2 lifetimer = def.lifetimer or 180, -- 3 minutes @@ -2303,7 +2441,7 @@ minetest.register_entity(name, { water_damage = def.water_damage or 0, lava_damage = def.lava_damage or 0, fall_damage = def.fall_damage or 1, - fall_speed = def.fall_speed or -10, -- must be lower than -2 (mcl_core: -10) + fall_speed = def.fall_speed or -10, -- must be lower than -2 (default: -10) drops = def.drops or {}, armor = def.armor or 100, on_rightclick = def.on_rightclick, @@ -2312,11 +2450,10 @@ minetest.register_entity(name, { sounds = def.sounds or {}, animation = def.animation, follow = def.follow, - jump = def.jump or true, + jump = def.jump ~= false, walk_chance = def.walk_chance or 50, attacks_monsters = def.attacks_monsters or false, group_attack = def.group_attack or false, - --fov = def.fov or 120, passive = def.passive or false, recovery_time = def.recovery_time or 0.5, knock_back = def.knock_back or 3, @@ -2354,8 +2491,10 @@ minetest.register_entity(name, { dogshoot_switch = def.dogshoot_switch, dogshoot_count = 0, dogshoot_count_max = def.dogshoot_count_max or 5, + dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5), attack_animals = def.attack_animals or false, specific_attack = def.specific_attack, + owner_loyal = def.owner_loyal, on_blast = def.on_blast or do_tnt, @@ -2363,62 +2502,25 @@ minetest.register_entity(name, { on_punch = mob_punch, - on_activate = function(self, staticdata, dtime_s) - mob_activate(self, staticdata, dtime_s, def) + on_activate = function(self, staticdata) + return mob_activate(self, staticdata, def) end, get_staticdata = function(self) - - -- remove mob when out of range unless tamed - if remove_far - and self.remove_ok - and not self.tamed - and self.lifetimer < 20000 then - - --print ("REMOVED " .. self.name) - - self.object:remove() - - return nil - end - - self.remove_ok = true - self.attack = nil - self.following = nil - self.state = "stand" - - -- used to rotate older mobs - if self.drawtype - and self.drawtype == "side" then - self.rotate = math.rad(90) - end - - 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) + return mob_staticdata(self) end, }) end -- END mobs:register_mob function + -- count how many mobs of one type are inside an area local count_mobs = function(pos, type) - local num = 0 - local objs = minetest.get_objects_inside_radius(pos, 32) + local num_type = 0 + local num_total = 0 + local objs = minetest.get_objects_inside_radius(pos, aoc_range) for n = 1, #objs do @@ -2426,15 +2528,24 @@ local count_mobs = function(pos, type) local obj = objs[n]:get_luaentity() + -- count mob type and add to total also if obj and obj.name and obj.name == type then - num = num + 1 + + num_type = num_type + 1 + num_total = num_total + 1 + + -- add to total mobs + elseif obj and obj.name and obj.health ~= nil then + + num_total = num_total + 1 end end end - return num + return num_type, num_total end + -- global functions function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, @@ -2476,9 +2587,9 @@ function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, end -- do not spawn if too many of same mob in area - if active_object_count_wider >= aoc - and count_mobs(pos, name) >= aoc then ---print ("--- too many entities", name, aoc) + if active_object_count_wider >= max_per_block + or count_mobs(pos, name) >= aoc then +--print ("--- too many entities", name, aoc, active_object_count_wider) return end @@ -2523,6 +2634,13 @@ function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, return end + -- are we spawning within height limits? + if pos.y > max_height + or pos.y < min_height then +--print ("--- height limits not met", name, pos.y) + return + end + -- are light levels ok? local light = minetest.get_node_light(pos) if not light @@ -2532,13 +2650,6 @@ function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, return end - -- are we spawning within height limits? - if pos.y > max_height - or pos.y < min_height then ---print ("--- height limits not met", name, pos.y) - return - end - -- are we spawning inside solid nodes? if minetest.registered_nodes[node_ok(pos).name].walkable == true then --print ("--- feet in block", name, node_ok(pos).name) @@ -2573,6 +2684,7 @@ function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, }) end + -- compatibility with older mob registration function mobs:register_spawn(name, nodes, max_light, min_light, chance, active_object_count, max_height, day_toggle) @@ -2580,6 +2692,7 @@ function mobs:register_spawn(name, nodes, max_light, min_light, chance, active_o chance, active_object_count, -31000, max_height, day_toggle) end + -- MarkBu's spawn function function mobs:spawn(def) @@ -2600,10 +2713,12 @@ function mobs:spawn(def) chance, active_object_count, min_height, max_height, day_toggle, on_spawn) end + -- set content id's local c_air = minetest.get_content_id("air") local c_ignore = minetest.get_content_id("ignore") local c_obsidian = minetest.get_content_id("mcl_core:obsidian") +local c_brick = minetest.get_content_id("mcl_core:obsidian") local c_chest = minetest.get_content_id("mcl_core:chest") local c_fire = minetest.get_content_id("mcl_fire:fire") @@ -2653,6 +2768,7 @@ function mobs:explosion(pos, radius, fire, smoke, sound) and data[vi] ~= c_air and data[vi] ~= c_ignore and data[vi] ~= c_obsidian + and data[vi] ~= c_brick and data[vi] ~= c_chest and data[vi] ~= c_fire then @@ -2673,7 +2789,7 @@ function mobs:explosion(pos, radius, fire, smoke, sound) and (minetest.registered_nodes[n].groups.flammable or random(1, 100) <= 30) then - minetest.set_node(p, {name = "mcl_fire:fire"}) + minetest.set_node(p, {name = "fire:basic_flame"}) else minetest.set_node(p, {name = "air"}) @@ -2691,6 +2807,7 @@ function mobs:explosion(pos, radius, fire, smoke, sound) end end + -- register arrow for shoot attack function mobs:register_arrow(name, def) @@ -2711,6 +2828,9 @@ function mobs:register_arrow(name, def) timer = 0, switch = 0, owner_id = def.owner_id, + rotate = def.rotate, + automatic_face_movement_dir = def.rotate + and (def.rotate - (pi / 180)) or false, on_step = def.on_step or function(self, dtime) @@ -2732,25 +2852,15 @@ function mobs:register_arrow(name, def) and def.tail == 1 and def.tail_texture then --- effect(pos, 1, def.tail_texture, --- def.tail_size or 5, --- def.tail_size or 10, --- 0, 0) -- 0 radius and 0 gravity to just hover - - minetest.add_particlespawner({ - amount = 1, - time = 0.25, - minpos = pos, - maxpos = pos, - minvel = {x = 0, y = 0, z = 0}, - maxvel = {x = 0, y = 0, z = 0}, - minacc = {x = 0, y = 0, z = 0}, - maxacc = {x = 0, y = 0, z = 0}, - minexptime = 0.1, - maxexptime = 1, - minsize = def.tail_size or 5, - maxsize = def.tail_size or 10, + minetest.add_particle({ + pos = pos, + velocity = {x = 0, y = 0, z = 0}, + acceleration = {x = 0, y = 0, z = 0}, + expirationtime = def.expire or 0.25, + collisiondetection = false, texture = def.tail_texture, + size = def.tail_size or 5, + glow = def.glow or 0, }) end @@ -2759,7 +2869,6 @@ function mobs:register_arrow(name, def) local node = node_ok(pos).name if minetest.registered_nodes[node].walkable then - --if node ~= "air" then self.hit_node(self, pos, node) @@ -2816,7 +2925,8 @@ function mobs:register_arrow(name, def) }) end --- Spawn Egg + +-- register spawn eggs function mobs:register_egg(mob, desc, background, addegg, no_creative) local grp = {} @@ -2825,7 +2935,6 @@ function mobs:register_egg(mob, desc, background, addegg, no_creative) if creative and no_creative == true then grp = {not_in_creative_inventory = 1} end - grp.spawn_egg = 1 local invimg = background @@ -2834,29 +2943,76 @@ function mobs:register_egg(mob, desc, background, addegg, no_creative) "^[mask:mobs_chicken_egg_overlay.png)" end + -- register new spawn egg containing mob information + minetest.register_craftitem(mob .. "_set", { + + description = desc .. " (Tamed)", + inventory_image = invimg, + groups = {not_in_creative_inventory = 1}, + stack_max = 1, + + on_place = function(itemstack, placer, pointed_thing) + + local pos = pointed_thing.above + + -- am I clicking on something with existing on_rightclick function? + local under = minetest.get_node(pointed_thing.under) + local def = minetest.registered_nodes[under.name] + if def and def.on_rightclick then + return def.on_rightclick(pointed_thing.under, under, placer, itemstack) + end + + if pos + and within_limits(pos, 0) + and not minetest.is_protected(pos, placer:get_player_name()) then + + pos.y = pos.y + 1 + + local data = itemstack:get_metadata() + local mob = minetest.add_entity(pos, mob, data) + local ent = mob:get_luaentity() + + if not ent then + mob:remove() + return + end + + if ent.type ~= "monster" then + -- set owner and tame if not monster + ent.owner = placer:get_player_name() + ent.tamed = true + end + + -- since mob is unique we remove egg once spawned + itemstack:take_item() + end + + return itemstack + end, + }) + + + -- register old stackable mob egg minetest.register_craftitem(mob, { description = desc, - _doc_items_longdesc = "So called “spawn eggs” are items which are used to spawn a single monster or animal.", - _doc_items_usagehelp = "Place the spawn egg on any block to make a mob appear at this position.", inventory_image = invimg, groups = grp, on_place = function(itemstack, placer, pointed_thing) - -- Call on_rightclick if the pointed node defines it - local node = minetest.get_node(pointed_thing.under) - if placer and not placer:get_player_control().sneak then - if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then - return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack - end - end - local pos = pointed_thing.above + -- am I clicking on something with existing on_rightclick function? + local under = minetest.get_node(pointed_thing.under) + local def = minetest.registered_nodes[under.name] + if def and def.on_rightclick then + return def.on_rightclick(pointed_thing.under, under, placer, itemstack) + end + if pos and within_limits(pos, 0) - and (not placer or not minetest.is_protected(pos, placer:get_player_name())) then + and not minetest.is_protected(pos, placer:get_player_name()) then pos.y = pos.y + 1 @@ -2870,10 +3026,8 @@ function mobs:register_egg(mob, desc, background, addegg, no_creative) if ent.type ~= "monster" then -- set owner and tame if not monster - if placer then - ent.owner = placer:get_player_name() - ent.tamed = true - end + ent.owner = placer:get_player_name() + ent.tamed = true end -- if not in creative then take item @@ -2885,85 +3039,158 @@ function mobs:register_egg(mob, desc, background, addegg, no_creative) 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) - if not self.child - and clicker:is_player() - and clicker:get_inventory() then + if self.child + or not clicker:is_player() + or not clicker:get_inventory() then + return false + end - -- get name of clicked mob - local mobname = self.name + -- get name of clicked mob + local mobname = self.name + + -- if not nil change what will be added to inventory + if replacewith then + mobname = replacewith + end + + local name = clicker:get_player_name() + local tool = clicker:get_wielded_item() + + -- are we using hand, net or lasso to pick up mob? + if tool:get_name() ~= "" + and tool:get_name() ~= "mobs:net" + and tool:get_name() ~= "mobs:magic_lasso" then + return false + end + + -- is mob tamed? + if self.tamed == false + and force_take == false then + + minetest.chat_send_player(name, S("Not tamed!")) + + return true -- false + end + + -- cannot pick up if not owner + if self.owner ~= name + and force_take == false then + + minetest.chat_send_player(name, S("@1 is owner!", self.owner)) + + return true -- false + end + + if clicker:get_inventory():room_for_item("main", mobname) then + + -- was mob clicked with hand, net, or lasso? + local chance = 0 + + if tool:get_name() == "" then + chance = chance_hand + + elseif tool:get_name() == "mobs:net" then + + chance = chance_net + + tool:add_wear(4000) -- 17 uses + + clicker:set_wielded_item(tool) + + elseif tool:get_name() == "mobs:magic_lasso" then + + chance = chance_lasso + + tool:add_wear(650) -- 100 uses + + clicker:set_wielded_item(tool) - -- if not nil change what will be added to inventory - if replacewith then - mobname = replacewith end - local name = clicker:get_player_name() + -- calculate chance.. add to inventory if successful? + if chance > 0 and random(1, 100) <= chance then - -- is mob tamed? - if self.tamed == false - and force_take == false then + -- default mob egg + local new_stack = ItemStack(mobname) - minetest.chat_send_player(name, S("Not tamed!")) + -- add special mob egg with all mob information + -- unless 'replacewith' contains new item to use + if not replacewith then - return - end + new_stack = ItemStack(mobname .. "_set") - -- cannot pick up if not owner - if self.owner ~= name - and force_take == false then + local tmp = {} - minetest.chat_send_player(name, S("@1 is owner!", self.owner)) + for _,stat in pairs(self) do + local t = type(stat) + if t ~= "function" + and t ~= "nil" + and t ~= "userdata" then + tmp[_] = self[_] + end + end - return - end + local data_str = minetest.serialize(tmp) - 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 - chance = chance_hand - - elseif tool:get_name() == "mobs:net" then - - chance = chance_net - - tool:add_wear(4000) -- 17 uses - - clicker:set_wielded_item(tool) - - elseif tool:get_name() == "mobs:magic_lasso" then - - chance = chance_lasso - - tool:add_wear(650) -- 100 uses - - clicker:set_wielded_item(tool) + new_stack:set_metadata(data_str) end - -- return if no chance - if chance == 0 then return end + local inv = clicker:get_inventory() - -- calculate chance.. add to inventory if successful? - if random(1, 100) <= chance then - - clicker:get_inventory():add_item("main", mobname) - - self.object:remove() + if inv:room_for_item("main", new_stack) then + inv:add_item("main", new_stack) else - minetest.chat_send_player(name, S("Missed!")) + minetest.add_item(clicker:getpos(), new_stack) end + + self.object:remove() + + else + minetest.chat_send_player(name, S("Missed!")) end end + + return true end + +-- protect tamed mob with rune item +function mobs:protect(self, clicker) + + local name = clicker:get_player_name() + local tool = clicker:get_wielded_item() + + if tool:get_name() ~= "mobs:protector" then + return false + end + + if self.tamed == false then + minetest.chat_send_player(name, S("Not tamed!")) + return true -- false + end + + if self.protected == true then + minetest.chat_send_player(name, S("Already protected!")) + return true -- false + end + + tool:take_item() -- take 1 protection rune + clicker:set_wielded_item(tool) + + self.protected = true + minetest.chat_send_player(name, S("Protected!")) + + return true +end + + local mob_obj = {} local mob_sta = {} @@ -2996,6 +3223,10 @@ function mobs:feed_tame(self, clicker, feed_count, breed, tame) if self.htimer < 1 then + minetest.chat_send_player(clicker:get_player_name(), + S("@1 at full health (@2)", + self.name:split(":")[2], tostring(self.health))) + self.htimer = 5 end end @@ -3026,6 +3257,12 @@ function mobs:feed_tame(self, clicker, feed_count, breed, tame) if tame then + if self.tamed == false then + minetest.chat_send_player(clicker:get_player_name(), + S("@1 has been tamed!", + self.name:split(":")[2])) + end + self.tamed = true if not self.owner or self.owner == "" then @@ -3034,13 +3271,7 @@ function mobs:feed_tame(self, clicker, feed_count, breed, tame) 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 + mob_sound(self, self.sounds.random) end return true @@ -3072,6 +3303,7 @@ function mobs:feed_tame(self, clicker, feed_count, breed, tame) end + -- inspired by blockmen's nametag mod minetest.register_on_player_receive_fields(function(player, formname, fields) @@ -3087,14 +3319,13 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) return end - local mobname - if string.len(fields.name) > 80 then - minetest.chat_send_player(name, "Warning: Your chosen name is very long! It has been shortened to 80 characters.") - mobname = string.sub(fields.name, 1, 80) + -- limit name entered to 64 characters long + if string.len(fields.name) > 64 then + fields.name = string.sub(fields.name, 1, 64) end -- update nametag - mob_obj[name].nametag = mobname + mob_obj[name].nametag = fields.name update_tag(mob_obj[name]) @@ -3113,6 +3344,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end) + -- compatibility function for old entities to new modpack entities function mobs:alias_mob(old_name, new_name) diff --git a/mods/ENTITIES/mobs/api.txt b/mods/ENTITIES/mobs/api.txt index 3473b38274..43d0db72f7 100644 --- a/mods/ENTITIES/mobs/api.txt +++ b/mods/ENTITIES/mobs/api.txt @@ -1,5 +1,5 @@ -MOB API (28th September 2016) +MOB API (12th May 2017) The mob api is a function that can be called on by other mods to add new animals or monsters into minetest. @@ -12,7 +12,7 @@ The mob api is a function that can be called on by other mods to add new animals 'remove_far_mobs' if true then mobs that are outside players visual range will be removed (default is false) 'mobname' can change specific mob chance rate (0 to disable) and spawn number e.g. mobs_animal:cow = 1000,5 'mob_difficulty' sets difficulty level (health and hit damage multiplied by this number), defaults to 1.0. - + 'mob_show_health' if false then punching mob will not show health status (true by default) mobs:register_mob(name, definition) @@ -25,8 +25,9 @@ This functions registers a new mob as a Minetest entity. 'docile_by_day' when true, mob will not attack during daylight hours unless provoked 'attacks_monsters' usually for npc's to attack monsters in area 'group_attack' true to defend same kind of mobs from attack in area + 'owner_loyal' when true owned mobs will attack any monsters you punch 'attack_animals' true for monster to attack animals as well as player and npc's - 'attack_specific' has a table of entity names that monsters can attack {"player", "mobs_animal:chicken"} + 'specific_attack' has a table of entity names that monsters can attack {"player", "mobs_animal:chicken"} 'hp_min' minimum health 'hp_max' maximum health (mob health is randomly selected between both) 'physical' same is in minetest.register_entity() @@ -42,14 +43,13 @@ This functions registers a new mob as a Minetest entity. 'makes_footstep_sound' same is in minetest.register_entity() 'follow' item when held will cause mob to follow player, can be single string "default:apple" or table {"default:apple", "default:diamond"} 'view_range' the range in that the monster will see the playerand follow him - 'walk_chance' chance of mob walking around - 'jump_chance' chance of mob jumping around, set above to 0 for jumping mob only + 'walk_chance' chance of mob walking around, set above to 0 for jumping mob only 'walk_velocity' the velocity when the monster is walking around 'run_velocity' the velocity when the monster is attacking a player 'runaway' when true mob will turn and run away when punched 'stepheight' minimum node height mob can walk onto without jumping (default: 0.6) 'jump' can mob jump, true or false - 'jump_height' height mob can jump, default is 6 + 'jump_height' height mob can jump, default is 6 (0 to disable jump) 'fly' can mob fly, true or false (used for swimming mobs also) 'fly_in' node name that mob flys inside, e.g "air", "default:water_source" for fish 'damage' the damage per second @@ -82,8 +82,9 @@ This functions registers a new mob as a Minetest entity. 'shoot' shoots defined arrows when player is within range 'explode' follows player in range and will flash and explode when in reach 'dogshoot' shoots arrows when in range and one on one attack when in reach - 'dogshoot_switch' allows switching between shoot and dogfight modes inside dogshoot using timer (1 = shoot, 2 = dogfight) - 'dogshoot_count_max' number of seconds before switching above modes. + 'dogshoot_switch' allows switching between shoot and dogfight modes inside dogshoot using timer (1 = shoot, 2 = dogfight) + 'dogshoot_count_max' number of seconds before switching to dogfight mode. + 'dogshoot_count2_max' number of seconds before switching back to shoot mode. 'custom_attack' is a function that is called when mob is in range to attack player, parameters are (self, to_attack) 'double_melee_attack' if false then api will choose randomly between 'punch' and 'punch2' attack animations 'on_blast' is called when TNT explodes near mob, function uses (object, damage) and returns (do_damage, do_knockback, drops) @@ -102,29 +103,31 @@ This functions registers a new mob as a Minetest entity. 'jump' sound when jumping 'explode' sound when exploding 'distance' maximum distance sounds are heard from (default is 10) - 'animation' a table with the animation ranges and speed of the model - 'stand_start' start frame of stand animation - 'stand_end' end frame of stand animation - 'walk_start' start frame of walk animation - 'walk_end' end frame of walk animation - 'run_start' start frame of run animation - 'run_end' end frame of run animation - 'punch_start' start frame of punch animation - 'punch_end' end frame of punch animation - 'punch2_start' start frame of alt.punch animation - 'punch2_end' end frame of alt.punch animation - 'shoot_start' start frame of shoot animation - 'shoot_end' end frame of shoot animation - 'speed_normal' normal animation speed - 'speed_run' running animation speed - 'speed_punch' punching animation speed - 'speed_punch2' alternative punching animation speed - 'speed_shoot' shooting animation speed + +Mobs can look for specific nodes as they walk and replace them to mimic eating + 'replace_what' group if items to replace e.g. {"farming:wheat_8", "farming:carrot_8"} 'replace_with' replace with what e.g. "air" or in chickens case "mobs:egg" 'replace_rate' how random should the replace rate be (typically 10) 'replace_offset' +/- value to check specific node to replace +The 'replace_what' has been updated to use tables for what, with and y_offset e.g. + + replace_what = { {"group:grass", "air", 0}, {"default:dirt_with_grass", "default:dirt", -1} } + +Mob animation comes in three parts, start_frame, end_frame and frame_speed which +can be added to the mob definition under pre-defined mob animation names like: + + 'animation' a table with the animation ranges and speed of the model + 'stand_start', 'stand_end', 'stand_speed' when mob stands still + 'walk_start', 'walk_end', 'walk_speed' when mob walks + 'run_start', 'run_end', 'run_speed' when mob runs + 'fly_start', 'fly_end', 'fly_speed' when mob flies + 'punch_start', 'punch_end', 'punch_speed' when mob attacks + 'punch2_start', 'punch2_end', 'punch2_speed' when mob attacks (alternative) + 'die_start', 'die_end', 'die_speed' when mob dies +also 'speed_normal' for compatibility with older mobs for animation speed (deprecated) + The mob api also has some preset variables and functions that it will remember for each mob @@ -133,8 +136,9 @@ The mob api also has some preset variables and functions that it will remember f 'self.child' used for when breeding animals have child, will use child_texture and be half size 'self.owner' string used to set owner of npc mobs, typically used for dogs 'self.order' set to "follow" or "stand" so that npc will follow owner or stand it's ground + 'self.nametag' contains the name of the mob which it can show above 'on_die' a function that is called when mob is killed - 'do_custom' a custom function that is called while mob is active and which has access to all of the self.* variables e.g. (self.health for health or self.standing_in for node status), return with 'false' to skip remainder of mob API. + 'do_custom' a custom function that is called every tick while mob is active and which has access to all of the self.* variables e.g. (self.health for health or self.standing_in for node status), return with 'false' to skip remainder of mob API. mobs:register_spawn(name, nodes, max_light, min_light, chance, active_object_count, max_height, day_toggle) @@ -151,7 +155,7 @@ These functions register a spawn algorithm for the mob. Without this function th 'interval' is same as in register_abm() (default is 30 for mobs:register_spawn) 'chance' is same as in register_abm() 'active_object_count' mob is only spawned if active_object_count_wider of ABM is <= this - 'min_height' is the maximum height the mob can spawn + 'min_height' is the minimum height the mob can spawn 'max_height' is the maximum height the mob can spawn 'day_toggle' true for day spawning, false for night or nil for anytime 'on_spawn' is a custom function which runs after mob has spawned and gives self and pos values. @@ -191,6 +195,9 @@ This function registers a arrow for mobs with the attack type shoot. 'tail' when set to 1 adds a trail or tail to mob arrows 'tail_texture' texture string used for above effect 'tail_size' has size for above texture (defaults to between 5 and 10) + 'expire' contains float value for how long tail appears for (defaults to 0.25) + 'glow' has value for how brightly tail glows 1 to 10 (default is 0, no glow) + 'rotate' integer value in degrees to rotate arrow 'on_step' is a custom function when arrow is active, nil for default. @@ -226,12 +233,12 @@ This function is generally called inside the on_rightclick section of the mob ap 'chance_net' chance of capturing mob using net (1 to 100) 0 to disable 'chance_lasso' chance of capturing mob using magic lasso (1 to 100) 0 to disable 'force_take' take mob by force, even if tamed (true or false) - 'replacewith' once captured replace mob with this item instead + 'replacewith' once captured replace mob with this item instead (overrides new mob eggs with saved information) mobs:feed_tame(self, clicker, feed_count, breed) -This function allows the mob to be fed the item inside self.follow be it apple, wheat or whatever a set number of times and be tamed or bred as a result. +This function allows the mob to be fed the item inside self.follow be it apple, wheat or whatever a set number of times and be tamed or bred as a result. Will return true when mob is fed with item it likes. 'self' mob information 'clicker' player information @@ -242,7 +249,7 @@ This function allows the mob to be fed the item inside self.follow be it apple, mobs:protect(self, clicker) -This function can be used to right-click any tamed mob with mobs:protector item, this will protect the mob from harm inside of a protected area from other players. +This function can be used to right-click any tamed mob with mobs:protector item, this will protect the mob from harm inside of a protected area from other players. Will return true when mob right-clicked with mobs:protector item. 'self' mob information 'clicker' player information @@ -257,3 +264,184 @@ Useful Internal Variables 'self.gotten' true when sheep have been sheared or cows have been milked, a toggle switch which can be used for many functions 'self.child' true when mob is currently a child (when two mobs have bred and current mob is the outcome) 'self.hornytimer' background timer that controls breeding functions and mob childhood timings + + +Mobs can now be ridden by players and the following shows the functions and usage: + + +mobs:attach(self, player) + +This function attaches a player to the mob so it can be ridden. + + 'self' mob information + 'player' player information + + +mobs:detach(player, offset) + +This function will detach the player currently riding a mob to an offset position. + + 'player' player information + 'offset' position table containing offset values + + +mobs:drive(self, move_animation, stand_animation, can_fly, dtime) + +This function allows an attached player to move the mob around and animate it at same time. + + 'self' mob information + 'move_animation' string containing movement animation e.g. "walk" + 'stand_animation' string containing standing animation e.g. "stand" + 'can_fly' if true then jump and sneak controls will allow mob to fly up and down + 'dtime' tick time used inside drive function + + +mobs:fly(self, dtime, speed, can_shoot, arrow_entity, move_animation, stand_animation) + +This function allows an attached player to fly the mob around using directional controls. + + 'self' mob information + 'dtime' tick time used inside fly function + 'speed' speed of flight + 'can_shoot' true if mob can fire arrow (sneak and left mouse button fires) + 'arrow_entity' name of arrow entity used for firing + 'move_animation' string containing movement animation e.g. "walk" + 'stand_animation' string containing movement animation e.g. "stand" + + +Certain variables need to be set before using the above functions: + + 'self.v2' toggle switch + 'self.max_speed_forward' max speed mob can move forward + 'self.max_speed_reverse' max speed mob can move backwards + 'self.accel' acceleration speed + 'self.terrain_type' integer containing terrain mob can walk on (1 = water, 2 or 3 = land) + 'self.driver_attach_at' position offset for attaching player to mob + 'self.driver_eye_offset' position offset for attached player view + 'self.driver_scale' sets driver scale for mobs larger than {x=1, y=1} + + +Here is an example mob to show how the above functions work: + + +-- rideable horse +mobs:register_mob("mob_horse:horse", { + type = "animal", + visual = "mesh", + visual_size = {x = 1.20, y = 1.20}, + mesh = "mobs_horse.x", + collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.25, 0.4}, + animation = { + speed_normal = 15, + speed_run = 30, + stand_start = 25, + stand_end = 75, + walk_start = 75, + walk_end = 100, + run_start = 75, + run_end = 100, + }, + textures = { + {"mobs_horse.png"}, + {"mobs_horsepeg.png"}, + {"mobs_horseara.png"} + }, + fear_height = 3, + runaway = true, + fly = false, + walk_chance = 60, + view_range = 5, + follow = {"farming:wheat"}, + passive = true, + hp_min = 12, + hp_max = 16, + armor = 200, + lava_damage = 5, + fall_damage = 5, + water_damage = 1, + makes_footstep_sound = true, + drops = { + {name = "mobs:meat_raw", chance = 1, min = 2, max = 3} + }, + + do_custom = function(self, dtime) + + -- set needed values if not already present + if not self.v2 then + self.v2 = 0 + self.max_speed_forward = 6 + self.max_speed_reverse = 2 + self.accel = 6 + self.terrain_type = 3 + self.driver_attach_at = {x = 0, y = 20, z = -2} + self.driver_eye_offset = {x = 0, y = 3, z = 0} + self.driver_scale = {x = 1, y = 1} + end + + -- if driver present allow control of horse + if self.driver then + + mobs.drive(self, "walk", "stand", false, dtime) + + return false -- skip rest of mob functions + end + + return true + end, + + on_die = function(self, pos) + + -- drop saddle when horse is killed while riding + -- also detach from horse properly + if self.driver then + minetest.add_item(pos, "mobs:saddle") + mobs.detach(self.driver, {x = 1, y = 0, z = 1}) + end + + end, + + on_rightclick = function(self, clicker) + + -- make sure player is clicking + if not clicker or not clicker:is_player() then + return + end + + -- feed, tame or heal horse + if mobs:feed_tame(self, clicker, 10, true, true) then + return + end + + -- make sure tamed horse is being clicked by owner only + if self.tamed and self.owner == clicker:get_player_name() then + + local inv = clicker:get_inventory() + + -- detatch player already riding horse + if self.driver and clicker == self.driver then + + mobs.detach(clicker, {x = 1, y = 0, z = 1}) + + -- add saddle back to inventory + if inv:room_for_item("main", "mobs:saddle") then + inv:add_item("main", "mobs:saddle") + else + minetest.add_item(clicker.getpos(), "mobs:saddle") + end + + -- attach player to horse + elseif not self.driver + and clicker:get_wielded_item():get_name() == "mobs:saddle" then + + self.object:set_properties({stepheight = 1.1}) + mobs.attach(self, clicker) + + -- take saddle from inventory + inv:remove_item("main", "mobs:saddle") + end + end + + -- used to capture horse with magic lasso + mobs:capture_mob(self, clicker, 0, 0, 80, false, nil) + end +}) diff --git a/mods/ENTITIES/mobs/depends.txt b/mods/ENTITIES/mobs/depends.txt index 614b3facbe..de61bea552 100644 --- a/mods/ENTITIES/mobs/depends.txt +++ b/mods/ENTITIES/mobs/depends.txt @@ -1,6 +1,6 @@ mcl_core -mcl_sounds mcl_hunger +mcl_sounds invisibility? intllib? lucky_block? diff --git a/mods/ENTITIES/mobs/init.lua b/mods/ENTITIES/mobs/init.lua index 6c6908bbe9..2d8d01c6df 100644 --- a/mods/ENTITIES/mobs/init.lua +++ b/mods/ENTITIES/mobs/init.lua @@ -4,11 +4,14 @@ local path = minetest.get_modpath("mobs") -- Mob API dofile(path .. "/api.lua") +-- Rideable Mobs +dofile(path .. "/mount.lua") + -- Mob Items dofile(path .. "/crafts.lua") -- Mob Spawner --- moved into other mod (MCL2 change) +-- MCL2 has its own spawners in mcl_mobspawners -- Lucky Blocks dofile(path .. "/lucky_block.lua") diff --git a/mods/ENTITIES/mobs/locale/de.txt b/mods/ENTITIES/mobs/locale/de.txt index 08a84e6bdc..aa7a7a7a9f 100644 --- a/mods/ENTITIES/mobs/locale/de.txt +++ b/mods/ENTITIES/mobs/locale/de.txt @@ -21,7 +21,7 @@ Enter name: = Namen eingeben: Rename = Umbenennen #crafts.lua -Name Tag = Namensschild +Nametag = Namensschild Leather = Leder Raw Meat = Rohes Fleisch Meat = Fleisch @@ -30,9 +30,9 @@ Net (right-click animal to put in inventory) = Netz (Rechtsklick auf Tier,\num e Steel Shears (right-click to shear) = Stahlschere (Rechtsklick zum Scheren) #spawner.lua -Monster Spawner = Monsterspawner +Mob Spawner = Mobspawner Mob MinLight MaxLight Amount PlayerDist = Mob MinLicht MaxLicht Menge SpielerEntfng Spawner Not Active (enter settings) = Spawner nicht aktiv (Einstellungen eintragen) Spawner Active (@1) = Spawner aktiv (@1) Mob Spawner settings failed! = Mobspawnereinstellungen gescheitert! -> name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] distance[1-20] y_offset[-10 to 10] = Name min. Licht[0-14] max. Licht[0-14] max. Mobs in Gebiet[0 zum deaktivieren] Entfernung zum Spieler[1-20] Höhe[-10 bis 10] +> name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] distance[1-20] y_offset[-10 to 10] = Name min. Licht[0-14] max. Licht[0-14] max. Mobs in Gebiet[0 zum deaktivieren] Entfernung zum Spieler[1-20] Höhe[-10 bis 10] \ No newline at end of file diff --git a/mods/ENTITIES/mobs/locale/pt.txt b/mods/ENTITIES/mobs/locale/pt.txt index f80250bfdf..064631fe39 100644 --- a/mods/ENTITIES/mobs/locale/pt.txt +++ b/mods/ENTITIES/mobs/locale/pt.txt @@ -21,7 +21,7 @@ Enter name: = Insira um nome: Rename = Renomear #crafts.lua -Name Tag = Etiqueta +Nametag = Etiqueta Leather = Couro Raw Meat = Carne crua Meat = Carne @@ -30,7 +30,7 @@ Net (right-click animal to put in inventory) = Net (clique-direito no animal par Steel Shears (right-click to shear) = Tesoura de Aço (clique-direito para tosquiar) #spawner.lua -Monster Spawner = +Mob Spawner = Spawnador de Mob Mob MinLight MaxLight Amount PlayerDist = Mob LuzMinima LuzMaxima Valor DistJogador Spawner Not Active (enter settings) = Spawnador Inativo (configurar) Spawner Active (@1) = Spawnador Ativo (@1) diff --git a/mods/ENTITIES/mobs/locale/template.txt b/mods/ENTITIES/mobs/locale/template.txt index a96d390d4d..c263ce97da 100644 --- a/mods/ENTITIES/mobs/locale/template.txt +++ b/mods/ENTITIES/mobs/locale/template.txt @@ -19,7 +19,7 @@ Enter name: = Rename = #crafts.lua -Name Tag = +Nametag = Leather = Raw Meat = Meat = @@ -28,9 +28,9 @@ Net (right-click animal to put in inventory) = Steel Shears (right-click to shear) = #spawner.lua -Monster Spawner = +Mob Spawner = Mob MinLight MaxLight Amount PlayerDist = Spawner Not Active (enter settings) = Spawner Active (@1) = Mob Spawner settings failed! = -> name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] distance[1-20] y_offset[-10 to 10] = +> name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] distance[1-20] y_offset[-10 to 10] = \ No newline at end of file diff --git a/mods/ENTITIES/mobs/locale/tr.txt b/mods/ENTITIES/mobs/locale/tr.txt new file mode 100644 index 0000000000..3f2833e23d --- /dev/null +++ b/mods/ENTITIES/mobs/locale/tr.txt @@ -0,0 +1,39 @@ +# Türkçe çeviri by Admicos +# Turkish translation by Admicos + +# Son düzenleme: 26 Nisan 2017 +# Last edit: 26 April 2017 + +#init.lua +[MOD] Mobs Redo loaded = [MOD] Mobs Red yüklendi + +#api.lua +[MOBS] mod profiling enabled, damage not enabled = [MOBS] profilleme açık, zarar kapalı +lifetimer expired, removed @1 = Can zamanlayıcısı bitti, @1 silindi +[Mobs Redo] @1 has spawning disabled = @1 yaratılması kapandı +[Mobs Redo] Chance setting for @1 is now @2 = [Mobs Redo] @1'in şans ayarı şimdi @2 +[mobs] @1 failed to spawn at @2 = @1, @2'de yaratılamadı +Not tamed! = Evcil değil! +@1 is owner! = Sahibi @1! +Missed! = Kaçırdın! +@1 at full health (@2) = @1 tam canında (@2) +@1 has been tamed! = @1 tamamen evcilleştirilmiştir! +Enter name: = İsim gir: +Rename = Yeniden adlandır + +#crafts.lua +Nametag = İsim etiketi +Leather = Deri +Raw Meat = Çiğ et +Meat = Et +Magic Lasso (right-click animal to put in inventory) = Sihirli kement (hayvana sağ tıklayarak envantere koy) +Net (right-click animal to put in inventory) = Ağ (hayvana sağ tıklayarak envantere koy) +Steel Shears (right-click to shear) = Çelik makas (sağ tıklayarak kes) + +#spawner.lua +Mob Spawner = Canavar Yaratıcı +Mob MinLight MaxLight Amount PlayerDist = Mob MinIşık MaxIşık Miktar OyuncuMesafesi +Spawner Not Active (enter settings) = Yaratıcı aktif değil (ayarlara gir) +Spawner Active (@1) = Yaratıcı aktif (@1) +Mob Spawner settings failed! = Yaratıcı ayarları uygulanamadı. +> name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] distance[1-20] y_offset[-10 to 10] = > isim min_isik[0-14] max_isik[0-14] alandaki_max_canavar_sayisi[kapatmak icin 0] mesafe[1-20] y_cikinti[-10 ve 10 arası] \ No newline at end of file diff --git a/mods/ENTITIES/mobs/lucky_block.lua b/mods/ENTITIES/mobs/lucky_block.lua index e4001f4031..0c2794f6ac 100644 --- a/mods/ENTITIES/mobs/lucky_block.lua +++ b/mods/ENTITIES/mobs/lucky_block.lua @@ -2,7 +2,14 @@ if minetest.get_modpath("lucky_block") then lucky_block:add_blocks({ + {"dro", {"mobs:meat_raw"}, 5}, + {"dro", {"mobs:meat"}, 5}, {"dro", {"mobs:nametag"}, 1}, + {"dro", {"mobs:leather"}, 5}, + {"dro", {"mobs:net"}, 1}, + {"dro", {"mobs:magic_lasso"}, 1}, + {"dro", {"mobs:shears"}, 1}, + {"dro", {"mobs:protector"}, 1}, {"lig"}, }) end diff --git a/mods/ENTITIES/mobs/mount.lua b/mods/ENTITIES/mobs/mount.lua new file mode 100644 index 0000000000..0482dfca69 --- /dev/null +++ b/mods/ENTITIES/mobs/mount.lua @@ -0,0 +1,437 @@ + +-- lib_mount by Blert2112 (edited by TenPlus1) + +local enable_crash = true +local crash_threshold = 6.5 -- ignored if enable_crash=false + +------------------------------------------------------------------------------ + +-- +-- Helper functions +-- + +local function node_is(pos) + + local node = minetest.get_node(pos) + + if node.name == "air" then + return "air" + end + + if minetest.get_item_group(node.name, "lava") ~= 0 then + return "lava" + end + + if minetest.get_item_group(node.name, "liquid") ~= 0 then + return "liquid" + end + + if minetest.get_item_group(node.name, "walkable") ~= 0 then + return "walkable" + end + + return "other" +end + + +local function get_sign(i) + + i = i or 0 + + if i == 0 then + return 0 + else + return i / math.abs(i) + end +end + + +local function get_velocity(v, yaw, y) + + local x = -math.sin(yaw) * v + local z = math.cos(yaw) * v + + return {x = x, y = y, z = z} +end + + +local function get_v(v) + return math.sqrt(v.x * v.x + v.z * v.z) +end + + +local function force_detach(player) + + local attached_to = player:get_attach() + + if not attached_to then + return + end + + local entity = attached_to:get_luaentity() + + if entity.driver + and entity.driver == player then + + entity.driver = nil + end + + player:set_detach() + mcl_player.player_attached[player:get_player_name()] = false + player:set_eye_offset({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0}) + mcl_player.player_set_animation(player, "stand" , 30) + player:set_properties({visual_size = {x = 1, y = 1} }) + +end + +------------------------------------------------------------------------------- + + +minetest.register_on_leaveplayer(function(player) + force_detach(player) +end) + +minetest.register_on_shutdown(function() + local players = minetest.get_connected_players() + for i = 1, #players do + force_detach(players[i]) + end +end) + +minetest.register_on_dieplayer(function(player) + force_detach(player) + return true +end) + +------------------------------------------------------------------------------- + +function mobs.attach(entity, player) + + local attach_at, eye_offset = {}, {} + + entity.player_rotation = entity.player_rotation or {x = 0, y = 0, z = 0} + entity.driver_attach_at = entity.driver_attach_at or {x = 0, y = 0, z = 0} + entity.driver_eye_offset = entity.driver_eye_offset or {x = 0, y = 0, z = 0} + entity.driver_scale = entity.driver_scale or {x = 1, y = 1} + + local rot_view = 0 + + if entity.player_rotation.y == 90 then + rot_view = math.pi/2 + end + + attach_at = entity.driver_attach_at + eye_offset = entity.driver_eye_offset + entity.driver = player + + force_detach(player) + + player:set_attach(entity.object, "", attach_at, entity.player_rotation) + mcl_player.player_attached[player:get_player_name()] = true + player:set_eye_offset(eye_offset, {x = 0, y = 0, z = 0}) + + player:set_properties({ + visual_size = { + x = entity.driver_scale.x, + y = entity.driver_scale.y + } + }) + + minetest.after(0.2, function() + mcl_player.player_set_animation(player, "sit" , 30) + end) + + --player:set_look_yaw(entity.object:getyaw() - rot_view) + player:set_look_horizontal(entity.object:getyaw() - rot_view) +end + + +function mobs.detach(player, offset) + + force_detach(player) + + mcl_player.player_set_animation(player, "stand" , 30) + + local pos = player:getpos() + + pos = {x = pos.x + offset.x, y = pos.y + 0.2 + offset.y, z = pos.z + offset.z} + + minetest.after(0.1, function() + player:setpos(pos) + end) +end + + +function mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime) + + local rot_steer, rot_view = math.pi/2, 0 + + if entity.player_rotation.y == 90 then + rot_steer, rot_view = 0, math.pi/2 + end + + local acce_y = 0 + local velo = entity.object:getvelocity() + + entity.v = get_v(velo) * get_sign(entity.v) + + -- process controls + if entity.driver then + +--print ("---velo", get_v(velo)) + + local ctrl = entity.driver:get_player_control() + + -- move forwards + if ctrl.up then + + entity.v = entity.v + entity.accel / 10 + + -- move backwards + elseif ctrl.down then + + if entity.max_speed_reverse == 0 and entity.v == 0 then + return + end + + entity.v = entity.v - entity.accel / 10 + end + + -- fix mob rotation +-- entity.object:setyaw(entity.driver:get_look_yaw() - entity.rotate) + entity.object:setyaw(entity.driver:get_look_horizontal() - entity.rotate) + + if can_fly then + + -- fly up + if ctrl.jump then + velo.y = velo.y + 1 + if velo.y > entity.accel then velo.y = entity.accel end + + elseif velo.y > 0 then + velo.y = velo.y - 0.1 + if velo.y < 0 then velo.y = 0 end + end + + -- fly down + if ctrl.sneak then + velo.y = velo.y - 1 + if velo.y < -entity.accel then velo.y = -entity.accel end + + elseif velo.y < 0 then + velo.y = velo.y + 0.1 + if velo.y > 0 then velo.y = 0 end + end + + else + + -- jump + if ctrl.jump then + + if velo.y == 0 then + velo.y = velo.y + entity.jump_height + acce_y = acce_y + (acce_y * 3) + 1 + end + end + + end + end + + -- if not moving then set animation and return + if entity.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then + + if stand_anim then + set_animation(entity, stand_anim) + end + + return + end + + -- set moving animation + if moving_anim then + set_animation(entity, moving_anim) + end + + -- Stop! + local s = get_sign(entity.v) + + entity.v = entity.v - 0.02 * s + + if s ~= get_sign(entity.v) then + + entity.object:setvelocity({x = 0, y = 0, z = 0}) + entity.v = 0 + return + end + + -- enforce speed limit forward and reverse + local max_spd = entity.max_speed_reverse + + if get_sign(entity.v) >= 0 then + max_spd = entity.max_speed_forward + end + + if math.abs(entity.v) > max_spd then + entity.v = entity.v - get_sign(entity.v) + end + + -- Set position, velocity and acceleration + local p = entity.object:getpos() + local new_velo = {x = 0, y = 0, z = 0} + local new_acce = {x = 0, y = -9.8, z = 0} + + p.y = p.y - 0.5 + + local ni = node_is(p) + local v = entity.v + + if ni == "air" then + + if can_fly == true then + new_acce.y = 0 + end + + elseif ni == "liquid" or ni == "lava" then + + if ni == "lava" and entity.lava_damage ~= 0 then + + entity.lava_counter = (entity.lava_counter or 0) + dtime + + if entity.lava_counter > 1 then + + minetest.sound_play("default_punch", { + object = entity.object, + max_hear_distance = 5 + }) + + entity.object:punch(entity.object, 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = entity.lava_damage} + }, nil) + + entity.lava_counter = 0 + end + end + + if entity.terrain_type == 2 + or entity.terrain_type == 3 then + + new_acce.y = 0 + p.y = p.y + 1 + + if node_is(p) == "liquid" then + + if velo.y >= 5 then + velo.y = 5 + elseif velo.y < 0 then + new_acce.y = 20 + else + new_acce.y = 5 + end + else + if math.abs(velo.y) < 1 then + local pos = entity.object:getpos() + pos.y = math.floor(pos.y) + 0.5 + entity.object:setpos(pos) + velo.y = 0 + end + end + else + v = v * 0.25 + end + end + + new_velo = get_velocity(v, entity.object:getyaw() - rot_view, velo.y) + new_acce.y = new_acce.y + acce_y + + entity.object:setvelocity(new_velo) + entity.object:setacceleration(new_acce) + + -- CRASH! + if enable_crash then + + local intensity = entity.v2 - v + + if intensity >= crash_threshold then + +--print("----------- crash", intensity) + + entity.object:punch(entity.object, 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = intensity} + }, nil) + + end + end + + entity.v2 = v +end + + +-- directional flying routine by D00Med (edited by TenPlus1) + +function mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim) + + local ctrl = entity.driver:get_player_control() + local velo = entity.object:getvelocity() + local dir = entity.driver:get_look_dir() +-- local yaw = entity.driver:get_look_yaw() + local yaw = entity.driver:get_look_horizontal() + 1.57 -- offset fix between old and new commands + local rot_steer, rot_view = math.pi / 2, 0 + + if entity.player_rotation.y == 90 then + rot_steer, rot_view = 0, math.pi / 2 + end + + if ctrl.up then + entity.object:setvelocity({ + x = dir.x * speed, + y = dir.y * speed + 2, + z = dir.z * speed + }) + + elseif ctrl.down then + entity.object:setvelocity({ + x = -dir.x * speed, + y = dir.y * speed + 2, + z = -dir.z * speed + }) + + elseif not ctrl.down or ctrl.up or ctrl.jump then + entity.object:setvelocity({x = 0, y = -2, z = 0}) + end + + entity.object:setyaw(yaw + math.pi + math.pi / 2 - entity.rotate) + + -- firing arrows + if ctrl.LMB and ctrl.sneak and shoots then + + local pos = entity.object:getpos() + local obj = minetest.add_entity({ + x = pos.x + 0 + dir.x * 2.5, + y = pos.y + 1.5 + dir.y, + z = pos.z + 0 + dir.z * 2.5}, arrow) + + local ent = obj:get_luaentity() + if ent then + ent.switch = 1 -- for mob specific arrows + ent.owner_id = tostring(entity.object) -- so arrows dont hurt entity you are riding + local vec = {x = dir.x * 6, y = dir.y * 6, z = dir.z * 6} +-- local yaw = entity.driver:get_look_yaw() + local yaw = entity.driver:get_look_horizontal() + obj:setyaw(yaw + math.pi / 2) + obj:setvelocity(vec) + else + obj:remove() + end + end + + -- change animation if stopped + if velo.x == 0 and velo.y == 0 and velo.z == 0 then + + set_animation(entity, stand_anim) + else + -- moving animation + set_animation(entity, moving_anim) + end +end diff --git a/mods/ENTITIES/mobs/readme.MD b/mods/ENTITIES/mobs/readme.MD index e48c9a562a..7eae00751b 100644 --- a/mods/ENTITIES/mobs/readme.MD +++ b/mods/ENTITIES/mobs/readme.MD @@ -22,7 +22,9 @@ Lucky Blocks: 9 Changelog: - +- 1.35- Added owner_loyal flag for owned mobs to attack player enemies, also fixed group_attack +- 1.34- Added function to fly mob using directional movement (thanks D00Med for flying code) +- 1.33- Added functions to mount ride mobs (mobs.attach, mobs.detach, mobs.drive) many thanks to Blert2112 - 1.32- Added new spawn check to count specific mobs AND new minetest.conf setting to chance spawn chance and numbers, added ability to protect tamed mobs - 1.31- Added 'attack_animals' and 'specific_attack' flags for custom monster attacks, also 'mob_difficulty' .conf setting to make mobs harder. - 1.30- Added support for invisibility mod (mobs cant attack what they cant see), tweaked and tidied code