daily pastebin goal
52%
SHARE
TWEET

Untitled

a guest Jun 26th, 2018 82 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1.  
  2. -- Mobs Api
  3.  
  4. mobs = {}
  5. mobs.mod = "redo"
  6. mobs.version = "20180626"
  7.  
  8.  
  9. -- Intllib
  10. local MP = minetest.get_modpath(minetest.get_current_modname())
  11. local S, NS = dofile(MP .. "/intllib.lua")
  12. mobs.intllib = S
  13.  
  14.  
  15. -- CMI support check
  16. local use_cmi = minetest.global_exists("cmi")
  17.  
  18.  
  19. -- Invisibility mod check
  20. mobs.invis = {}
  21. if minetest.global_exists("invisibility") then
  22.     mobs.invis = invisibility
  23. end
  24.  
  25.  
  26. -- creative check
  27. local creative_mode_cache = minetest.settings:get_bool("creative_mode")
  28. function mobs.is_creative(name)
  29.     return creative_mode_cache or minetest.check_player_privs(name, {creative = true})
  30. end
  31.  
  32.  
  33. -- localize math functions
  34. local pi = math.pi
  35. local square = math.sqrt
  36. local sin = math.sin
  37. local cos = math.cos
  38. local abs = math.abs
  39. local min = math.min
  40. local max = math.max
  41. local atann = math.atan
  42. local random = math.random
  43. local floor = math.floor
  44. local atan = function(x)
  45.     if not x or x ~= x then
  46.         --error("atan bassed NaN")
  47.         return 0
  48.     else
  49.         return atann(x)
  50.     end
  51. end
  52.  
  53.  
  54. -- Load settings
  55. local damage_enabled = minetest.settings:get_bool("enable_damage")
  56. local mobs_spawn = minetest.settings:get_bool("mobs_spawn") ~= false
  57. local peaceful_only = minetest.settings:get_bool("only_peaceful_mobs")
  58. local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
  59. local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
  60. local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
  61. local creative = minetest.settings:get_bool("creative_mode")
  62. local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
  63. local remove_far = minetest.settings:get_bool("remove_far_mobs") ~= false
  64. local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0
  65. local show_health = minetest.settings:get_bool("mob_show_health") ~= false
  66. local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 99)
  67. local mob_chance_multiplier = tonumber(minetest.settings:get("mob_chance_multiplier") or 1)
  68.  
  69. -- Peaceful mode message so players will know there are no monsters
  70. if peaceful_only then
  71.     minetest.register_on_joinplayer(function(player)
  72.         minetest.chat_send_player(player:get_player_name(),
  73.             S("** Peaceful Mode Active - No Monsters Will Spawn"))
  74.     end)
  75. end
  76.  
  77. -- calculate aoc range for mob count
  78. local aosrb = tonumber(minetest.settings:get("active_object_send_range_blocks"))
  79. local abr = tonumber(minetest.settings:get("active_block_range"))
  80. local aoc_range = max(aosrb, abr) * 16
  81.  
  82. -- pathfinding settings
  83. local enable_pathfinding = true
  84. local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
  85. local stuck_path_timeout = 10 -- how long will mob follow path before giving up
  86.  
  87. -- default nodes
  88. local node_fire = "fire:basic_flame"
  89. local node_permanent_flame = "fire:permanent_flame"
  90. local node_ice = "default:ice"
  91. local node_snowblock = "default:snowblock"
  92. local node_snow = "default:snow"
  93. mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "default:dirt"
  94.  
  95.  
  96. -- play sound
  97. local mob_sound = function(self, sound)
  98.  
  99.     if sound then
  100.         minetest.sound_play(sound, {
  101.             object = self.object,
  102.             gain = 1.0,
  103.             max_hear_distance = self.sounds.distance
  104.         })
  105.     end
  106. end
  107.  
  108.  
  109. -- attack player/mob
  110. local do_attack = function(self, player)
  111.  
  112.     if self.state == "attack" then
  113.         return
  114.     end
  115.  
  116.     self.attack = player
  117.     self.state = "attack"
  118.  
  119.     if random(0, 100) < 90 then
  120.         mob_sound(self, self.sounds.war_cry)
  121.     end
  122. end
  123.  
  124.  
  125. -- move mob in facing direction
  126. local set_velocity = function(self, v)
  127.  
  128.     -- do not move if mob has been ordered to stay
  129.     if self.order == "stand" then
  130.         self.object:set_velocity({x = 0, y = 0, z = 0})
  131.         return
  132.     end
  133.  
  134.     local yaw = (self.object:get_yaw() or 0) + self.rotate
  135.  
  136.     self.object:set_velocity({
  137.         x = sin(yaw) * -v,
  138.         y = self.object:get_velocity().y,
  139.         z = cos(yaw) * v
  140.     })
  141. end
  142.  
  143.  
  144. -- calculate mob velocity
  145. local get_velocity = function(self)
  146.  
  147.     local v = self.object:get_velocity()
  148.  
  149.     return (v.x * v.x + v.z * v.z) ^ 0.5
  150. end
  151.  
  152.  
  153. -- set and return valid yaw
  154. local set_yaw = function(self, yaw, delay)
  155.  
  156.     if not yaw or yaw ~= yaw then
  157.         yaw = 0
  158.     end
  159.  
  160.     delay = delay or 0
  161.  
  162.     if delay == 0 then
  163.         self.object:set_yaw(yaw)
  164.         return yaw
  165.     end
  166.  
  167.     self.target_yaw = yaw
  168.     self.delay = delay
  169.  
  170.     return self.target_yaw
  171. end
  172.  
  173. -- global function to set mob yaw
  174. function mobs:yaw(self, yaw, delay)
  175.     set_yaw(self, yaw, delay)
  176. end
  177.  
  178.  
  179. -- set defined animation
  180. local set_animation = function(self, anim)
  181.  
  182.     if not self.animation
  183.     or not anim then return end
  184.  
  185.     self.animation.current = self.animation.current or ""
  186.  
  187.     if anim == self.animation.current
  188.     or not self.animation[anim .. "_start"]
  189.     or not self.animation[anim .. "_end"] then
  190.         return
  191.     end
  192.  
  193.     self.animation.current = anim
  194.  
  195.     self.object:set_animation({
  196.         x = self.animation[anim .. "_start"],
  197.         y = self.animation[anim .. "_end"]},
  198.         self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
  199.         0, self.animation[anim .. "_loop"] ~= false)
  200. end
  201.  
  202.  
  203. -- above function exported for mount.lua
  204. function mobs:set_animation(self, anim)
  205.     set_animation(self, anim)
  206. end
  207.  
  208.  
  209. -- calculate distance
  210. local get_distance = function(a, b)
  211.  
  212.     local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
  213.  
  214.     return square(x * x + y * y + z * z)
  215. end
  216.  
  217.  
  218. -- check line of sight (BrunoMine)
  219. local line_of_sight = function(self, pos1, pos2, stepsize)
  220.  
  221.     stepsize = stepsize or 1
  222.  
  223.     local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
  224.  
  225.     -- normal walking and flying mobs can see you through air
  226.     if s == true then
  227.         return true
  228.     end
  229.  
  230.     -- New pos1 to be analyzed
  231.     local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
  232.  
  233.     local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
  234.  
  235.     -- Checks the return
  236.     if r == true then return true end
  237.  
  238.     -- Nodename found
  239.     local nn = minetest.get_node(pos).name
  240.  
  241.     -- Target Distance (td) to travel
  242.     local td = get_distance(pos1, pos2)
  243.  
  244.     -- Actual Distance (ad) traveled
  245.     local ad = 0
  246.  
  247.     -- It continues to advance in the line of sight in search of a real
  248.     -- obstruction which counts as 'normal' nodebox.
  249.     while minetest.registered_nodes[nn]
  250.     and (minetest.registered_nodes[nn].walkable == false
  251.     or minetest.registered_nodes[nn].drawtype == "nodebox") do
  252.  
  253.         -- Check if you can still move forward
  254.         if td < ad + stepsize then
  255.             return true -- Reached the target
  256.         end
  257.  
  258.         -- Moves the analyzed pos
  259.         local d = get_distance(pos1, pos2)
  260.  
  261.         npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x
  262.         npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y
  263.         npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z
  264.  
  265.         -- NaN checks
  266.         if d == 0
  267.         or npos1.x ~= npos1.x
  268.         or npos1.y ~= npos1.y
  269.         or npos1.z ~= npos1.z then
  270.             return false
  271.         end
  272.  
  273.         ad = ad + stepsize
  274.  
  275.         -- scan again
  276.         r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
  277.  
  278.         if r == true then return true end
  279.  
  280.         -- New Nodename found
  281.         nn = minetest.get_node(pos).name
  282.  
  283.     end
  284.  
  285.     return false
  286. end
  287.  
  288.  
  289. -- are we flying in what we are suppose to? (taikedz)
  290. local flight_check = function(self, pos_w)
  291.  
  292.     local def = minetest.registered_nodes[self.standing_in]
  293.  
  294.     if not def then return false end -- nil check
  295.  
  296.     if type(self.fly_in) == "string"
  297.     and self.standing_in == self.fly_in then
  298.  
  299.         return true
  300.  
  301.     elseif type(self.fly_in) == "table" then
  302.  
  303.         for _,fly_in in pairs(self.fly_in) do
  304.  
  305.             if self.standing_in == fly_in then
  306.  
  307.                 return true
  308.             end
  309.         end
  310.     end
  311.  
  312.     -- stops mobs getting stuck inside stairs and plantlike nodes
  313.     if def.drawtype ~= "airlike"
  314.     and def.drawtype ~= "liquid"
  315.     and def.drawtype ~= "flowingliquid" then
  316.         return true
  317.     end
  318.  
  319.     return false
  320. end
  321.  
  322.  
  323. -- custom particle effects
  324. local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
  325.  
  326.     radius = radius or 2
  327.     min_size = min_size or 0.5
  328.     max_size = max_size or 1
  329.     gravity = gravity or -10
  330.     glow = glow or 0
  331.  
  332.     minetest.add_particlespawner({
  333.         amount = amount,
  334.         time = 0.25,
  335.         minpos = pos,
  336.         maxpos = pos,
  337.         minvel = {x = -radius, y = -radius, z = -radius},
  338.         maxvel = {x = radius, y = radius, z = radius},
  339.         minacc = {x = 0, y = gravity, z = 0},
  340.         maxacc = {x = 0, y = gravity, z = 0},
  341.         minexptime = 0.1,
  342.         maxexptime = 1,
  343.         minsize = min_size,
  344.         maxsize = max_size,
  345.         texture = texture,
  346.         glow = glow,
  347.     })
  348. end
  349.  
  350.  
  351. -- update nametag colour
  352. local update_tag = function(self)
  353.  
  354.     local col = "#00FF00"
  355.     local qua = self.hp_max / 4
  356.  
  357.     if self.health <= floor(qua * 3) then
  358.         col = "#FFFF00"
  359.     end
  360.  
  361.     if self.health <= floor(qua * 2) then
  362.         col = "#FF6600"
  363.     end
  364.  
  365.     if self.health <= floor(qua) then
  366.         col = "#FF0000"
  367.     end
  368.  
  369.     self.object:set_properties({
  370.         nametag = self.nametag,
  371.         nametag_color = col
  372.     })
  373.  
  374. end
  375.  
  376.  
  377. -- drop items
  378. local item_drop = function(self, cooked)
  379.  
  380.     -- no drops if disabled by setting
  381.     if not mobs_drop_items then return end
  382.  
  383.     -- no drops for child mobs
  384.     if self.child then return end
  385.  
  386.     local obj, item, num
  387.     local pos = self.object:get_pos()
  388.  
  389.     self.drops = self.drops or {} -- nil check
  390.  
  391.     for n = 1, #self.drops do
  392.  
  393.         if random(1, self.drops[n].chance) == 1 then
  394.  
  395.             num = random(self.drops[n].min or 1, self.drops[n].max or 1)
  396.             item = self.drops[n].name
  397.  
  398.             -- cook items when true
  399.             if cooked then
  400.  
  401.                 local output = minetest.get_craft_result({
  402.                     method = "cooking", width = 1, items = {item}})
  403.  
  404.                 if output and output.item and not output.item:is_empty() then
  405.                     item = output.item:get_name()
  406.                 end
  407.             end
  408.  
  409.             -- add item if it exists
  410.             obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
  411.  
  412.             if obj and obj:get_luaentity() then
  413.  
  414.                 obj:set_velocity({
  415.                     x = random(-10, 10) / 9,
  416.                     y = 6,
  417.                     z = random(-10, 10) / 9,
  418.                 })
  419.             elseif obj then
  420.                 obj:remove() -- item does not exist
  421.             end
  422.         end
  423.     end
  424.  
  425.     self.drops = {}
  426. end
  427.  
  428.  
  429. -- check if mob is dead or only hurt
  430. local check_for_death = function(self, cause, cmi_cause)
  431.  
  432.     -- has health actually changed?
  433.     if self.health == self.old_health and self.health > 0 then
  434.         return
  435.     end
  436.  
  437.     self.old_health = self.health
  438.  
  439.     -- still got some health? play hurt sound
  440.     if self.health > 0 then
  441.  
  442.         mob_sound(self, self.sounds.damage)
  443.  
  444.         -- make sure health isn't higher than max
  445.         if self.health > self.hp_max then
  446.             self.health = self.hp_max
  447.         end
  448.  
  449.         -- backup nametag so we can show health stats
  450.         if not self.nametag2 then
  451.             self.nametag2 = self.nametag or ""
  452.         end
  453.  
  454.         if show_health
  455.         and (cmi_cause and cmi_cause.type == "punch") then
  456.  
  457.             self.htimer = 2
  458.             self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
  459.  
  460.             update_tag(self)
  461.         end
  462.  
  463.         return false
  464.     end
  465.  
  466.     -- dropped cooked item if mob died in lava
  467.     if cause == "lava" then
  468.         item_drop(self, true)
  469.     else
  470.         item_drop(self, nil)
  471.     end
  472.  
  473.     mob_sound(self, self.sounds.death)
  474.  
  475.     local pos = self.object:get_pos()
  476.  
  477.     -- execute custom death function
  478.     if self.on_die then
  479.  
  480.         self.on_die(self, pos)
  481.  
  482.         if use_cmi then
  483.             cmi.notify_die(self.object, cmi_cause)
  484.         end
  485.  
  486.         self.object:remove()
  487.  
  488.         return true
  489.     end
  490.  
  491.     -- default death function and die animation (if defined)
  492.     if self.animation
  493.     and self.animation.die_start
  494.     and self.animation.die_end then
  495.  
  496.         local frames = self.animation.die_end - self.animation.die_start
  497.         local speed = self.animation.die_speed or 15
  498.         local length = max(frames / speed, 0)
  499.  
  500.         self.attack = nil
  501.         self.v_start = false
  502.         self.timer = 0
  503.         self.blinktimer = 0
  504.         self.passive = true
  505.         self.state = "die"
  506.         set_velocity(self, 0)
  507.         set_animation(self, "die")
  508.  
  509.         minetest.after(length, function(self)
  510.  
  511.             if use_cmi and self.object:get_luaentity() then
  512.                 cmi.notify_die(self.object, cmi_cause)
  513.             end
  514.  
  515.             self.object:remove()
  516.         end, self)
  517.     else
  518.  
  519.         if use_cmi then
  520.             cmi.notify_die(self.object, cmi_cause)
  521.         end
  522.  
  523.         self.object:remove()
  524.     end
  525.  
  526.     effect(pos, 20, "tnt_smoke.png")
  527.  
  528.     return true
  529. end
  530.  
  531.  
  532. -- check if within physical map limits (-30911 to 30927)
  533. local within_limits = function(pos, radius)
  534.  
  535.     if  (pos.x - radius) > -30913
  536.     and (pos.x + radius) <  30928
  537.     and (pos.y - radius) > -30913
  538.     and (pos.y + radius) <  30928
  539.     and (pos.z - radius) > -30913
  540.     and (pos.z + radius) <  30928 then
  541.         return true -- within limits
  542.     end
  543.  
  544.     return false -- beyond limits
  545. end
  546.  
  547.  
  548. -- is mob facing a cliff
  549. local is_at_cliff = function(self)
  550.  
  551.     if self.fear_height == 0 then -- 0 for no falling protection!
  552.         return false
  553.     end
  554.  
  555.     local yaw = self.object:get_yaw()
  556.     local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
  557.     local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
  558.     local pos = self.object:get_pos()
  559.     local ypos = pos.y + self.collisionbox[2] -- just above floor
  560.  
  561.     if minetest.line_of_sight(
  562.         {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
  563.         {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}
  564.     , 1) then
  565.  
  566.         return true
  567.     end
  568.  
  569.     return false
  570. end
  571.  
  572.  
  573. -- get node but use fallback for nil or unknown
  574. local node_ok = function(pos, fallback)
  575.  
  576.     fallback = fallback or mobs.fallback_node
  577.  
  578.     local node = minetest.get_node_or_nil(pos)
  579.  
  580.     if node and minetest.registered_nodes[node.name] then
  581.         return node
  582.     end
  583.  
  584.     return minetest.registered_nodes[fallback]
  585. end
  586.  
  587.  
  588. -- environmental damage (water, lava, fire, light etc.)
  589. local do_env_damage = function(self)
  590.  
  591.     -- feed/tame text timer (so mob 'full' messages dont spam chat)
  592.     if self.htimer > 0 then
  593.         self.htimer = self.htimer - 1
  594.     end
  595.  
  596.     -- reset nametag after showing health stats
  597.     if self.htimer < 1 and self.nametag2 then
  598.  
  599.         self.nametag = self.nametag2
  600.         self.nametag2 = nil
  601.  
  602.         update_tag(self)
  603.     end
  604.  
  605.     local pos = self.object:get_pos()
  606.  
  607.     self.time_of_day = minetest.get_timeofday()
  608.  
  609.     -- remove mob if beyond map limits
  610.     if not within_limits(pos, 0) then
  611.         self.object:remove()
  612.         return
  613.     end
  614.  
  615.     -- bright light harms mob
  616.     if self.light_damage ~= 0
  617. --  and pos.y > 0
  618. --  and self.time_of_day > 0.2
  619. --  and self.time_of_day < 0.8
  620.     and (minetest.get_node_light(pos) or 0) > 12 then
  621.  
  622.         self.health = self.health - self.light_damage
  623.  
  624.         effect(pos, 5, "tnt_smoke.png")
  625.  
  626.         if check_for_death(self, "light", {type = "light"}) then return end
  627.     end
  628. --[[
  629.     local y_level = self.collisionbox[2]
  630.  
  631.     if self.child then
  632.         y_level = self.collisionbox[2] * 0.5
  633.     end
  634.  
  635.     -- what is mob standing in?
  636.     pos.y = pos.y + y_level + 0.25 -- foot level
  637.     self.standing_in = node_ok(pos, "air").name
  638. --  print ("standing in " .. self.standing_in)
  639. ]]
  640.     -- don't fall when on ignore, just stand still
  641.     if self.standing_in == "ignore" then
  642.         self.object:set_velocity({x = 0, y = 0, z = 0})
  643.     end
  644.  
  645.     local nodef = minetest.registered_nodes[self.standing_in]
  646.  
  647.     pos.y = pos.y + 1 -- for particle effect position
  648.  
  649.     -- water
  650.     if self.water_damage
  651.     and nodef.groups.water then
  652.  
  653.         if self.water_damage ~= 0 then
  654.  
  655.             self.health = self.health - self.water_damage
  656.  
  657.             effect(pos, 5, "bubble.png", nil, nil, 1, nil)
  658.  
  659.             if check_for_death(self, "water", {type = "environment",
  660.                     pos = pos, node = self.standing_in}) then return end
  661.         end
  662.  
  663.     -- lava or fire
  664.     elseif self.lava_damage
  665.     and (nodef.groups.lava
  666.     or self.standing_in == node_fire
  667.     or self.standing_in == node_permanent_flame) then
  668.  
  669.         if self.lava_damage ~= 0 then
  670.  
  671.             self.health = self.health - self.lava_damage
  672.  
  673.             effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
  674.  
  675.             if check_for_death(self, "lava", {type = "environment",
  676.                     pos = pos, node = self.standing_in}) then return end
  677.         end
  678.  
  679.     -- damage_per_second node check
  680.     elseif nodef.damage_per_second ~= 0 then
  681.  
  682.         self.health = self.health - nodef.damage_per_second
  683.  
  684.         effect(pos, 5, "tnt_smoke.png")
  685.  
  686.         if check_for_death(self, "dps", {type = "environment",
  687.                 pos = pos, node = self.standing_in}) then return end
  688.     end
  689. --[[
  690.     --- suffocation inside solid node
  691.     if self.suffocation ~= 0
  692.     and nodef.walkable == true
  693.     and nodef.groups.disable_suffocation ~= 1
  694.     and nodef.drawtype == "normal" then
  695.  
  696.         self.health = self.health - self.suffocation
  697.  
  698.         if check_for_death(self, "suffocation", {type = "environment",
  699.                 pos = pos, node = self.standing_in}) then return end
  700.     end
  701. ]]
  702.     check_for_death(self, "", {type = "unknown"})
  703. end
  704.  
  705.  
  706. -- jump if facing a solid node (not fences or gates)
  707. local do_jump = function(self)
  708.  
  709.     if not self.jump
  710.     or self.jump_height == 0
  711.     or self.fly
  712.     or self.child
  713.     or self.order == "stand" then
  714.         return false
  715.     end
  716.  
  717.     self.facing_fence = false
  718.  
  719.     -- something stopping us while moving?
  720.     if self.state ~= "stand"
  721.     and get_velocity(self) > 0.5
  722.     and self.object:get_velocity().y ~= 0 then
  723.         return false
  724.     end
  725.  
  726.     local pos = self.object:get_pos()
  727.     local yaw = self.object:get_yaw()
  728.  
  729.     -- what is mob standing on?
  730.     pos.y = pos.y + self.collisionbox[2] - 0.2
  731.  
  732.     local nod = node_ok(pos)
  733.  
  734. --print ("standing on:", nod.name, pos.y)
  735.  
  736.     if minetest.registered_nodes[nod.name].walkable == false then
  737.         return false
  738.     end
  739.  
  740.     -- where is front
  741.     local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
  742.     local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
  743.  
  744.     -- what is in front of mob?
  745.     local nod = node_ok({
  746.         x = pos.x + dir_x,
  747.         y = pos.y + 0.5,
  748.         z = pos.z + dir_z
  749.     })
  750.  
  751.     -- thin blocks that do not need to be jumped
  752.     if nod.name == node_snow then
  753.         return false
  754.     end
  755.  
  756. --print ("in front:", nod.name, pos.y + 0.5)
  757.  
  758.     if self.walk_chance == 0
  759.     or minetest.registered_items[nod.name].walkable then
  760.  
  761.         if not nod.name:find("fence")
  762.         and not nod.name:find("gate") then
  763.  
  764.             local v = self.object:get_velocity()
  765.  
  766.             v.y = self.jump_height
  767.  
  768.             set_animation(self, "jump") -- only when defined
  769.  
  770.             self.object:set_velocity(v)
  771.  
  772.             -- when in air move forward
  773.             minetest.after(0.3, function(self, v)
  774.  
  775.                 if self.object:get_luaentity() then
  776.  
  777.                     self.object:set_acceleration({
  778.                         x = v.x * 2,--1.5,
  779.                         y = 0,
  780.                         z = v.z * 2,--1.5
  781.                     })
  782.                 end
  783.             end, self, v)
  784.  
  785.             if get_velocity(self) > 0 then
  786.                 mob_sound(self, self.sounds.jump)
  787.             end
  788.         else
  789.             self.facing_fence = true
  790.         end
  791.  
  792.         return true
  793.     end
  794.  
  795.     return false
  796. end
  797.  
  798.  
  799. -- blast damage to entities nearby (modified from TNT mod)
  800. local entity_physics = function(pos, radius)
  801.  
  802.     radius = radius * 2
  803.  
  804.     local objs = minetest.get_objects_inside_radius(pos, radius)
  805.     local obj_pos, dist
  806.  
  807.     for n = 1, #objs do
  808.  
  809.         obj_pos = objs[n]:get_pos()
  810.  
  811.         dist = get_distance(pos, obj_pos)
  812.         if dist < 1 then dist = 1 end
  813.  
  814.         local damage = floor((4 / dist) * radius)
  815.         local ent = objs[n]:get_luaentity()
  816.  
  817.         -- punches work on entities AND players
  818.         objs[n]:punch(objs[n], 1.0, {
  819.             full_punch_interval = 1.0,
  820.             damage_groups = {fleshy = damage},
  821.         }, pos)
  822.     end
  823. end
  824.  
  825.  
  826. -- should mob follow what I'm holding ?
  827. local follow_holding = function(self, clicker)
  828.  
  829.     if mobs.invis[clicker:get_player_name()] then
  830.         return false
  831.     end
  832.  
  833.     local item = clicker:get_wielded_item()
  834.     local t = type(self.follow)
  835.  
  836.     -- single item
  837.     if t == "string"
  838.     and item:get_name() == self.follow then
  839.         return true
  840.  
  841.     -- multiple items
  842.     elseif t == "table" then
  843.  
  844.         for no = 1, #self.follow do
  845.  
  846.             if self.follow[no] == item:get_name() then
  847.                 return true
  848.             end
  849.         end
  850.     end
  851.  
  852.     return false
  853. end
  854.  
  855.  
  856. -- find two animals of same type and breed if nearby and horny
  857. local breed = function(self)
  858.  
  859.     -- child takes 240 seconds before growing into adult
  860.     if self.child == true then
  861.  
  862.         self.hornytimer = self.hornytimer + 1
  863.  
  864.         if self.hornytimer > 240 then
  865.  
  866.             self.child = false
  867.             self.hornytimer = 0
  868.  
  869.             self.object:set_properties({
  870.                 textures = self.base_texture,
  871.                 mesh = self.base_mesh,
  872.                 visual_size = self.base_size,
  873.                 collisionbox = self.base_colbox,
  874.                 selectionbox = self.base_selbox,
  875.             })
  876.  
  877.             -- custom function when child grows up
  878.             if self.on_grown then
  879.                 self.on_grown(self)
  880.             else
  881.                 -- jump when fully grown so as not to fall into ground
  882.                 self.object:set_velocity({
  883.                     x = 0,
  884.                     y = self.jump_height,
  885.                     z = 0
  886.                 })
  887.             end
  888.         end
  889.  
  890.         return
  891.     end
  892.  
  893.     -- horny animal can mate for 40 seconds,
  894.     -- afterwards horny animal cannot mate again for 200 seconds
  895.     if self.horny == true
  896.     and self.hornytimer < 240 then
  897.  
  898.         self.hornytimer = self.hornytimer + 1
  899.  
  900.         if self.hornytimer >= 240 then
  901.             self.hornytimer = 0
  902.             self.horny = false
  903.         end
  904.     end
  905.  
  906.     -- find another same animal who is also horny and mate if nearby
  907.     if self.horny == true
  908.     and self.hornytimer <= 40 then
  909.  
  910.         local pos = self.object:get_pos()
  911.  
  912.         effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
  913.  
  914.         local objs = minetest.get_objects_inside_radius(pos, 3)
  915.         local num = 0
  916.         local ent = nil
  917.  
  918.         for n = 1, #objs do
  919.  
  920.             ent = objs[n]:get_luaentity()
  921.  
  922.             -- check for same animal with different colour
  923.             local canmate = false
  924.  
  925.             if ent then
  926.  
  927.                 if ent.name == self.name then
  928.                     canmate = true
  929.                 else
  930.                     local entname = string.split(ent.name,":")
  931.                     local selfname = string.split(self.name,":")
  932.  
  933.                     if entname[1] == selfname[1] then
  934.                         entname = string.split(entname[2],"_")
  935.                         selfname = string.split(selfname[2],"_")
  936.  
  937.                         if entname[1] == selfname[1] then
  938.                             canmate = true
  939.                         end
  940.                     end
  941.                 end
  942.             end
  943.  
  944.             if ent
  945.             and canmate == true
  946.             and ent.horny == true
  947.             and ent.hornytimer <= 40 then
  948.                 num = num + 1
  949.             end
  950.  
  951.             -- found your mate? then have a baby
  952.             if num > 1 then
  953.  
  954.                 self.hornytimer = 41
  955.                 ent.hornytimer = 41
  956.  
  957.                 -- spawn baby
  958.                 minetest.after(5, function(self, ent)
  959.  
  960.                     if not self.object:get_luaentity() then
  961.                         return
  962.                     end
  963.  
  964.                     -- custom breed function
  965.                     if self.on_breed then
  966.  
  967.                         -- when false skip going any further
  968.                         if self.on_breed(self, ent) == false then
  969.                                 return
  970.                         end
  971.                     else
  972.                         effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
  973.                     end
  974.  
  975.                     local mob = minetest.add_entity(pos, self.name)
  976.                     local ent2 = mob:get_luaentity()
  977.                     local textures = self.base_texture
  978.  
  979.                     -- using specific child texture (if found)
  980.                     if self.child_texture then
  981.                         textures = self.child_texture[1]
  982.                     end
  983.  
  984.                     -- and resize to half height
  985.                     mob:set_properties({
  986.                         textures = textures,
  987.                         visual_size = {
  988.                             x = self.base_size.x * .5,
  989.                             y = self.base_size.y * .5,
  990.                         },
  991.                         collisionbox = {
  992.                             self.base_colbox[1] * .5,
  993.                             self.base_colbox[2] * .5,
  994.                             self.base_colbox[3] * .5,
  995.                             self.base_colbox[4] * .5,
  996.                             self.base_colbox[5] * .5,
  997.                             self.base_colbox[6] * .5,
  998.                         },
  999.                         selectionbox = {
  1000.                             self.base_selbox[1] * .5,
  1001.                             self.base_selbox[2] * .5,
  1002.                             self.base_selbox[3] * .5,
  1003.                             self.base_selbox[4] * .5,
  1004.                             self.base_selbox[5] * .5,
  1005.                             self.base_selbox[6] * .5,
  1006.                         },
  1007.                     })
  1008.                     -- tamed and owned by parents' owner
  1009.                     ent2.child = true
  1010.                     ent2.tamed = true
  1011.                     ent2.owner = self.owner
  1012.                 end, self, ent)
  1013.  
  1014.                 num = 0
  1015.  
  1016.                 break
  1017.             end
  1018.         end
  1019.     end
  1020. end
  1021.  
  1022.  
  1023. -- find and replace what mob is looking for (grass, wheat etc.)
  1024. local replace = function(self, pos)
  1025.  
  1026.     if not mobs_griefing
  1027.     or not self.replace_rate
  1028.     or not self.replace_what
  1029.     or self.child == true
  1030.     or self.object:get_velocity().y ~= 0
  1031.     or random(1, self.replace_rate) > 1 then
  1032.         return
  1033.     end
  1034.  
  1035.     local what, with, y_offset
  1036.  
  1037.     if type(self.replace_what[1]) == "table" then
  1038.  
  1039.         local num = random(#self.replace_what)
  1040.  
  1041.         what = self.replace_what[num][1] or ""
  1042.         with = self.replace_what[num][2] or ""
  1043.         y_offset = self.replace_what[num][3] or 0
  1044.     else
  1045.         what = self.replace_what
  1046.         with = self.replace_with or ""
  1047.         y_offset = self.replace_offset or 0
  1048.     end
  1049.  
  1050.     pos.y = pos.y + y_offset
  1051.  
  1052.     if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
  1053.  
  1054. -- print ("replace node = ".. minetest.get_node(pos).name, pos.y)
  1055.  
  1056.         local oldnode = {name = what}
  1057.         local newnode = {name = with}
  1058.         local on_replace_return
  1059.  
  1060.         if self.on_replace then
  1061.             on_replace_return = self.on_replace(self, pos, oldnode, newnode)
  1062.         end
  1063.  
  1064.         if on_replace_return ~= false then
  1065.  
  1066.             minetest.set_node(pos, {name = with})
  1067.  
  1068.             -- when cow/sheep eats grass, replace wool and milk
  1069.             if self.gotten == true then
  1070.                 self.gotten = false
  1071.                 self.object:set_properties(self)
  1072.             end
  1073.         end
  1074.     end
  1075. end
  1076.  
  1077.  
  1078. -- check if daytime and also if mob is docile during daylight hours
  1079. local day_docile = function(self)
  1080.  
  1081.     if self.docile_by_day == false then
  1082.  
  1083.         return false
  1084.  
  1085.     elseif self.docile_by_day == true
  1086.     and self.time_of_day > 0.2
  1087.     and self.time_of_day < 0.8 then
  1088.  
  1089.         return true
  1090.     end
  1091. end
  1092.  
  1093.  
  1094. local los_switcher = false
  1095. local height_switcher = false
  1096.  
  1097. -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
  1098. local smart_mobs = function(self, s, p, dist, dtime)
  1099.  
  1100.     local s1 = self.path.lastpos
  1101.  
  1102.     local target_pos = self.attack:get_pos()
  1103.  
  1104.     -- is it becoming stuck?
  1105.     if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
  1106.         self.path.stuck_timer = self.path.stuck_timer + dtime
  1107.     else
  1108.         self.path.stuck_timer = 0
  1109.     end
  1110.  
  1111.     self.path.lastpos = {x = s.x, y = s.y, z = s.z}
  1112.  
  1113.     local use_pathfind = false
  1114.     local has_lineofsight = minetest.line_of_sight(
  1115.         {x = s.x, y = (s.y) + .5, z = s.z},
  1116.         {x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2)
  1117.  
  1118.     -- im stuck, search for path
  1119.     if not has_lineofsight then
  1120.  
  1121.         if los_switcher == true then
  1122.             use_pathfind = true
  1123.             los_switcher = false
  1124.         end -- cannot see target!
  1125.     else
  1126.         if los_switcher == false then
  1127.  
  1128.             los_switcher = true
  1129.             use_pathfind = false
  1130.  
  1131.             minetest.after(1, function(self)
  1132.  
  1133.                 if self.object:get_luaentity() then
  1134.  
  1135.                     if has_lineofsight then
  1136.                         self.path.following = false
  1137.                     end
  1138.                 end
  1139.             end, self)
  1140.         end -- can see target!
  1141.     end
  1142.  
  1143.     if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
  1144.  
  1145.         use_pathfind = true
  1146.         self.path.stuck_timer = 0
  1147.  
  1148.         minetest.after(1, function(self)
  1149.  
  1150.             if self.object:get_luaentity() then
  1151.  
  1152.                 if has_lineofsight then
  1153.                     self.path.following = false
  1154.                 end
  1155.             end
  1156.         end, self)
  1157.     end
  1158.  
  1159.     if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
  1160.  
  1161.         use_pathfind = true
  1162.         self.path.stuck_timer = 0
  1163.  
  1164.         minetest.after(1, function(self)
  1165.  
  1166.             if self.object:get_luaentity() then
  1167.  
  1168.                 if has_lineofsight then
  1169.                     self.path.following = false
  1170.                 end
  1171.             end
  1172.         end, self)
  1173.     end
  1174.  
  1175.     if math.abs(vector.subtract(s,target_pos).y) > self.stepheight then
  1176.  
  1177.         if height_switcher then
  1178.             use_pathfind = true
  1179.             height_switcher = false
  1180.         end
  1181.     else
  1182.         if not height_switcher then
  1183.             use_pathfind = false
  1184.             height_switcher = true
  1185.         end
  1186.     end
  1187.  
  1188.     if use_pathfind then
  1189.         -- lets try find a path, first take care of positions
  1190.         -- since pathfinder is very sensitive
  1191.         local sheight = self.collisionbox[5] - self.collisionbox[2]
  1192.  
  1193.         -- round position to center of node to avoid stuck in walls
  1194.         -- also adjust height for player models!
  1195.         s.x = floor(s.x + 0.5)
  1196. --      s.y = floor(s.y + 0.5) - sheight
  1197.         s.z = floor(s.z + 0.5)
  1198.  
  1199.         local ssight, sground = minetest.line_of_sight(s, {
  1200.             x = s.x, y = s.y - 4, z = s.z}, 1)
  1201.  
  1202.         -- determine node above ground
  1203.         if not ssight then
  1204.             s.y = sground.y + 1
  1205.         end
  1206.  
  1207.         local p1 = self.attack:get_pos()
  1208.  
  1209.         p1.x = floor(p1.x + 0.5)
  1210.         p1.y = floor(p1.y + 0.5)
  1211.         p1.z = floor(p1.z + 0.5)
  1212.  
  1213.         local dropheight = 6
  1214.         if self.fear_height ~= 0 then dropheight = self.fear_height end
  1215.  
  1216.         self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "Dijkstra")
  1217. --[[
  1218.         -- show path using particles
  1219.         if self.path.way and #self.path.way > 0 then
  1220.             print ("-- path length:" .. tonumber(#self.path.way))
  1221.             for _,pos in pairs(self.path.way) do
  1222.                 minetest.add_particle({
  1223.                 pos = pos,
  1224.                 velocity = {x=0, y=0, z=0},
  1225.                 acceleration = {x=0, y=0, z=0},
  1226.                 expirationtime = 1,
  1227.                 size = 4,
  1228.                 collisiondetection = false,
  1229.                 vertical = false,
  1230.                 texture = "heart.png",
  1231.                 })
  1232.             end
  1233.         end
  1234. ]]
  1235.  
  1236.         self.state = ""
  1237.         do_attack(self, self.attack)
  1238.  
  1239.         -- no path found, try something else
  1240.         if not self.path.way then
  1241.  
  1242.             self.path.following = false
  1243.  
  1244.              -- lets make way by digging/building if not accessible
  1245.             if self.pathfinding == 2 and mobs_griefing then
  1246.  
  1247.                 -- is player higher than mob?
  1248.                 if s.y < p1.y then
  1249.  
  1250.                     -- build upwards
  1251.                     if not minetest.is_protected(s, "") then
  1252.  
  1253.                         local ndef1 = minetest.registered_nodes[self.standing_in]
  1254.  
  1255.                         if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
  1256.  
  1257.                                 minetest.set_node(s, {name = mobs.fallback_node})
  1258.                         end
  1259.                     end
  1260.  
  1261.                     local sheight = math.ceil(self.collisionbox[5]) + 1
  1262.  
  1263.                     -- assume mob is 2 blocks high so it digs above its head
  1264.                     s.y = s.y + sheight
  1265.  
  1266.                     -- remove one block above to make room to jump
  1267.                     if not minetest.is_protected(s, "") then
  1268.  
  1269.                         local node1 = node_ok(s, "air").name
  1270.                         local ndef1 = minetest.registered_nodes[node1]
  1271.  
  1272.                         if node1 ~= "air"
  1273.                         and node1 ~= "ignore"
  1274.                         and ndef1
  1275.                         and not ndef1.groups.level
  1276.                         and not ndef1.groups.unbreakable
  1277.                         and not ndef1.groups.liquid then
  1278.  
  1279.                             minetest.set_node(s, {name = "air"})
  1280.                             minetest.add_item(s, ItemStack(node1))
  1281.  
  1282.                         end
  1283.                     end
  1284.  
  1285.                     s.y = s.y - sheight
  1286.                     self.object:set_pos({x = s.x, y = s.y + 2, z = s.z})
  1287.  
  1288.                 else -- dig 2 blocks to make door toward player direction
  1289.  
  1290.                     local yaw1 = self.object:get_yaw() + pi / 2
  1291.                     local p1 = {
  1292.                         x = s.x + cos(yaw1),
  1293.                         y = s.y,
  1294.                         z = s.z + sin(yaw1)
  1295.                     }
  1296.  
  1297.                     if not minetest.is_protected(p1, "") then
  1298.  
  1299.                         local node1 = node_ok(p1, "air").name
  1300.                         local ndef1 = minetest.registered_nodes[node1]
  1301.  
  1302.                         if node1 ~= "air"
  1303.                             and node1 ~= "ignore"
  1304.                             and ndef1
  1305.                             and not ndef1.groups.level
  1306.                             and not ndef1.groups.unbreakable
  1307.                             and not ndef1.groups.liquid then
  1308.  
  1309.                             minetest.add_item(p1, ItemStack(node1))
  1310.                             minetest.set_node(p1, {name = "air"})
  1311.                         end
  1312.  
  1313.                         p1.y = p1.y + 1
  1314.                         node1 = node_ok(p1, "air").name
  1315.                         ndef1 = minetest.registered_nodes[node1]
  1316.  
  1317.                         if node1 ~= "air"
  1318.                         and node1 ~= "ignore"
  1319.                         and ndef1
  1320.                         and not ndef1.groups.level
  1321.                         and not ndef1.groups.unbreakable
  1322.                         and not ndef1.groups.liquid then
  1323.  
  1324.                             minetest.add_item(p1, ItemStack(node1))
  1325.                             minetest.set_node(p1, {name = "air"})
  1326.                         end
  1327.  
  1328.                     end
  1329.                 end
  1330.             end
  1331.  
  1332.             -- will try again in 2 second
  1333.             self.path.stuck_timer = stuck_timeout - 2
  1334.  
  1335.             -- frustration! cant find the damn path :(
  1336.             mob_sound(self, self.sounds.random)
  1337.         else
  1338.             -- yay i found path
  1339.             mob_sound(self, self.sounds.war_cry)
  1340.             set_velocity(self, self.walk_velocity)
  1341.  
  1342.             -- follow path now that it has it
  1343.             self.path.following = true
  1344.         end
  1345.     end
  1346. end
  1347.  
  1348.  
  1349. -- specific attacks
  1350. local specific_attack = function(list, what)
  1351.  
  1352.     -- no list so attack default (player, animals etc.)
  1353.     if list == nil then
  1354.         return true
  1355.     end
  1356.  
  1357.     -- found entity on list to attack?
  1358.     for no = 1, #list do
  1359.  
  1360.         if list[no] == what then
  1361.             return true
  1362.         end
  1363.     end
  1364.  
  1365.     return false
  1366. end
  1367.  
  1368.  
  1369. -- general attack function for all mobs ==========
  1370. local general_attack = function(self)
  1371.  
  1372.     -- return if already attacking, passive or docile during day
  1373.     if self.passive
  1374.     or self.state == "attack"
  1375.     or day_docile(self) then
  1376.         return
  1377.     end
  1378.  
  1379.     local s = self.object:get_pos()
  1380.     local objs = minetest.get_objects_inside_radius(s, self.view_range)
  1381.  
  1382.     -- remove entities we aren't interested in
  1383.     for n = 1, #objs do
  1384.  
  1385.         local ent = objs[n]:get_luaentity()
  1386.  
  1387.         -- are we a player?
  1388.         if objs[n]:is_player() then
  1389.  
  1390.             -- if player invisible or mob not setup to attack then remove from list
  1391.             if self.attack_player == false
  1392.             or (self.owner and self.type ~= "monster")
  1393.             or mobs.invis[objs[n]:get_player_name()]
  1394.             or not specific_attack(self.specific_attack, "player") then
  1395.                 objs[n] = nil
  1396. --print("- pla", n)
  1397.             end
  1398.  
  1399.         -- or are we a mob?
  1400.         elseif ent and ent._cmi_is_mob then
  1401.  
  1402.             -- remove mobs not to attack
  1403.             if self.name == ent.name
  1404.             or (not self.attack_animals and ent.type == "animal")
  1405.             or (not self.attacks_monsters and ent.type == "monster")
  1406.             or (not self.attack_npc and ent.type == "npc")
  1407.             or not specific_attack(self.specific_attack, ent.name) then
  1408.                 objs[n] = nil
  1409. --print("- mob", n, self.name, ent.name)
  1410.             end
  1411.  
  1412.         -- remove non-mobs
  1413.         elseif ent and not ent._cmi_is_mob then
  1414. --print(" -obj", n)
  1415.             objs[n] = nil
  1416.         end
  1417.     end
  1418.  
  1419.     local p, sp, dist, min_player
  1420.     local min_dist = self.view_range + 1
  1421.  
  1422.     -- go through remaining entities and select closest
  1423.     for _,player in pairs(objs) do
  1424.  
  1425.         p = player:get_pos()
  1426.         sp = s
  1427.  
  1428.         dist = get_distance(p, s)
  1429.  
  1430.         -- aim higher to make looking up hills more realistic
  1431.         p.y = p.y + 1
  1432.         sp.y = sp.y + 1
  1433.  
  1434.         -- choose closest player to attack that isnt self
  1435.         if dist ~= 0
  1436.         and dist < min_dist
  1437.         and line_of_sight(self, sp, p, 2) == true then
  1438.             min_dist = dist
  1439.             min_player = player
  1440.         end
  1441.     end
  1442.  
  1443.     -- attack closest player or mob
  1444.     if min_player then
  1445.         do_attack(self, min_player)
  1446.     end
  1447. end
  1448.  
  1449.  
  1450. -- specific runaway
  1451. local specific_runaway = function(list, what)
  1452.  
  1453.     -- no list so do not run
  1454.     if list == nil then
  1455.         return false
  1456.     end
  1457.  
  1458.     -- found entity on list to attack?
  1459.     for no = 1, #list do
  1460.  
  1461.         if list[no] == what then
  1462.             return true
  1463.         end
  1464.     end
  1465.  
  1466.     return false
  1467. end
  1468.  
  1469.  
  1470. -- find someone to runaway from
  1471. local runaway_from = function(self)
  1472.  
  1473.     if not self.runaway_from then
  1474.         return
  1475.     end
  1476.  
  1477.     local s = self.object:get_pos()
  1478.     local p, sp, dist
  1479.     local player, obj, min_player
  1480.     local type, name = "", ""
  1481.     local min_dist = self.view_range + 1
  1482.     local objs = minetest.get_objects_inside_radius(s, self.view_range)
  1483.  
  1484.     for n = 1, #objs do
  1485.  
  1486.         if objs[n]:is_player() then
  1487.  
  1488.             if mobs.invis[ objs[n]:get_player_name() ]
  1489.             or self.owner == objs[n]:get_player_name() then
  1490.  
  1491.                 type = ""
  1492.             else
  1493.                 player = objs[n]
  1494.                 type = "player"
  1495.                 name = "player"
  1496.             end
  1497.         else
  1498.             obj = objs[n]:get_luaentity()
  1499.  
  1500.             if obj then
  1501.                 player = obj.object
  1502.                 type = obj.type
  1503.                 name = obj.name or ""
  1504.             end
  1505.         end
  1506.  
  1507.         -- find specific mob to runaway from
  1508.         if name ~= "" and name ~= self.name
  1509.         and specific_runaway(self.runaway_from, name) then
  1510.  
  1511.             p = player:get_pos()
  1512.             sp = s
  1513.  
  1514.             -- aim higher to make looking up hills more realistic
  1515.             p.y = p.y + 1
  1516.             sp.y = sp.y + 1
  1517.  
  1518.             dist = get_distance(p, s)
  1519.  
  1520.  
  1521.             -- choose closest player/mpb to runaway from
  1522.             if dist < min_dist
  1523.             and line_of_sight(self, sp, p, 2) == true then
  1524.                 min_dist = dist
  1525.                 min_player = player
  1526.             end
  1527.         end
  1528.     end
  1529.  
  1530.     if min_player then
  1531.  
  1532.         local lp = player:get_pos()
  1533.         local vec = {
  1534.             x = lp.x - s.x,
  1535.             y = lp.y - s.y,
  1536.             z = lp.z - s.z
  1537.         }
  1538.  
  1539.         local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
  1540.  
  1541.         if lp.x > s.x then
  1542.             yaw = yaw + pi
  1543.         end
  1544.  
  1545.         yaw = set_yaw(self, yaw, 4)
  1546.         self.state = "runaway"
  1547.         self.runaway_timer = 3
  1548.         self.following = nil
  1549.     end
  1550. end
  1551.  
  1552.  
  1553. -- follow player if owner or holding item, if fish outta water then flop
  1554. local follow_flop = function(self)
  1555.  
  1556.     -- find player to follow
  1557.     if (self.follow ~= ""
  1558.     or self.order == "follow")
  1559.     and not self.following
  1560.     and self.state ~= "attack"
  1561.     and self.state ~= "runaway" then
  1562.  
  1563.         local s = self.object:get_pos()
  1564.         local players = minetest.get_connected_players()
  1565.  
  1566.         for n = 1, #players do
  1567.  
  1568.             if get_distance(players[n]:get_pos(), s) < self.view_range
  1569.             and not mobs.invis[ players[n]:get_player_name() ] then
  1570.  
  1571.                 self.following = players[n]
  1572.  
  1573.                 break
  1574.             end
  1575.         end
  1576.     end
  1577.  
  1578.     if self.type == "npc"
  1579.     and self.order == "follow"
  1580.     and self.state ~= "attack"
  1581.     and self.owner ~= "" then
  1582.  
  1583.         -- npc stop following player if not owner
  1584.         if self.following
  1585.         and self.owner
  1586.         and self.owner ~= self.following:get_player_name() then
  1587.             self.following = nil
  1588.         end
  1589.     else
  1590.         -- stop following player if not holding specific item
  1591.         if self.following
  1592.         and self.following:is_player()
  1593.         and follow_holding(self, self.following) == false then
  1594.             self.following = nil
  1595.         end
  1596.  
  1597.     end
  1598.  
  1599.     -- follow that thing
  1600.     if self.following then
  1601.  
  1602.         local s = self.object:get_pos()
  1603.         local p
  1604.  
  1605.         if self.following:is_player() then
  1606.  
  1607.             p = self.following:get_pos()
  1608.  
  1609.         elseif self.following.object then
  1610.  
  1611.             p = self.following.object:get_pos()
  1612.         end
  1613.  
  1614.         if p then
  1615.  
  1616.             local dist = get_distance(p, s)
  1617.  
  1618.             -- dont follow if out of range
  1619.             if dist > self.view_range then
  1620.                 self.following = nil
  1621.             else
  1622.                 local vec = {
  1623.                     x = p.x - s.x,
  1624.                     z = p.z - s.z
  1625.                 }
  1626.  
  1627.                 local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
  1628.  
  1629.                 if p.x > s.x then yaw = yaw + pi end
  1630.  
  1631.                 yaw = set_yaw(self, yaw, 6)
  1632.  
  1633.                 -- anyone but standing npc's can move along
  1634.                 if dist > self.reach
  1635.                 and self.order ~= "stand" then
  1636.  
  1637.                     set_velocity(self, self.walk_velocity)
  1638.  
  1639.                     if self.walk_chance ~= 0 then
  1640.                         set_animation(self, "walk")
  1641.                     end
  1642.                 else
  1643.                     set_velocity(self, 0)
  1644.                     set_animation(self, "stand")
  1645.                 end
  1646.  
  1647.                 return
  1648.             end
  1649.         end
  1650.     end
  1651.  
  1652.     -- swimmers flop when out of their element, and swim again when back in
  1653.     if self.fly then
  1654.         local s = self.object:get_pos()
  1655.         if not flight_check(self, s) then
  1656.  
  1657.             self.state = "flop"
  1658.             self.object:set_velocity({x = 0, y = -5, z = 0})
  1659.  
  1660.             set_animation(self, "stand")
  1661.  
  1662.             return
  1663.         elseif self.state == "flop" then
  1664.             self.state = "stand"
  1665.         end
  1666.     end
  1667. end
  1668.  
  1669.  
  1670. -- dogshoot attack switch and counter function
  1671. local dogswitch = function(self, dtime)
  1672.  
  1673.     -- switch mode not activated
  1674.     if not self.dogshoot_switch
  1675.     or not dtime then
  1676.         return 0
  1677.     end
  1678.  
  1679.     self.dogshoot_count = self.dogshoot_count + dtime
  1680.  
  1681.     if (self.dogshoot_switch == 1
  1682.     and self.dogshoot_count > self.dogshoot_count_max)
  1683.     or (self.dogshoot_switch == 2
  1684.     and self.dogshoot_count > self.dogshoot_count2_max) then
  1685.  
  1686.         self.dogshoot_count = 0
  1687.  
  1688.         if self.dogshoot_switch == 1 then
  1689.             self.dogshoot_switch = 2
  1690.         else
  1691.             self.dogshoot_switch = 1
  1692.         end
  1693.     end
  1694.  
  1695.     return self.dogshoot_switch
  1696. end
  1697.  
  1698.  
  1699. -- execute current state (stand, walk, run, attacks)
  1700. local do_states = function(self, dtime)
  1701.  
  1702.     local yaw = self.object:get_yaw() or 0
  1703.  
  1704.     if self.state == "stand" then
  1705.  
  1706.         if random(1, 4) == 1 then
  1707.  
  1708.             local lp = nil
  1709.             local s = self.object:get_pos()
  1710.             local objs = minetest.get_objects_inside_radius(s, 3)
  1711.  
  1712.             for n = 1, #objs do
  1713.  
  1714.                 if objs[n]:is_player() then
  1715.                     lp = objs[n]:get_pos()
  1716.                     break
  1717.                 end
  1718.             end
  1719.  
  1720.             -- look at any players nearby, otherwise turn randomly
  1721.             if lp then
  1722.  
  1723.                 local vec = {
  1724.                     x = lp.x - s.x,
  1725.                     z = lp.z - s.z
  1726.                 }
  1727.  
  1728.                 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
  1729.  
  1730.                 if lp.x > s.x then yaw = yaw + pi end
  1731.             else
  1732.                 yaw = yaw + random(-0.5, 0.5)
  1733.             end
  1734.  
  1735.             yaw = set_yaw(self, yaw, 8)
  1736.         end
  1737.  
  1738.         set_velocity(self, 0)
  1739.         set_animation(self, "stand")
  1740.  
  1741.         -- npc's ordered to stand stay standing
  1742.         if self.type ~= "npc"
  1743.         or self.order ~= "stand" then
  1744.  
  1745.             if self.walk_chance ~= 0
  1746.             and self.facing_fence ~= true
  1747.             and random(1, 100) <= self.walk_chance
  1748.             and is_at_cliff(self) == false then
  1749.  
  1750.                 set_velocity(self, self.walk_velocity)
  1751.                 self.state = "walk"
  1752.                 set_animation(self, "walk")
  1753.  
  1754.                 --[[ fly up/down randomly for flying mobs
  1755.                 if self.fly and random(1, 100) <= self.walk_chance then
  1756.  
  1757.                     local v = self.object:get_velocity()
  1758.                     local ud = random(-1, 2) / 9
  1759.  
  1760.                     self.object:set_velocity({x = v.x, y = ud, z = v.z})
  1761.                 end--]]
  1762.             end
  1763.         end
  1764.  
  1765.     elseif self.state == "walk" then
  1766.  
  1767.         local s = self.object:get_pos()
  1768.         local lp = nil
  1769.  
  1770.         -- is there something I need to avoid?
  1771.         if self.water_damage > 0
  1772.         and self.lava_damage > 0 then
  1773.  
  1774.             lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
  1775.  
  1776.         elseif self.water_damage > 0 then
  1777.  
  1778.             lp = minetest.find_node_near(s, 1, {"group:water"})
  1779.  
  1780.         elseif self.lava_damage > 0 then
  1781.  
  1782.             lp = minetest.find_node_near(s, 1, {"group:lava"})
  1783.         end
  1784.  
  1785.         if lp then
  1786.  
  1787.             -- if mob in water or lava then look for land
  1788.             if (self.lava_damage
  1789.                 and minetest.registered_nodes[self.standing_in].groups.lava)
  1790.             or (self.water_damage
  1791.                 and minetest.registered_nodes[self.standing_in].groups.water) then
  1792.  
  1793.                 lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
  1794.                     "group:sand", node_ice, node_snowblock})
  1795.  
  1796.                 -- did we find land?
  1797.                 if lp then
  1798.  
  1799.                     local vec = {
  1800.                         x = lp.x - s.x,
  1801.                         z = lp.z - s.z
  1802.                     }
  1803.  
  1804.                     yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
  1805.  
  1806.                     if lp.x > s.x then yaw = yaw + pi end
  1807.  
  1808.                     -- look towards land and jump/move in that direction
  1809.                     yaw = set_yaw(self, yaw, 6)
  1810.                     do_jump(self)
  1811.                     set_velocity(self, self.walk_velocity)
  1812.                 else
  1813.                     yaw = yaw + random(-0.5, 0.5)
  1814.                 end
  1815.  
  1816.             else
  1817.  
  1818.                 local vec = {
  1819.                     x = lp.x - s.x,
  1820.                     z = lp.z - s.z
  1821.                 }
  1822.  
  1823.                 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
  1824.  
  1825.                 if lp.x > s.x then yaw = yaw + pi end
  1826.             end
  1827.  
  1828.             yaw = set_yaw(self, yaw, 8)
  1829.  
  1830.         -- otherwise randomly turn
  1831.         elseif random(1, 100) <= 30 then
  1832.  
  1833.             yaw = yaw + random(-0.5, 0.5)
  1834.  
  1835.             yaw = set_yaw(self, yaw, 8)
  1836.         end
  1837.  
  1838.         -- stand for great fall in front
  1839.         local temp_is_cliff = is_at_cliff(self)
  1840.  
  1841.         if self.facing_fence == true
  1842.         or temp_is_cliff
  1843.         or random(1, 100) <= 30 then
  1844.  
  1845.             set_velocity(self, 0)
  1846.             self.state = "stand"
  1847.             set_animation(self, "stand")
  1848.         else
  1849.             set_velocity(self, self.walk_velocity)
  1850.  
  1851.             if flight_check(self)
  1852.             and self.animation
  1853.             and self.animation.fly_start
  1854.             and self.animation.fly_end then
  1855.                 set_animation(self, "fly")
  1856.             else
  1857.                 set_animation(self, "walk")
  1858.             end
  1859.         end
  1860.  
  1861.     -- runaway when punched
  1862.     elseif self.state == "runaway" then
  1863.  
  1864.         self.runaway_timer = self.runaway_timer + 1
  1865.  
  1866.         -- stop after 5 seconds or when at cliff
  1867.         if self.runaway_timer > 5
  1868.         or is_at_cliff(self) then
  1869.             self.runaway_timer = 0
  1870.             set_velocity(self, 0)
  1871.             self.state = "stand"
  1872.             set_animation(self, "stand")
  1873.         else
  1874.             set_velocity(self, self.run_velocity)
  1875.             set_animation(self, "walk")
  1876.         end
  1877.  
  1878.     -- attack routines (explode, dogfight, shoot, dogshoot)
  1879.     elseif self.state == "attack" then
  1880.  
  1881.         -- calculate distance from mob and enemy
  1882.         local s = self.object:get_pos()
  1883.         local p = self.attack:get_pos() or s
  1884.         local dist = get_distance(p, s)
  1885.  
  1886.         -- stop attacking if player invisible or out of range
  1887.         if dist > self.view_range
  1888.         or not self.attack
  1889.         or not self.attack:get_pos()
  1890.         or self.attack:get_hp() <= 0
  1891.         or (self.attack:is_player() and mobs.invis[ self.attack:get_player_name() ]) then
  1892.  
  1893. --          print(" ** stop attacking **", dist, self.view_range)
  1894.             self.state = "stand"
  1895.             set_velocity(self, 0)
  1896.             set_animation(self, "stand")
  1897.             self.attack = nil
  1898.             self.v_start = false
  1899.             self.timer = 0
  1900.             self.blinktimer = 0
  1901.             self.path.way = nil
  1902.  
  1903.             return
  1904.         end
  1905.  
  1906.         if self.attack_type == "explode" then
  1907.  
  1908.             local vec = {
  1909.                 x = p.x - s.x,
  1910.                 z = p.z - s.z
  1911.             }
  1912.  
  1913.             yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
  1914.  
  1915.             if p.x > s.x then yaw = yaw + pi end
  1916.  
  1917.             yaw = set_yaw(self, yaw)
  1918.  
  1919.             local node_break_radius = self.explosion_radius or 1
  1920.             local entity_damage_radius = self.explosion_damage_radius
  1921.                     or (node_break_radius * 2)
  1922.  
  1923.             -- start timer when in reach and line of sight
  1924.             if not self.v_start
  1925.             and dist <= self.reach
  1926.             and line_of_sight(self, s, p, 2) then
  1927.  
  1928.                 self.v_start = true
  1929.                 self.timer = 0
  1930.                 self.blinktimer = 0
  1931.                 mob_sound(self, self.sounds.fuse)
  1932. --              print ("=== explosion timer started", self.explosion_timer)
  1933.  
  1934.             -- stop timer if out of reach or direct line of sight
  1935.             elseif self.allow_fuse_reset
  1936.             and self.v_start
  1937.             and (dist > self.reach
  1938.                     or not line_of_sight(self, s, p, 2)) then
  1939.                 self.v_start = false
  1940.                 self.timer = 0
  1941.                 self.blinktimer = 0
  1942.                 self.blinkstatus = false
  1943.                 self.object:settexturemod("")
  1944.             end
  1945.  
  1946.             -- walk right up to player unless the timer is active
  1947.             if self.v_start and (self.stop_to_explode or dist < 1.5) then
  1948.                 set_velocity(self, 0)
  1949.             else
  1950.                 set_velocity(self, self.run_velocity)
  1951.             end
  1952.  
  1953.             if self.animation and self.animation.run_start then
  1954.                 set_animation(self, "run")
  1955.             else
  1956.                 set_animation(self, "walk")
  1957.             end
  1958.  
  1959.             if self.v_start then
  1960.  
  1961.                 self.timer = self.timer + dtime
  1962.                 self.blinktimer = (self.blinktimer or 0) + dtime
  1963.  
  1964.                 if self.blinktimer > 0.2 then
  1965.  
  1966.                     self.blinktimer = 0
  1967.  
  1968.                     if self.blinkstatus then
  1969.                         self.object:settexturemod("")
  1970.                     else
  1971.                         self.object:settexturemod("^[brighten")
  1972.                     end
  1973.  
  1974.                     self.blinkstatus = not self.blinkstatus
  1975.                 end
  1976.  
  1977. --              print ("=== explosion timer", self.timer)
  1978.  
  1979.                 if self.timer > self.explosion_timer then
  1980.  
  1981.                     local pos = self.object:get_pos()
  1982.  
  1983.                     -- dont damage anything if area protected or next to water
  1984.                     if minetest.find_node_near(pos, 1, {"group:water"})
  1985.                     or minetest.is_protected(pos, "") then
  1986.  
  1987.                         node_break_radius = 1
  1988.                     end
  1989.  
  1990.                     self.object:remove()
  1991.  
  1992.                     if minetest.get_modpath("tnt") and tnt and tnt.boom
  1993.                     and not minetest.is_protected(pos, "") then
  1994.  
  1995.                         tnt.boom(pos, {
  1996.                             radius = node_break_radius,
  1997.                             damage_radius = entity_damage_radius,
  1998.                             sound = self.sounds.explode,
  1999.                         })
  2000.                     else
  2001.  
  2002.                         minetest.sound_play(self.sounds.explode, {
  2003.                             pos = pos,
  2004.                             gain = 1.0,
  2005.                             max_hear_distance = self.sounds.distance or 32
  2006.                         })
  2007.  
  2008.                         entity_physics(pos, entity_damage_radius)
  2009.                         effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
  2010.                     end
  2011.  
  2012.                     return
  2013.                 end
  2014.             end
  2015.  
  2016.         elseif self.attack_type == "dogfight"
  2017.         or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 2)
  2018.         or (self.attack_type == "dogshoot" and dist <= self.reach and dogswitch(self) == 0) then
  2019.  
  2020.             if self.fly
  2021.             and dist > self.reach then
  2022.  
  2023.                 local p1 = s
  2024.                 local me_y = floor(p1.y)
  2025.                 local p2 = p
  2026.                 local p_y = floor(p2.y + 1)
  2027.                 local v = self.object:get_velocity()
  2028.  
  2029.                 if flight_check(self, s) then
  2030.  
  2031.                     if me_y < p_y then
  2032.  
  2033.                         self.object:set_velocity({
  2034.                             x = v.x,
  2035.                             y = 1 * self.walk_velocity,
  2036.                             z = v.z
  2037.                         })
  2038.  
  2039.                     elseif me_y > p_y then
  2040.  
  2041.                         self.object:set_velocity({
  2042.                             x = v.x,
  2043.                             y = -1 * self.walk_velocity,
  2044.                             z = v.z
  2045.                         })
  2046.                     end
  2047.                 else
  2048.                     if me_y < p_y then
  2049.  
  2050.                         self.object:set_velocity({
  2051.                             x = v.x,
  2052.                             y = 0.01,
  2053.                             z = v.z
  2054.                         })
  2055.  
  2056.                     elseif me_y > p_y then
  2057.  
  2058.                         self.object:set_velocity({
  2059.                             x = v.x,
  2060.                             y = -0.01,
  2061.                             z = v.z
  2062.                         })
  2063.                     end
  2064.                 end
  2065.  
  2066.             end
  2067.  
  2068.             -- rnd: new movement direction
  2069.             if self.path.following
  2070.             and self.path.way
  2071.             and self.attack_type ~= "dogshoot" then
  2072.  
  2073.                 -- no paths longer than 50
  2074.                 if #self.path.way > 50
  2075.                 or dist < self.reach then
  2076.                     self.path.following = false
  2077.                     return
  2078.                 end
  2079.  
  2080.                 local p1 = self.path.way[1]
  2081.  
  2082.                 if not p1 then
  2083.                     self.path.following = false
  2084.                     return
  2085.                 end
  2086.  
  2087.                 if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
  2088.                     -- reached waypoint, remove it from queue
  2089.                     table.remove(self.path.way, 1)
  2090.                 end
  2091.  
  2092.                 -- set new temporary target
  2093.                 p = {x = p1.x, y = p1.y, z = p1.z}
  2094.             end
  2095.  
  2096.             local vec = {
  2097.                 x = p.x - s.x,
  2098.                 z = p.z - s.z
  2099.             }
  2100.  
  2101.             yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
  2102.  
  2103.             if p.x > s.x then yaw = yaw + pi end
  2104.  
  2105.             yaw = set_yaw(self, yaw)
  2106.  
  2107.             -- move towards enemy if beyond mob reach
  2108.             if dist > self.reach then
  2109.  
  2110.                 -- path finding by rnd
  2111.                 if self.pathfinding -- only if mob has pathfinding enabled
  2112.                 and enable_pathfinding then
  2113.  
  2114.                     smart_mobs(self, s, p, dist, dtime)
  2115.                 end
  2116.  
  2117.                 if is_at_cliff(self) then
  2118.  
  2119.                     set_velocity(self, 0)
  2120.                     set_animation(self, "stand")
  2121.                 else
  2122.  
  2123.                     if self.path.stuck then
  2124.                         set_velocity(self, self.walk_velocity)
  2125.                     else
  2126.                         set_velocity(self, self.run_velocity)
  2127.                     end
  2128.  
  2129.                     if self.animation and self.animation.run_start then
  2130.                         set_animation(self, "run")
  2131.                     else
  2132.                         set_animation(self, "walk")
  2133.                     end
  2134.                 end
  2135.  
  2136.             else -- rnd: if inside reach range
  2137.  
  2138.                 self.path.stuck = false
  2139.                 self.path.stuck_timer = 0
  2140.                 self.path.following = false -- not stuck anymore
  2141.  
  2142.                 set_velocity(self, 0)
  2143.  
  2144.                 if not self.custom_attack then
  2145.  
  2146.                     if self.timer > 1 then
  2147.  
  2148.                         self.timer = 0
  2149.  
  2150.                         if self.double_melee_attack
  2151.                         and random(1, 2) == 1 then
  2152.                             set_animation(self, "punch2")
  2153.                         else
  2154.                             set_animation(self, "punch")
  2155.                         end
  2156.  
  2157.                         local p2 = p
  2158.                         local s2 = s
  2159.  
  2160.                         p2.y = p2.y + .5
  2161.                         s2.y = s2.y + .5
  2162.  
  2163.                         if line_of_sight(self, p2, s2) == true then
  2164.  
  2165.                             -- play attack sound
  2166.                             mob_sound(self, self.sounds.attack)
  2167.  
  2168.                             -- punch player (or what player is attached to)
  2169.                             local attached = self.attack:get_attach()
  2170.                             if attached then
  2171.                                 self.attack = attached
  2172.                             end
  2173.                             self.attack:punch(self.object, 1.0, {
  2174.                                 full_punch_interval = 1.0,
  2175.                                 damage_groups = {fleshy = self.damage}
  2176.                             }, nil)
  2177.                         end
  2178.                     end
  2179.                 else    -- call custom attack every second
  2180.                     if self.custom_attack
  2181.                     and self.timer > 1 then
  2182.  
  2183.                         self.timer = 0
  2184.  
  2185.                         self.custom_attack(self, p)
  2186.                     end
  2187.                 end
  2188.             end
  2189.  
  2190.         elseif self.attack_type == "shoot"
  2191.         or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 1)
  2192.         or (self.attack_type == "dogshoot" and dist > self.reach and dogswitch(self) == 0) then
  2193.  
  2194.             p.y = p.y - .5
  2195.             s.y = s.y + .5
  2196.  
  2197.             local dist = get_distance(p, s)
  2198.             local vec = {
  2199.                 x = p.x - s.x,
  2200.                 y = p.y - s.y,
  2201.                 z = p.z - s.z
  2202.             }
  2203.  
  2204.             yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
  2205.  
  2206.             if p.x > s.x then yaw = yaw + pi end
  2207.  
  2208.             yaw = set_yaw(self, yaw)
  2209.  
  2210.             set_velocity(self, 0)
  2211.  
  2212.             if self.shoot_interval
  2213.             and self.timer > self.shoot_interval
  2214.             and random(1, 100) <= 60 then
  2215.  
  2216.                 self.timer = 0
  2217.                 set_animation(self, "shoot")
  2218.  
  2219.                 -- play shoot attack sound
  2220.                 mob_sound(self, self.sounds.shoot_attack)
  2221.  
  2222.                 local p = self.object:get_pos()
  2223.  
  2224.                 p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2
  2225.  
  2226.                 if minetest.registered_entities[self.arrow] then
  2227.  
  2228.                     local obj = minetest.add_entity(p, self.arrow)
  2229.                     local ent = obj:get_luaentity()
  2230.                     local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
  2231.                     local v = ent.velocity or 1 -- or set to default
  2232.  
  2233.                     ent.switch = 1
  2234.                     ent.owner_id = tostring(self.object) -- add unique owner id to arrow
  2235.  
  2236.                      -- offset makes shoot aim accurate
  2237.                     vec.y = vec.y + self.shoot_offset
  2238.                     vec.x = vec.x * (v / amount)
  2239.                     vec.y = vec.y * (v / amount)
  2240.                     vec.z = vec.z * (v / amount)
  2241.  
  2242.                     obj:set_velocity(vec)
  2243.                 end
  2244.             end
  2245.         end
  2246.     end
  2247. end
  2248.  
  2249.  
  2250. -- falling and fall damage
  2251. local falling = function(self, pos)
  2252.  
  2253.     if self.fly then
  2254.         return
  2255.     end
  2256.  
  2257.     -- floating in water (or falling)
  2258.     local v = self.object:get_velocity()
  2259.  
  2260.     if v.y > 0 then
  2261.  
  2262.         -- apply gravity when moving up
  2263.         self.object:set_acceleration({
  2264.             x = 0,
  2265.             y = -10,
  2266.             z = 0
  2267.         })
  2268.  
  2269.     elseif v.y <= 0 and v.y > self.fall_speed then
  2270.  
  2271.         -- fall downwards at set speed
  2272.         self.object:set_acceleration({
  2273.             x = 0,
  2274.             y = self.fall_speed,
  2275.             z = 0
  2276.         })
  2277.     else
  2278.         -- stop accelerating once max fall speed hit
  2279.         self.object:set_acceleration({x = 0, y = 0, z = 0})
  2280.     end
  2281.  
  2282.     -- in water then float up
  2283.     if minetest.registered_nodes[self.standing_in].groups.water then
  2284.  
  2285.         if self.floats == 1 then
  2286.  
  2287.             self.object:set_acceleration({
  2288.                 x = 0,
  2289.                 y = -self.fall_speed / (max(1, v.y) ^ 8), -- 8 was 2
  2290.                 z = 0
  2291.             })
  2292.         end
  2293.     else
  2294.  
  2295.         -- fall damage onto solid ground
  2296.         if self.fall_damage == 1
  2297.         and self.object:get_velocity().y == 0 then
  2298.  
  2299.             local d = (self.old_y or 0) - self.object:get_pos().y
  2300.  
  2301.             if d > 5 then
  2302.  
  2303.                 self.health = self.health - floor(d - 5)
  2304.  
  2305.                 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
  2306.  
  2307.                 if check_for_death(self, "fall", {type = "fall"}) then
  2308.                     return
  2309.                 end
  2310.             end
  2311.  
  2312.             self.old_y = self.object:get_pos().y
  2313.         end
  2314.     end
  2315. end
  2316.  
  2317.  
  2318. -- deal damage and effects when mob punched
  2319. local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
  2320.  
  2321.     -- custom punch function
  2322.     if self.do_punch then
  2323.  
  2324.         -- when false skip going any further
  2325.         if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
  2326.             return
  2327.         end
  2328.     end
  2329.  
  2330.     -- mob health check
  2331. --  if self.health <= 0 then
  2332. --      return
  2333. --  end
  2334.  
  2335.     -- error checking when mod profiling is enabled
  2336.     if not tool_capabilities then
  2337.         minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
  2338.         return
  2339.     end
  2340.  
  2341.     -- is mob protected?
  2342.     if self.protected and hitter:is_player()
  2343.     and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then
  2344.         minetest.chat_send_player(hitter:get_player_name(), S("Mob has been protected!"))
  2345.         return
  2346.     end
  2347.  
  2348.  
  2349.     -- weapon wear
  2350.     local weapon = hitter:get_wielded_item()
  2351.     local punch_interval = 1.4
  2352.  
  2353.     -- calculate mob damage
  2354.     local damage = 0
  2355.     local armor = self.object:get_armor_groups() or {}
  2356.     local tmp
  2357.  
  2358.     -- quick error check incase it ends up 0 (serialize.h check test)
  2359.     if tflp == 0 then
  2360.         tflp = 0.2
  2361.     end
  2362.  
  2363.     if use_cmi then
  2364.         damage = cmi.calculate_damage(self.object, hitter, tflp, tool_capabilities, dir)
  2365.     else
  2366.  
  2367.         for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
  2368.  
  2369.             tmp = tflp / (tool_capabilities.full_punch_interval or 1.4)
  2370.  
  2371.             if tmp < 0 then
  2372.                 tmp = 0.0
  2373.             elseif tmp > 1 then
  2374.                 tmp = 1.0
  2375.             end
  2376.  
  2377.             damage = damage + (tool_capabilities.damage_groups[group] or 0)
  2378.                 * tmp * ((armor[group] or 0) / 100.0)
  2379.         end
  2380.     end
  2381.  
  2382.     -- check for tool immunity or special damage
  2383.     for n = 1, #self.immune_to do
  2384.  
  2385.         if self.immune_to[n][1] == weapon:get_name() then
  2386.  
  2387.             damage = self.immune_to[n][2] or 0
  2388.             break
  2389.  
  2390.         -- if "all" then no tool does damage unless it's specified in list
  2391.         elseif self.immune_to[n][1] == "all" then
  2392.             damage = self.immune_to[n][2] or 0
  2393.         end
  2394.     end
  2395.  
  2396.     -- healing
  2397.     if damage <= -1 then
  2398.         self.health = self.health - floor(damage)
  2399.         return
  2400.     end
  2401.  
  2402. --  print ("Mob Damage is", damage)
  2403.  
  2404.     if use_cmi then
  2405.  
  2406.         local cancel =  cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage)
  2407.  
  2408.         if cancel then return end
  2409.     end
  2410.  
  2411.     -- add weapon wear
  2412.     if tool_capabilities then
  2413.         punch_interval = tool_capabilities.full_punch_interval or 1.4
  2414.     end
  2415.  
  2416.     if weapon:get_definition()
  2417.     and weapon:get_definition().tool_capabilities then
  2418.  
  2419.         weapon:add_wear(floor((punch_interval / 75) * 9000))
  2420.         hitter:set_wielded_item(weapon)
  2421.     end
  2422.  
  2423.     -- only play hit sound and show blood effects if damage is 1 or over
  2424.     if damage >= 1 then
  2425.  
  2426.         -- weapon sounds
  2427.         if weapon:get_definition().sounds ~= nil then
  2428.  
  2429.             local s = random(0, #weapon:get_definition().sounds)
  2430.  
  2431.             minetest.sound_play(weapon:get_definition().sounds[s], {
  2432.                 object = self.object, --hitter,
  2433.                 max_hear_distance = 8
  2434.             })
  2435.         else
  2436.             minetest.sound_play("default_punch", {
  2437.                 object = self.object, --hitter,
  2438.                 max_hear_distance = 5
  2439.             })
  2440.         end
  2441.  
  2442.         -- blood_particles
  2443.         if self.blood_amount > 0
  2444.         and not disable_blood then
  2445.  
  2446.             local pos = self.object:get_pos()
  2447.  
  2448.             pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
  2449.  
  2450.             -- do we have a single blood texture or multiple?
  2451.             if type(self.blood_texture) == "table" then
  2452.  
  2453.                 local blood = self.blood_texture[random(1, #self.blood_texture)]
  2454.  
  2455.                 effect(pos, self.blood_amount, blood, nil, nil, 1, nil)
  2456.             else
  2457.                 effect(pos, self.blood_amount, self.blood_texture, nil, nil, 1, nil)
  2458.             end
  2459.         end
  2460.  
  2461.         -- do damage
  2462.         self.health = self.health - floor(damage)
  2463.  
  2464.         -- exit here if dead, special item check
  2465.         if weapon:get_name() == "mobs:pick_lava" then
  2466.             if check_for_death(self, "lava", {type = "punch",
  2467.                     puncher = hitter}) then
  2468.                 return
  2469.             end
  2470.         else
  2471.             if check_for_death(self, "hit", {type = "punch",
  2472.                     puncher = hitter}) then
  2473.                 return
  2474.             end
  2475.         end
  2476.  
  2477.         --[[ add healthy afterglow when hit (can cause hit lag with larger textures)
  2478.         minetest.after(0.1, function()
  2479.  
  2480.             if not self.object:get_luaentity() then return end
  2481.  
  2482.             self.object:settexturemod("^[colorize:#c9900070")
  2483.  
  2484.             core.after(0.3, function()
  2485.                 self.object:settexturemod("")
  2486.             end)
  2487.         end) ]]
  2488.  
  2489.         -- knock back effect (only on full punch)
  2490.         if self.knock_back
  2491.         and tflp >= punch_interval then
  2492.  
  2493.             local v = self.object:get_velocity()
  2494.             local r = 1.4 - min(punch_interval, 1.4)
  2495.             local kb = r * 5
  2496.             local up = 2
  2497.  
  2498.             -- if already in air then dont go up anymore when hit
  2499.             if v.y > 0
  2500.             or self.fly then
  2501.                 up = 0
  2502.             end
  2503.  
  2504.             -- direction error check
  2505.             dir = dir or {x = 0, y = 0, z = 0}
  2506.  
  2507.             -- check if tool already has specific knockback value
  2508.             if tool_capabilities.damage_groups["knockback"] then
  2509.                 kb = tool_capabilities.damage_groups["knockback"]
  2510.             else
  2511.                 kb = kb * 1.5
  2512.             end
  2513.  
  2514.             self.object:set_velocity({
  2515.                 x = dir.x * kb,
  2516.                 y = up,
  2517.                 z = dir.z * kb
  2518.             })
  2519.  
  2520.             self.pause_timer = 0.25
  2521.         end
  2522.     end -- END if damage
  2523.  
  2524.     -- if skittish then run away
  2525.     if self.runaway == true then
  2526.  
  2527.         local lp = hitter:get_pos()
  2528.         local s = self.object:get_pos()
  2529.         local vec = {
  2530.             x = lp.x - s.x,
  2531.             y = lp.y - s.y,
  2532.             z = lp.z - s.z
  2533.         }
  2534.  
  2535.         local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
  2536.  
  2537.         if lp.x > s.x then
  2538.             yaw = yaw + pi
  2539.         end
  2540.  
  2541.         yaw = set_yaw(self, yaw, 6)
  2542.         self.state = "runaway"
  2543.         self.runaway_timer = 0
  2544.         self.following = nil
  2545.     end
  2546.  
  2547.     local name = hitter:get_player_name() or ""
  2548.  
  2549.     -- attack puncher and call other mobs for help
  2550.     if self.passive == false
  2551.     and self.state ~= "flop"
  2552.     and self.child == false
  2553.     and hitter:get_player_name() ~= self.owner
  2554.     and not mobs.invis[ name ] then
  2555.  
  2556.         -- attack whoever punched mob
  2557.         self.state = ""
  2558.         do_attack(self, hitter)
  2559.  
  2560.         -- alert others to the attack
  2561.         local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
  2562.         local obj = nil
  2563.  
  2564.         for n = 1, #objs do
  2565.  
  2566.             obj = objs[n]:get_luaentity()
  2567.  
  2568.             if obj then
  2569.  
  2570.                 -- only alert members of same mob
  2571.                 if obj.group_attack == true
  2572.                 and obj.state ~= "attack"
  2573.                 and obj.owner ~= name
  2574.                 and obj.name == self.name then
  2575.                     do_attack(obj, hitter)
  2576.                 end
  2577.  
  2578.                 -- have owned mobs attack player threat
  2579.                 if obj.owner == name and obj.owner_loyal then
  2580.                     do_attack(obj, self.object)
  2581.                 end
  2582.             end
  2583.         end
  2584.     end
  2585. end
  2586.  
  2587.  
  2588. -- get entity staticdata
  2589. local mob_staticdata = function(self)
  2590.  
  2591.     -- remove mob when out of range unless tamed
  2592.     if remove_far
  2593.     and self.remove_ok
  2594.     and self.type ~= "npc"
  2595.     and self.state ~= "attack"
  2596.     and not self.tamed
  2597.     and self.lifetimer < 20000 then
  2598.  
  2599.         --print ("REMOVED " .. self.name)
  2600.  
  2601.         self.object:remove()
  2602.  
  2603.         return ""-- nil
  2604.     end
  2605.  
  2606.     self.remove_ok = true
  2607.     self.attack = nil
  2608.     self.following = nil
  2609.     self.state = "stand"
  2610.  
  2611.     -- used to rotate older mobs
  2612.     if self.drawtype
  2613.     and self.drawtype == "side" then
  2614.         self.rotate = math.rad(90)
  2615.     end
  2616.  
  2617.     if use_cmi then
  2618.         self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
  2619.     end
  2620.  
  2621.     local tmp = {}
  2622.  
  2623.     for _,stat in pairs(self) do
  2624.  
  2625.         local t = type(stat)
  2626.  
  2627.         if  t ~= "function"
  2628.         and t ~= "nil"
  2629.         and t ~= "userdata"
  2630.         and _ ~= "_cmi_components" then
  2631.             tmp[_] = self[_]
  2632.         end
  2633.     end
  2634.  
  2635.     --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
  2636.     return minetest.serialize(tmp)
  2637. end
  2638.  
  2639.  
  2640. -- activate mob and reload settings
  2641. local mob_activate = function(self, staticdata, def, dtime)
  2642.  
  2643.     -- remove monsters in peaceful mode
  2644.     if self.type == "monster"
  2645.     and peaceful_only then
  2646.  
  2647.         self.object:remove()
  2648.  
  2649.         return
  2650.     end
  2651.  
  2652.     -- load entity variables
  2653.     local tmp = minetest.deserialize(staticdata)
  2654.  
  2655.     if tmp then
  2656.         for _,stat in pairs(tmp) do
  2657.             self[_] = stat
  2658.         end
  2659.     end
  2660.  
  2661.     -- select random texture, set model and size
  2662.     if not self.base_texture then
  2663.  
  2664.         -- compatiblity with old simple mobs textures
  2665.         if type(def.textures[1]) == "string" then
  2666.             def.textures = {def.textures}
  2667.         end
  2668.  
  2669.         self.base_texture = def.textures[random(1, #def.textures)]
  2670.         self.base_mesh = def.mesh
  2671.         self.base_size = self.visual_size
  2672.         self.base_colbox = self.collisionbox
  2673.         self.base_selbox = self.selectionbox
  2674.     end
  2675.  
  2676.     -- for current mobs that dont have this set
  2677.     if not self.base_selbox then
  2678.         self.base_selbox = self.selectionbox or self.base_colbox
  2679.     end
  2680.  
  2681.     -- set texture, model and size
  2682.     local textures = self.base_texture
  2683.     local mesh = self.base_mesh
  2684.     local vis_size = self.base_size
  2685.     local colbox = self.base_colbox
  2686.     local selbox = self.base_selbox
  2687.  
  2688.     -- specific texture if gotten
  2689.     if self.gotten == true
  2690.     and def.gotten_texture then
  2691.         textures = def.gotten_texture
  2692.     end
  2693.  
  2694.     -- specific mesh if gotten
  2695.     if self.gotten == true
  2696.     and def.gotten_mesh then
  2697.         mesh = def.gotten_mesh
  2698.     end
  2699.  
  2700.     -- set child objects to half size
  2701.     if self.child == true then
  2702.  
  2703.         vis_size = {
  2704.             x = self.base_size.x * .5,
  2705.             y = self.base_size.y * .5,
  2706.         }
  2707.  
  2708.         if def.child_texture then
  2709.             textures = def.child_texture[1]
  2710.         end
  2711.  
  2712.         colbox = {
  2713.             self.base_colbox[1] * .5,
  2714.             self.base_colbox[2] * .5,
  2715.             self.base_colbox[3] * .5,
  2716.             self.base_colbox[4] * .5,
  2717.             self.base_colbox[5] * .5,
  2718.             self.base_colbox[6] * .5
  2719.         }
  2720.         selbox = {
  2721.             self.base_selbox[1] * .5,
  2722.             self.base_selbox[2] * .5,
  2723.             self.base_selbox[3] * .5,
  2724.             self.base_selbox[4] * .5,
  2725.             self.base_selbox[5] * .5,
  2726.             self.base_selbox[6] * .5
  2727.         }
  2728.     end
  2729.  
  2730.     if self.health == 0 then
  2731.         self.health = random (self.hp_min, self.hp_max)
  2732.     end
  2733.  
  2734.     -- pathfinding init
  2735.     self.path = {}
  2736.     self.path.way = {} -- path to follow, table of positions
  2737.     self.path.lastpos = {x = 0, y = 0, z = 0}
  2738.     self.path.stuck = false
  2739.     self.path.following = false -- currently following path?
  2740.     self.path.stuck_timer = 0 -- if stuck for too long search for path
  2741.  
  2742.     -- mob defaults
  2743.     self.object:set_armor_groups({immortal = 1, fleshy = self.armor})
  2744.     self.old_y = self.object:get_pos().y
  2745.     self.old_health = self.health
  2746.     self.sounds.distance = self.sounds.distance or 10
  2747.     self.textures = textures
  2748.     self.mesh = mesh
  2749.     self.collisionbox = colbox
  2750.     self.selectionbox = selbox
  2751.     self.visual_size = vis_size
  2752.     self.standing_in = "air"
  2753.  
  2754.     -- check existing nametag
  2755.     if not self.nametag then
  2756.         self.nametag = def.nametag
  2757.     end
  2758.  
  2759.     -- set anything changed above
  2760.     self.object:set_properties(self)
  2761.     set_yaw(self, (random(0, 360) - 180) / 180 * pi, 6)
  2762.     update_tag(self)
  2763.     set_animation(self, "stand")
  2764.  
  2765.     -- run on_spawn function if found
  2766.     if self.on_spawn and not self.on_spawn_run then
  2767.         if self.on_spawn(self) then
  2768.             self.on_spawn_run = true --  if true, set flag to run once only
  2769.         end
  2770.     end
  2771.  
  2772.     -- run after_activate
  2773.     if def.after_activate then
  2774.         def.after_activate(self, staticdata, def, dtime)
  2775.     end
  2776.  
  2777.     if use_cmi then
  2778.         self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
  2779.         cmi.notify_activate(self.object, dtime)
  2780.     end
  2781. end
  2782.  
  2783.  
  2784. -- main mob function
  2785. local mob_step = function(self, dtime)
  2786.  
  2787.     if use_cmi then
  2788.         cmi.notify_step(self.object, dtime)
  2789.     end
  2790.  
  2791.     local pos = self.object:get_pos()
  2792.     local yaw = 0
  2793.  
  2794.     -- when lifetimer expires remove mob (except npc and tamed)
  2795.     if self.type ~= "npc"
  2796.     and not self.tamed
  2797.     and self.state ~= "attack"
  2798.     and remove_far ~= true
  2799.     and self.lifetimer < 20000 then
  2800.  
  2801.         self.lifetimer = self.lifetimer - dtime
  2802.  
  2803.         if self.lifetimer <= 0 then
  2804.  
  2805.             -- only despawn away from player
  2806.             local objs = minetest.get_objects_inside_radius(pos, 15)
  2807.  
  2808.             for n = 1, #objs do
  2809.  
  2810.                 if objs[n]:is_player() then
  2811.  
  2812.                     self.lifetimer = 20
  2813.  
  2814.                     return
  2815.                 end
  2816.             end
  2817.  
  2818. --          minetest.log("action",
  2819. --              S("lifetimer expired, removed @1", self.name))
  2820.  
  2821.             effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
  2822.  
  2823.             self.object:remove()
  2824.  
  2825.             return
  2826.         end
  2827.     end
  2828.  
  2829.     -- get node at foot level every quarter second
  2830.     self.node_timer = (self.node_timer or 0) + dtime
  2831.  
  2832.     if self.node_timer > 0.25 then
  2833.  
  2834.         self.node_timer = 0
  2835.  
  2836.         local y_level = self.collisionbox[2]
  2837.  
  2838.         if self.child then
  2839.             y_level = self.collisionbox[2] * 0.5
  2840.         end
  2841.  
  2842.         -- what is mob standing in?
  2843.         self.standing_in = node_ok({
  2844.             x = pos.x, y = pos.y + y_level + 0.25, z = pos.z}, "air").name
  2845. --      print ("standing in " .. self.standing_in)
  2846.     end
  2847.  
  2848.     -- check if falling, flying, floating
  2849.     falling(self, pos)
  2850.  
  2851.     -- smooth rotation by ThomasMonroe314
  2852.  
  2853.     if self.delay and self.delay > 0 then
  2854.  
  2855.         local yaw = self.object:get_yaw()
  2856.  
  2857.         if self.delay == 1 then
  2858.             yaw = self.target_yaw
  2859.         else
  2860.             local dif = abs(yaw - self.target_yaw)
  2861.  
  2862.             if yaw > self.target_yaw then
  2863.  
  2864.                 if dif > pi then
  2865.                     dif = 2 * pi - dif -- need to add
  2866.                     yaw = yaw + dif / self.delay
  2867.                 else
  2868.                     yaw = yaw - dif / self.delay -- need to subtract
  2869.                 end
  2870.  
  2871.             elseif yaw < self.target_yaw then
  2872.  
  2873.                 if dif > pi then
  2874.                     dif = 2 * pi - dif
  2875.                     yaw = yaw - dif / self.delay -- need to subtract
  2876.                 else
  2877.                     yaw = yaw + dif / self.delay -- need to add
  2878.                 end
  2879.             end
  2880.  
  2881.             if yaw > (pi * 2) then yaw = yaw - (pi * 2) end
  2882.             if yaw < 0 then yaw = yaw + (pi * 2) end
  2883.         end
  2884.  
  2885.         self.delay = self.delay - 1
  2886.         self.object:set_yaw(yaw)
  2887.     end
  2888.  
  2889.     -- end rotation
  2890.  
  2891.     -- knockback timer
  2892.     if self.pause_timer > 0 then
  2893.  
  2894.         self.pause_timer = self.pause_timer - dtime
  2895.  
  2896.         return
  2897.     end
  2898.  
  2899.     -- run custom function (defined in mob lua file)
  2900.     if self.do_custom then
  2901.  
  2902.         -- when false skip going any further
  2903.         if self.do_custom(self, dtime) == false then
  2904.             return
  2905.         end
  2906.     end
  2907.  
  2908.     -- attack timer
  2909.     self.timer = self.timer + dtime
  2910.  
  2911.     if self.state ~= "attack" then
  2912.  
  2913.         if self.timer < 1 then
  2914.             return
  2915.         end
  2916.  
  2917.         self.timer = 0
  2918.     end
  2919.  
  2920.     -- never go over 100
  2921.     if self.timer > 100 then
  2922.         self.timer = 1
  2923.     end
  2924.  
  2925.     -- mob plays random sound at times
  2926.     if random(1, 100) == 1 then
  2927.         mob_sound(self, self.sounds.random)
  2928.     end
  2929.  
  2930.     -- environmental damage timer (every 1 second)
  2931.     self.env_damage_timer = self.env_damage_timer + dtime
  2932.  
  2933.     if (self.state == "attack" and self.env_damage_timer > 1)
  2934.     or self.state ~= "attack" then
  2935.  
  2936.         self.env_damage_timer = 0
  2937.  
  2938.         -- check for environmental damage (water, fire, lava etc.)
  2939.         do_env_damage(self)
  2940.  
  2941.         -- node replace check (cow eats grass etc.)
  2942.         replace(self, pos)
  2943.     end
  2944.  
  2945.     general_attack(self)
  2946.  
  2947.     breed(self)
  2948.  
  2949.     follow_flop(self)
  2950.  
  2951.     do_states(self, dtime)
  2952.  
  2953.     do_jump(self)
  2954.  
  2955.     runaway_from(self)
  2956.  
  2957. end
  2958.  
  2959.  
  2960. -- default function when mobs are blown up with TNT
  2961. local do_tnt = function(obj, damage)
  2962.  
  2963.     --print ("----- Damage", damage)
  2964.  
  2965.     obj.object:punch(obj.object, 1.0, {
  2966.         full_punch_interval = 1.0,
  2967.         damage_groups = {fleshy = damage},
  2968.     }, nil)
  2969.  
  2970.     return false, true, {}
  2971. end
  2972.  
  2973.  
  2974. mobs.spawning_mobs = {}
  2975.  
  2976. -- register mob entity
  2977. function mobs:register_mob(name, def)
  2978.  
  2979.     mobs.spawning_mobs[name] = true
  2980.  
  2981. minetest.register_entity(name, {
  2982.  
  2983.     stepheight = def.stepheight or 1.1, -- was 0.6
  2984.     name = name,
  2985.     type = def.type,
  2986.     attack_type = def.attack_type,
  2987.     fly = def.fly,
  2988.     fly_in = def.fly_in or "air",
  2989.     owner = def.owner or "",
  2990.     order = def.order or "",
  2991.     on_die = def.on_die,
  2992.     do_custom = def.do_custom,
  2993.     jump_height = def.jump_height or 4, -- was 6
  2994.     drawtype = def.drawtype, -- DEPRECATED, use rotate instead
  2995.     rotate = math.rad(def.rotate or 0), --  0=front, 90=side, 180=back, 270=side2
  2996.     lifetimer = def.lifetimer or 180, -- 3 minutes
  2997.     hp_min = max(1, (def.hp_min or 5) * difficulty),
  2998.     hp_max = max(1, (def.hp_max or 10) * difficulty),
  2999.     physical = true,
  3000.     collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
  3001.     selectionbox = def.selectionbox or def.collisionbox,
  3002.     visual = def.visual,
  3003.     visual_size = def.visual_size or {x = 1, y = 1},
  3004.     mesh = def.mesh,
  3005.     makes_footstep_sound = def.makes_footstep_sound or false,
  3006.     view_range = def.view_range or 5,
  3007.     walk_velocity = def.walk_velocity or 1,
  3008.     run_velocity = def.run_velocity or 2,
  3009.     damage = max(0, (def.damage or 0) * difficulty),
  3010.     light_damage = def.light_damage or 0,
  3011.     water_damage = def.water_damage or 0,
  3012.     lava_damage = def.lava_damage or 0,
  3013.     suffocation = def.suffocation or 2,
  3014.     fall_damage = def.fall_damage or 1,
  3015.     fall_speed = def.fall_speed or -10, -- must be lower than -2 (default: -10)
  3016.     drops = def.drops or {},
  3017.     armor = def.armor or 100,
  3018.     on_rightclick = def.on_rightclick,
  3019.     arrow = def.arrow,
  3020.     shoot_interval = def.shoot_interval,
  3021.     sounds = def.sounds or {},
  3022.     animation = def.animation,
  3023.     follow = def.follow,
  3024.     jump = def.jump ~= false,
  3025.     walk_chance = def.walk_chance or 50,
  3026.     passive = def.passive or false,
  3027.     knock_back = def.knock_back ~= false,
  3028.     blood_amount = def.blood_amount or 5,
  3029.     blood_texture = def.blood_texture or "mobs_blood.png",
  3030.     shoot_offset = def.shoot_offset or 0,
  3031.     floats = def.floats or 1, -- floats in water by default
  3032.     replace_rate = def.replace_rate,
  3033.     replace_what = def.replace_what,
  3034.     replace_with = def.replace_with,
  3035.     replace_offset = def.replace_offset or 0,
  3036.     on_replace = def.on_replace,
  3037.     timer = 0,
  3038.     env_damage_timer = 0, -- only used when state = "attack"
  3039.     tamed = false,
  3040.     pause_timer = 0,
  3041.     horny = false,
  3042.     hornytimer = 0,
  3043.     child = false,
  3044.     gotten = false,
  3045.     health = 0,
  3046.     reach = def.reach or 3,
  3047.     htimer = 0,
  3048.     texture_list = def.textures,
  3049.     child_texture = def.child_texture,
  3050.     docile_by_day = def.docile_by_day or false,
  3051.     time_of_day = 0.5,
  3052.     fear_height = def.fear_height or 0,
  3053.     runaway = def.runaway,
  3054.     runaway_timer = 0,
  3055.     pathfinding = def.pathfinding,
  3056.     immune_to = def.immune_to or {},
  3057.     explosion_radius = def.explosion_radius,
  3058.     explosion_damage_radius = def.explosion_damage_radius,
  3059.     explosion_timer = def.explosion_timer or 3,
  3060.     allow_fuse_reset = def.allow_fuse_reset ~= false,
  3061.     stop_to_explode = def.stop_to_explode ~= false,
  3062.     custom_attack = def.custom_attack,
  3063.     double_melee_attack = def.double_melee_attack,
  3064.     dogshoot_switch = def.dogshoot_switch,
  3065.     dogshoot_count = 0,
  3066.     dogshoot_count_max = def.dogshoot_count_max or 5,
  3067.     dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5),
  3068.     attacks_monsters = def.attacks_monsters or false,
  3069.     group_attack = def.group_attack or false,
  3070.     attack_animals = def.attack_animals or false,
  3071.     attack_player = def.attack_player ~= false,
  3072.     attack_npc = def.attack_npc ~= false,
  3073.     specific_attack = def.specific_attack,
  3074.     runaway_from = def.runaway_from,
  3075.     owner_loyal = def.owner_loyal,
  3076.     facing_fence = false,
  3077.     _cmi_is_mob = true,
  3078.  
  3079.     on_spawn = def.on_spawn,
  3080.  
  3081.     on_blast = def.on_blast or do_tnt,
  3082.  
  3083.     on_step = mob_step,
  3084.  
  3085.     do_punch = def.do_punch,
  3086.  
  3087.     on_punch = mob_punch,
  3088.  
  3089.     on_breed = def.on_breed,
  3090.  
  3091.     on_grown = def.on_grown,
  3092.  
  3093.     on_activate = function(self, staticdata, dtime)
  3094.         return mob_activate(self, staticdata, def, dtime)
  3095.     end,
  3096.  
  3097.     get_staticdata = function(self)
  3098.         return mob_staticdata(self)
  3099.     end,
  3100.  
  3101. })
  3102.  
  3103. end -- END mobs:register_mob function
  3104.  
  3105.  
  3106. -- count how many mobs of one type are inside an area
  3107. local count_mobs = function(pos, type)
  3108.  
  3109.     local num_type = 0
  3110.     local num_total = 0
  3111.     local objs = minetest.get_objects_inside_radius(pos, aoc_range)
  3112.  
  3113.     for n = 1, #objs do
  3114.  
  3115.         if not objs[n]:is_player() then
  3116.  
  3117.             local obj = objs[n]:get_luaentity()
  3118.  
  3119.             -- count mob type and add to total also
  3120.             if obj and obj.name and obj.name == type then
  3121.  
  3122.                 num_type = num_type + 1
  3123.                 num_total = num_total + 1
  3124.  
  3125.             -- add to total mobs
  3126.             elseif obj and obj.name and obj.health ~= nil then
  3127.  
  3128.                 num_total = num_total + 1
  3129.             end
  3130.         end
  3131.     end
  3132.  
  3133.     return num_type, num_total
  3134. end
  3135.  
  3136.  
  3137. -- global functions
  3138.  
  3139. function mobs:spawn_abm_check(pos, node, name)
  3140.     -- global function to add additional spawn checks
  3141.     -- return true to stop spawning mob
  3142. end
  3143.  
  3144.  
  3145. function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light,
  3146.     interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
  3147.  
  3148.     -- Do mobs spawn at all?
  3149.     if not mobs_spawn then
  3150.         return
  3151.     end
  3152.  
  3153.     -- chance/spawn number override in minetest.conf for registered mob
  3154.     local numbers = minetest.settings:get(name)
  3155.  
  3156.     if numbers then
  3157.         numbers = numbers:split(",")
  3158.         chance = tonumber(numbers[1]) or chance
  3159.         aoc = tonumber(numbers[2]) or aoc
  3160.  
  3161.         if chance == 0 then
  3162.             minetest.log("warning", string.format("[mobs] %s has spawning disabled", name))
  3163.             return
  3164.         end
  3165.  
  3166.         minetest.log("action",
  3167.             string.format("[mobs] Chance setting for %s changed to %s (total: %s)", name, chance, aoc))
  3168.  
  3169.     end
  3170.  
  3171.     minetest.register_abm({
  3172.  
  3173.         label = name .. " spawning",
  3174.         nodenames = nodes,
  3175.         neighbors = neighbors,
  3176.         interval = interval,
  3177.         chance = max(1, (chance * mob_chance_multiplier)),
  3178.         catch_up = false,
  3179.  
  3180.         action = function(pos, node, active_object_count, active_object_count_wider)
  3181.  
  3182.             -- is mob actually registered?
  3183.             if not mobs.spawning_mobs[name]
  3184.             or not minetest.registered_entities[name] then
  3185. --print ("--- mob doesn't exist", name)
  3186.                 return
  3187.             end
  3188.  
  3189.             -- additional custom checks for spawning mob
  3190.             if mobs:spawn_abm_check(pos, node, name) == true then
  3191.                 return
  3192.             end
  3193.  
  3194.             -- do not spawn if too many of same mob in area
  3195.             if active_object_count_wider >= max_per_block
  3196.             or count_mobs(pos, name) >= aoc then
  3197. --print ("--- too many entities", name, aoc, active_object_count_wider)
  3198.                 return
  3199.             end
  3200.  
  3201.             -- if toggle set to nil then ignore day/night check
  3202.             if day_toggle ~= nil then
  3203.  
  3204.                 local tod = (minetest.get_timeofday() or 0) * 24000
  3205.  
  3206.                 if tod > 4500 and tod < 19500 then
  3207.                     -- daylight, but mob wants night
  3208.                     if day_toggle == false then
  3209. --print ("--- mob needs night", name)
  3210.                         return
  3211.                     end
  3212.                 else
  3213.                     -- night time but mob wants day
  3214.                     if day_toggle == true then
  3215. --print ("--- mob needs day", name)
  3216.                         return
  3217.                     end
  3218.                 end
  3219.             end
  3220.  
  3221.             -- spawn above node
  3222.             pos.y = pos.y + 1
  3223.  
  3224.             -- are we spawning within height limits?
  3225.             if pos.y > max_height
  3226.             or pos.y < min_height then
  3227. --print ("--- height limits not met", name, pos.y)
  3228.                 return
  3229.             end
  3230.  
  3231.             -- are light levels ok?
  3232.             local light = minetest.get_node_light(pos)
  3233.             if not light
  3234.             or light > max_light
  3235.             or light < min_light then
  3236. --print ("--- light limits not met", name, light)
  3237.                 return
  3238.             end
  3239.  
  3240.             -- only spawn away from player
  3241.             local objs = minetest.get_objects_inside_radius(pos, 10)
  3242.  
  3243.             for n = 1, #objs do
  3244.  
  3245.                 if objs[n]:is_player() then
  3246. --print ("--- player too close", name)
  3247.                     return
  3248.                 end
  3249.             end
  3250.  
  3251.             -- do we have enough height clearance to spawn mob?
  3252.             local ent = minetest.registered_entities[name]
  3253.             local height = max(0, math.ceil(ent.collisionbox[5] - ent.collisionbox[2]) - 1)
  3254.  
  3255.             for n = 0, height do
  3256.  
  3257.                 local pos2 = {x = pos.x, y = pos.y + n, z = pos.z}
  3258.  
  3259.                 if minetest.registered_nodes[node_ok(pos2).name].walkable == true then
  3260. --print ("--- inside block", name, node_ok(pos2).name)
  3261.                     return
  3262.                 end
  3263.             end
  3264.  
  3265.             -- mobs cannot spawn in protected areas when enabled
  3266.             if not spawn_protected
  3267.             and minetest.is_protected(pos, "") then
  3268. --print ("--- inside protected area", name)
  3269.                 return
  3270.             end
  3271.  
  3272.             -- spawn mob half block higher than ground
  3273.             pos.y = pos.y + 0.5
  3274.  
  3275.             local mob = minetest.add_entity(pos, name)
  3276. --[[
  3277.             print ("[mobs] Spawned " .. name .. " at "
  3278.             .. minetest.pos_to_string(pos) .. " on "
  3279.             .. node.name .. " near " .. neighbors[1])
  3280. ]]
  3281.             if on_spawn then
  3282.  
  3283.                 local ent = mob:get_luaentity()
  3284.  
  3285.                 on_spawn(ent, pos)
  3286.             end
  3287.         end
  3288.     })
  3289. end
  3290.  
  3291.  
  3292. -- compatibility with older mob registration
  3293. function mobs:register_spawn(name, nodes, max_light, min_light, chance, active_object_count, max_height, day_toggle)
  3294.  
  3295.     mobs:spawn_specific(name, nodes, {"air"}, min_light, max_light, 30,
  3296.         chance, active_object_count, -31000, max_height, day_toggle)
  3297. end
  3298.  
  3299.  
  3300. -- MarkBu's spawn function
  3301. function mobs:spawn(def)
  3302.  
  3303.     mobs:spawn_specific(
  3304.         def.name,
  3305.         def.nodes or {"group:soil", "group:stone"},
  3306.         def.neighbors or {"air"},
  3307.         def.min_light or 0,
  3308.         def.max_light or 15,
  3309.         def.interval or 30,
  3310.         def.chance or 5000,
  3311.         def.active_object_count or 1,
  3312.         def.min_height or -31000,
  3313.         def.max_height or 31000,
  3314.         def.day_toggle,
  3315.         def.on_spawn
  3316.     )
  3317. end
  3318.  
  3319.  
  3320. -- register arrow for shoot attack
  3321. function mobs:register_arrow(name, def)
  3322.  
  3323.     if not name or not def then return end -- errorcheck
  3324.  
  3325.     minetest.register_entity(name, {
  3326.  
  3327.         physical = false,
  3328.         visual = def.visual,
  3329.         visual_size = def.visual_size,
  3330.         textures = def.textures,
  3331.         velocity = def.velocity,
  3332.         hit_player = def.hit_player,
  3333.         hit_node = def.hit_node,
  3334.         hit_mob = def.hit_mob,
  3335.         drop = def.drop or false, -- drops arrow as registered item when true
  3336.         collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows
  3337.         timer = 0,
  3338.         switch = 0,
  3339.         owner_id = def.owner_id,
  3340.         rotate = def.rotate,
  3341.         automatic_face_movement_dir = def.rotate
  3342.             and (def.rotate - (pi / 180)) or false,
  3343.  
  3344.         on_activate = def.on_activate,
  3345.  
  3346.         on_step = def.on_step or function(self, dtime)
  3347.  
  3348.             self.timer = self.timer + 1
  3349.  
  3350.             local pos = self.object:get_pos()
  3351.  
  3352.             if self.switch == 0
  3353.             or self.timer > 150
  3354.             or not within_limits(pos, 0) then
  3355.  
  3356.                 self.object:remove() ; -- print ("removed arrow")
  3357.  
  3358.                 return
  3359.             end
  3360.  
  3361.             -- does arrow have a tail (fireball)
  3362.             if def.tail
  3363.             and def.tail == 1
  3364.             and def.tail_texture then
  3365.  
  3366.                 minetest.add_particle({
  3367.                     pos = pos,
  3368.                     velocity = {x = 0, y = 0, z = 0},
  3369.                     acceleration = {x = 0, y = 0, z = 0},
  3370.                     expirationtime = def.expire or 0.25,
  3371.                     collisiondetection = false,
  3372.                     texture = def.tail_texture,
  3373.                     size = def.tail_size or 5,
  3374.                     glow = def.glow or 0,
  3375.                 })
  3376.             end
  3377.  
  3378.             if self.hit_node then
  3379.  
  3380.                 local node = node_ok(pos).name
  3381.  
  3382.                 if minetest.registered_nodes[node].walkable then
  3383.  
  3384.                     self.hit_node(self, pos, node)
  3385.  
  3386.                     if self.drop == true then
  3387.  
  3388.                         pos.y = pos.y + 1
  3389.  
  3390.                         self.lastpos = (self.lastpos or pos)
  3391.  
  3392.                         minetest.add_item(self.lastpos, self.object:get_luaentity().name)
  3393.                     end
  3394.  
  3395.                     self.object:remove() ; -- print ("hit node")
  3396.  
  3397.                     return
  3398.                 end
  3399.             end
  3400.  
  3401.             if self.hit_player or self.hit_mob then
  3402.  
  3403.                 for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.0)) do
  3404.  
  3405.                     if self.hit_player
  3406.                     and player:is_player() then
  3407.  
  3408.                         self.hit_player(self, player)
  3409.                         self.object:remove() ; -- print ("hit player")
  3410.                         return
  3411.                     end
  3412.  
  3413.                     local entity = player:get_luaentity()
  3414.  
  3415.                     if entity
  3416.                     and self.hit_mob
  3417.                     and entity._cmi_is_mob == true
  3418.                     and tostring(player) ~= self.owner_id
  3419.                     and entity.name ~= self.object:get_luaentity().name then
  3420.  
  3421.                         self.hit_mob(self, player)
  3422.  
  3423.                         self.object:remove() ;  --print ("hit mob")
  3424.  
  3425.                         return
  3426.                     end
  3427.                 end
  3428.             end
  3429.  
  3430.             self.lastpos = pos
  3431.         end
  3432.     })
  3433. end
  3434.  
  3435.  
  3436. -- compatibility function
  3437. function mobs:explosion(pos, radius)
  3438.     local self = {sounds = {}}
  3439.     self.sounds.explode = "tnt_explode"
  3440.     mobs:boom(self, pos, radius)
  3441. end
  3442.  
  3443.  
  3444. -- no damage to nodes explosion
  3445. function mobs:safe_boom(self, pos, radius)
  3446.  
  3447.     minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", {
  3448.         pos = pos,
  3449.         gain = 1.0,
  3450.         max_hear_distance = self.sounds and self.sounds.distance or 32
  3451.     })
  3452.  
  3453.     entity_physics(pos, radius)
  3454.     effect(pos, 32, "tnt_smoke.png", radius * 3, radius * 5, radius, 1, 0)
  3455. end
  3456.  
  3457.  
  3458. -- make explosion with protection and tnt mod check
  3459. function mobs:boom(self, pos, radius)
  3460.  
  3461.     if mobs_griefing
  3462.     and minetest.get_modpath("tnt") and tnt and tnt.boom
  3463.     and not minetest.is_protected(pos, "") then
  3464.  
  3465.         tnt.boom(pos, {
  3466.             radius = radius,
  3467.             damage_radius = radius,
  3468.             sound = self.sounds and self.sounds.explode,
  3469.             explode_center = true,
  3470.         })
  3471.     else
  3472.         mobs:safe_boom(self, pos, radius)
  3473.     end
  3474. end
  3475.  
  3476.  
  3477. -- Register spawn eggs
  3478.  
  3479. -- Note: This also introduces the “spawn_egg” group:
  3480. -- * spawn_egg=1: Spawn egg (generic mob, no metadata)
  3481. -- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
  3482. function mobs:register_egg(mob, desc, background, addegg, no_creative)
  3483.  
  3484.     local grp = {spawn_egg = 1}
  3485.  
  3486.     -- do NOT add this egg to creative inventory (e.g. dungeon master)
  3487.     if creative and no_creative == true then
  3488.         grp.not_in_creative_inventory = 1
  3489.     end
  3490.  
  3491.     local invimg = background
  3492.  
  3493.     if addegg == 1 then
  3494.         invimg = "mobs_chicken_egg.png^(" .. invimg ..
  3495.             "^[mask:mobs_chicken_egg_overlay.png)"
  3496.     end
  3497.  
  3498.     -- register new spawn egg containing mob information
  3499.     minetest.register_craftitem(mob .. "_set", {
  3500.  
  3501.         description = S("@1 (Tamed)", desc),
  3502.         inventory_image = invimg,
  3503.         groups = {spawn_egg = 2, not_in_creative_inventory = 1},
  3504.         stack_max = 1,
  3505.  
  3506.         on_place = function(itemstack, placer, pointed_thing)
  3507.  
  3508.             local pos = pointed_thing.above
  3509.  
  3510.             -- am I clicking on something with existing on_rightclick function?
  3511.             local under = minetest.get_node(pointed_thing.under)
  3512.             local def = minetest.registered_nodes[under.name]
  3513.             if def and def.on_rightclick then
  3514.                 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
  3515.             end
  3516.  
  3517.             if pos
  3518.             and within_limits(pos, 0)
  3519.             and not minetest.is_protected(pos, placer:get_player_name()) then
  3520.  
  3521.                 if not minetest.registered_entities[mob] then
  3522.                     return
  3523.                 end
  3524.  
  3525.                 pos.y = pos.y + 1
  3526.  
  3527.                 local data = itemstack:get_metadata()
  3528.                 local mob = minetest.add_entity(pos, mob, data)
  3529.                 local ent = mob:get_luaentity()
  3530.  
  3531.                 -- set owner if not a monster
  3532.                 if ent.type ~= "monster" then
  3533.                     ent.owner = placer:get_player_name()
  3534.                     ent.tamed = true
  3535.                 end
  3536.  
  3537.                 -- since mob is unique we remove egg once spawned
  3538.                 itemstack:take_item()
  3539.             end
  3540.  
  3541.             return itemstack
  3542.         end,
  3543.     })
  3544.  
  3545.  
  3546.     -- register old stackable mob egg
  3547.     minetest.register_craftitem(mob, {
  3548.  
  3549.         description = desc,
  3550.         inventory_image = invimg,
  3551.         groups = grp,
  3552.  
  3553.         on_place = function(itemstack, placer, pointed_thing)
  3554.  
  3555.             local pos = pointed_thing.above
  3556.  
  3557.             -- am I clicking on something with existing on_rightclick function?
  3558.             local under = minetest.get_node(pointed_thing.under)
  3559.             local def = minetest.registered_nodes[under.name]
  3560.             if def and def.on_rightclick then
  3561.                 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
  3562.             end
  3563.  
  3564.             if pos
  3565.             and within_limits(pos, 0)
  3566.             and not minetest.is_protected(pos, placer:get_player_name()) then
  3567.  
  3568.                 if not minetest.registered_entities[mob] then
  3569.                     return
  3570.                 end
  3571.  
  3572.                 pos.y = pos.y + 1
  3573.  
  3574.                 local mob = minetest.add_entity(pos, mob)
  3575.                 local ent = mob:get_luaentity()
  3576.  
  3577.                 -- don't set owner if monster or sneak pressed
  3578.                 if ent.type ~= "monster"
  3579.                 and not placer:get_player_control().sneak then
  3580.                     ent.owner = placer:get_player_name()
  3581.                     ent.tamed = true
  3582.                 end
  3583.  
  3584.                 -- if not in creative then take item
  3585.                 if not mobs.is_creative(placer:get_player_name()) then
  3586.                     itemstack:take_item()
  3587.                 end
  3588.             end
  3589.  
  3590.             return itemstack
  3591.         end,
  3592.     })
  3593.  
  3594. end
  3595.  
  3596.  
  3597. -- capture critter (thanks to blert2112 for idea)
  3598. function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith)
  3599.  
  3600.     if self.child
  3601.     or not clicker:is_player()
  3602.     or not clicker:get_inventory() then
  3603.         return false
  3604.     end
  3605.  
  3606.     -- get name of clicked mob
  3607.     local mobname = self.name
  3608.  
  3609.     -- if not nil change what will be added to inventory
  3610.     if replacewith then
  3611.         mobname = replacewith
  3612.     end
  3613.  
  3614.     local name = clicker:get_player_name()
  3615.     local tool = clicker:get_wielded_item()
  3616.  
  3617.     -- are we using hand, net or lasso to pick up mob?
  3618.     if tool:get_name() ~= ""
  3619.     and tool:get_name() ~= "mobs:net"
  3620.     and tool:get_name() ~= "mobs:lasso" then
  3621.         return false
  3622.     end
  3623.  
  3624.     -- is mob tamed?
  3625.     if self.tamed == false
  3626.     and force_take == false then
  3627.  
  3628.         minetest.chat_send_player(name, S("Not tamed!"))
  3629.  
  3630.         return true -- false
  3631.     end
  3632.  
  3633.     -- cannot pick up if not owner
  3634.     if self.owner ~= name
  3635.     and force_take == false then
  3636.  
  3637.         minetest.chat_send_player(name, S("@1 is owner!", self.owner))
  3638.  
  3639.         return true -- false
  3640.     end
  3641.  
  3642.     if clicker:get_inventory():room_for_item("main", mobname) then
  3643.  
  3644.         -- was mob clicked with hand, net, or lasso?
  3645.         local chance = 0
  3646.  
  3647.         if tool:get_name() == "" then
  3648.             chance = chance_hand
  3649.  
  3650.         elseif tool:get_name() == "mobs:net" then
  3651.  
  3652.             chance = chance_net
  3653.  
  3654.             tool:add_wear(4000) -- 17 uses
  3655.  
  3656.             clicker:set_wielded_item(tool)
  3657.  
  3658.         elseif tool:get_name() == "mobs:lasso" then
  3659.  
  3660.             chance = chance_lasso
  3661.  
  3662.             tool:add_wear(650) -- 100 uses
  3663.  
  3664.             clicker:set_wielded_item(tool)
  3665.  
  3666.         end
  3667.  
  3668.         -- calculate chance.. add to inventory if successful?
  3669.         if chance > 0 and random(1, 100) <= chance then
  3670.  
  3671.             -- default mob egg
  3672.             local new_stack = ItemStack(mobname)
  3673.  
  3674.             -- add special mob egg with all mob information
  3675.             -- unless 'replacewith' contains new item to use
  3676.             if not replacewith then
  3677.  
  3678.                 new_stack = ItemStack(mobname .. "_set")
  3679.  
  3680.                 local tmp = {}
  3681.  
  3682.                 for _,stat in pairs(self) do
  3683.                     local t = type(stat)
  3684.                     if  t ~= "function"
  3685.                     and t ~= "nil"
  3686.                     and t ~= "userdata" then
  3687.                         tmp[_] = self[_]
  3688.                     end
  3689.                 end
  3690.  
  3691.                 local data_str = minetest.serialize(tmp)
  3692.  
  3693.                 new_stack:set_metadata(data_str)
  3694.             end
  3695.  
  3696.             local inv = clicker:get_inventory()
  3697.  
  3698.             if inv:room_for_item("main", new_stack) then
  3699.                 inv:add_item("main", new_stack)
  3700.             else
  3701.                 minetest.add_item(clicker:get_pos(), new_stack)
  3702.             end
  3703.  
  3704.             self.object:remove()
  3705.  
  3706.             mob_sound(self, "default_place_node_hard")
  3707.  
  3708.         elseif chance ~= 0 then
  3709.             minetest.chat_send_player(name, S("Missed!"))
  3710.  
  3711.             mob_sound(self, "mobs_swing")
  3712.         end
  3713.     end
  3714.  
  3715.     return true
  3716. end
  3717.  
  3718.  
  3719. -- protect tamed mob with rune item
  3720. function mobs:protect(self, clicker)
  3721.  
  3722.     local name = clicker:get_player_name()
  3723.     local tool = clicker:get_wielded_item()
  3724.  
  3725.     if tool:get_name() ~= "mobs:protector" then
  3726.         return false
  3727.     end
  3728.  
  3729.     if self.tamed == false then
  3730.         minetest.chat_send_player(name, S("Not tamed!"))
  3731.         return true -- false
  3732.     end
  3733.  
  3734.     if self.protected == true then
  3735.         minetest.chat_send_player(name, S("Already protected!"))
  3736.         return true -- false
  3737.     end
  3738.  
  3739.     if not mobs.is_creative(clicker:get_player_name()) then
  3740.         tool:take_item() -- take 1 protection rune
  3741.         clicker:set_wielded_item(tool)
  3742.     end
  3743.  
  3744.     self.protected = true
  3745.  
  3746.     local pos = self.object:get_pos()
  3747.     pos.y = pos.y + self.collisionbox[2] + 0.5
  3748.  
  3749.     effect(self.object:get_pos(), 25, "mobs_protect_particle.png", 0.5, 4, 2, 15)
  3750.  
  3751.     mob_sound(self, "mobs_spell")
  3752.  
  3753.     return true
  3754. end
  3755.  
  3756.  
  3757. local mob_obj = {}
  3758. local mob_sta = {}
  3759.  
  3760. -- feeding, taming and breeding (thanks blert2112)
  3761. function mobs:feed_tame(self, clicker, feed_count, breed, tame)
  3762.  
  3763.     if not self.follow then
  3764.         return false
  3765.     end
  3766.  
  3767.     -- can eat/tame with item in hand
  3768.     if follow_holding(self, clicker) then
  3769.  
  3770.         -- if not in creative then take item
  3771.         if not mobs.is_creative(clicker:get_player_name()) then
  3772.  
  3773.             local item = clicker:get_wielded_item()
  3774.  
  3775.             item:take_item()
  3776.  
  3777.             clicker:set_wielded_item(item)
  3778.         end
  3779.  
  3780.         -- increase health
  3781.         self.health = self.health + 4
  3782.  
  3783.         if self.health >= self.hp_max then
  3784.  
  3785.             self.health = self.hp_max
  3786.  
  3787.             if self.htimer < 1 then
  3788.  
  3789.                 minetest.chat_send_player(clicker:get_player_name(),
  3790.                     S("@1 at full health (@2)",
  3791.                     self.name:split(":")[2], tostring(self.health)))
  3792.  
  3793.                 self.htimer = 5
  3794.             end
  3795.         end
  3796.  
  3797.         self.object:set_hp(self.health)
  3798.  
  3799.         update_tag(self)
  3800.  
  3801.         -- make children grow quicker
  3802.         if self.child == true then
  3803.  
  3804.             self.hornytimer = self.hornytimer + 20
  3805.  
  3806.             return true
  3807.         end
  3808.  
  3809.         -- feed and tame
  3810.         self.food = (self.food or 0) + 1
  3811.         if self.food >= feed_count then
  3812.  
  3813.             self.food = 0
  3814.  
  3815.             if breed and self.hornytimer == 0 then
  3816.                 self.horny = true
  3817.             end
  3818.  
  3819.             self.gotten = false
  3820.  
  3821.             if tame then
  3822.  
  3823.                 if self.tamed == false then
  3824.                     minetest.chat_send_player(clicker:get_player_name(),
  3825.                         S("@1 has been tamed!",
  3826.                         self.name:split(":")[2]))
  3827.                 end
  3828.  
  3829.                 self.tamed = true
  3830.  
  3831.                 if not self.owner or self.owner == "" then
  3832.                     self.owner = clicker:get_player_name()
  3833.                 end
  3834.             end
  3835.  
  3836.             -- make sound when fed so many times
  3837.             mob_sound(self, self.sounds.random)
  3838.         end
  3839.  
  3840.         return true
  3841.     end
  3842.  
  3843.     local item = clicker:get_wielded_item()
  3844.  
  3845.     -- if mob has been tamed you can name it with a nametag
  3846.     if item:get_name() == "mobs:nametag"
  3847.     and clicker:get_player_name() == self.owner then
  3848.  
  3849.         local name = clicker:get_player_name()
  3850.  
  3851.         -- store mob and nametag stack in external variables
  3852.         mob_obj[name] = self
  3853.         mob_sta[name] = item
  3854.  
  3855.         local tag = self.nametag or ""
  3856.  
  3857.         minetest.show_formspec(name, "mobs_nametag", "size[8,4]"
  3858.             .. default.gui_bg
  3859.             .. default.gui_bg_img
  3860.             .. "field[0.5,1;7.5,0;name;" .. minetest.formspec_escape(S("Enter name:")) .. ";" .. tag .. "]"
  3861.             .. "button_exit[2.5,3.5;3,1;mob_rename;" .. minetest.formspec_escape(S("Rename")) .. "]")
  3862.     end
  3863.  
  3864.     return false
  3865. end
  3866.  
  3867.  
  3868. -- inspired by blockmen's nametag mod
  3869. minetest.register_on_player_receive_fields(function(player, formname, fields)
  3870.  
  3871.     -- right-clicked with nametag and name entered?
  3872.     if formname == "mobs_nametag"
  3873.     and fields.name
  3874.     and fields.name ~= "" then
  3875.  
  3876.         local name = player:get_player_name()
  3877.  
  3878.         if not mob_obj[name]
  3879.         or not mob_obj[name].object then
  3880.             return
  3881.         end
  3882.  
  3883.         -- make sure nametag is being used to name mob
  3884.         local item = player:get_wielded_item()
  3885.  
  3886.         if item:get_name() ~= "mobs:nametag" then
  3887.             return
  3888.         end
  3889.  
  3890.         -- limit name entered to 64 characters long
  3891.         if string.len(fields.name) > 64 then
  3892.             fields.name = string.sub(fields.name, 1, 64)
  3893.         end
  3894.  
  3895.         -- update nametag
  3896.         mob_obj[name].nametag = fields.name
  3897.  
  3898.         update_tag(mob_obj[name])
  3899.  
  3900.         -- if not in creative then take item
  3901.         if not mobs.is_creative(name) then
  3902.  
  3903.             mob_sta[name]:take_item()
  3904.  
  3905.             player:set_wielded_item(mob_sta[name])
  3906.         end
  3907.  
  3908.         -- reset external variables
  3909.         mob_obj[name] = nil
  3910.         mob_sta[name] = nil
  3911.     end
  3912. end)
  3913.  
  3914.  
  3915. -- compatibility function for old entities to new modpack entities
  3916. function mobs:alias_mob(old_name, new_name)
  3917.  
  3918.     -- spawn egg
  3919.     minetest.register_alias(old_name, new_name)
  3920.  
  3921.     -- entity
  3922.     minetest.register_entity(":" .. old_name, {
  3923.  
  3924.         physical = false,
  3925.  
  3926.         on_step = function(self)
  3927.  
  3928.             if minetest.registered_entities[new_name] then
  3929.                 minetest.add_entity(self.object:get_pos(), new_name)
  3930.             end
  3931.  
  3932.             self.object:remove()
  3933.         end
  3934.     })
  3935. end
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top