diff --git a/README.txt b/README.txt index e4e1282..79d4a5f 100644 --- a/README.txt +++ b/README.txt @@ -28,6 +28,7 @@ This mod contains the following additions: Changelog: +1.17- Added 'dogshoot' attack type, shoots when out of reach, melee attack when in reach, also api tweaks and self.reach added 1.16- Mobs follow multiple items now, Npc's can breed 1.15- Added Feeding/Taming/Breeding function, right-click to pick up any sheep with X mark on them and replace with new one to fix compatibility. 1.14- All .self variables saved in staticdata, Fixed self.health bug diff --git a/api.lua b/api.lua index 1a6d623..03145a9 100644 --- a/api.lua +++ b/api.lua @@ -1,4 +1,4 @@ --- Mobs Api (23rd September 2015) +-- Mobs Api (24th September 2015) mobs = {} mobs.mod = "redo" @@ -10,1210 +10,1186 @@ mobs.protected = tonumber(minetest.setting_get("mobs_spawn_protected")) or 1 mobs.remove = minetest.setting_getbool("remove_far_mobs") function mobs:register_mob(name, def) - minetest.register_entity(name, { - stepheight = def.stepheight or 0.6, - name = name, - fly = def.fly, - fly_in = def.fly_in or "air", - owner = def.owner or "", - order = def.order or "", - on_die = def.on_die, - do_custom = def.do_custom, - jump_height = def.jump_height or 6, - jump_chance = def.jump_chance or 0, - rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2 - lifetimer = def.lifetimer or 180, -- 3 minutes - hp_min = def.hp_min or 5, - hp_max = def.hp_max or 10, - physical = true, - collisionbox = def.collisionbox, - visual = def.visual, - visual_size = def.visual_size or {x = 1, y = 1}, - mesh = def.mesh, - makes_footstep_sound = def.makes_footstep_sound or false, - view_range = def.view_range or 5, - walk_velocity = def.walk_velocity or 1, - run_velocity = def.run_velocity or 2, - damage = def.damage, - light_damage = def.light_damage or 0, - water_damage = def.water_damage or 0, - lava_damage = def.lava_damage or 0, - fall_damage = def.fall_damage or 1, - fall_speed = def.fall_speed or -10, -- must be lower than -2 (default: -10) - drops = def.drops or {}, - armor = def.armor, - on_rightclick = def.on_rightclick, - type = def.type, - attack_type = def.attack_type, - arrow = def.arrow, - shoot_interval = def.shoot_interval, - sounds = def.sounds or {}, - animation = def.animation, - follow = def.follow, -- or "", - jump = def.jump or true, - walk_chance = def.walk_chance or 50, - attacks_monsters = def.attacks_monsters or false, - group_attack = def.group_attack or false, - --fov = def.fov or 120, - passive = def.passive or false, - recovery_time = def.recovery_time or 0.5, - knock_back = def.knock_back or 3, - blood_amount = def.blood_amount or 5, - blood_texture = def.blood_texture or "mobs_blood.png", - shoot_offset = def.shoot_offset or 0, - floats = def.floats or 1, -- floats in water by default - replace_rate = def.replace_rate, - replace_what = def.replace_what, - replace_with = def.replace_with, - replace_offset = def.replace_offset or 0, - timer = 0, - env_damage_timer = 0, -- only if state = "attack" - attack = {player = nil, dist = nil}, - state = "stand", - tamed = false, - pause_timer = 0, - horny = false, - hornytimer = 0, - child = false, - gotten = false, - health = 0, - do_attack = function(self, player, dist) - if self.state ~= "attack" then - if math.random(0,100) < 90 - and self.sounds.war_cry then - minetest.sound_play(self.sounds.war_cry,{ - object = self.object, - max_hear_distance = self.sounds.distance - }) - end - self.state = "attack" - self.attack.player = player - self.attack.dist = dist - end - end, - - set_velocity = function(self, v) - v = (v or 0) - if def.drawtype - and def.drawtype == "side" then - self.rotate = math.rad(90) - end - local yaw = self.object:getyaw() + self.rotate - local x = math.sin(yaw) * -v - local z = math.cos(yaw) * v - self.object:setvelocity({x = x, y = self.object:getvelocity().y, z = z}) - end, - - get_velocity = function(self) - local v = self.object:getvelocity() - return (v.x ^ 2 + v.z ^ 2) ^ (0.5) - end, ---[[ - in_fov = function(self, pos) - -- checks if POS is in self's FOV - local yaw = self.object:getyaw() + self.rotate - local vx = math.sin(yaw) - local vz = math.cos(yaw) - local ds = math.sqrt(vx ^ 2 + vz ^ 2) - local ps = math.sqrt(pos.x ^ 2 + pos.z ^ 2) - local d = {x = vx / ds, z = vz / ds} - local p = {x = pos.x / ps, z = pos.z / ps} - local an = (d.x * p.x) + (d.z * p.z) +minetest.register_entity(name, { + stepheight = def.stepheight or 0.6, + name = name, + fly = def.fly, + fly_in = def.fly_in or "air", + owner = def.owner or "", + order = def.order or "", + on_die = def.on_die, + do_custom = def.do_custom, + jump_height = def.jump_height or 6, + jump_chance = def.jump_chance or 0, + rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2 + lifetimer = def.lifetimer or 180, -- 3 minutes + hp_min = def.hp_min or 5, + hp_max = def.hp_max or 10, + physical = true, + collisionbox = def.collisionbox, + visual = def.visual, + visual_size = def.visual_size or {x = 1, y = 1}, + mesh = def.mesh, + makes_footstep_sound = def.makes_footstep_sound or false, + view_range = def.view_range or 5, + walk_velocity = def.walk_velocity or 1, + run_velocity = def.run_velocity or 2, + damage = def.damage or 0, + light_damage = def.light_damage or 0, + water_damage = def.water_damage or 0, + lava_damage = def.lava_damage or 0, + fall_damage = def.fall_damage or 1, + fall_speed = def.fall_speed or -10, -- must be lower than -2 (default: -10) + drops = def.drops or {}, + armor = def.armor, + on_rightclick = def.on_rightclick, + type = def.type, + attack_type = def.attack_type, + arrow = def.arrow, + shoot_interval = def.shoot_interval, + sounds = def.sounds or {}, + animation = def.animation, + follow = def.follow, + jump = def.jump or true, + walk_chance = def.walk_chance or 50, + attacks_monsters = def.attacks_monsters or false, + group_attack = def.group_attack or false, + --fov = def.fov or 120, + passive = def.passive or false, + recovery_time = def.recovery_time or 0.5, + knock_back = def.knock_back or 3, + blood_amount = def.blood_amount or 5, + blood_texture = def.blood_texture or "mobs_blood.png", + shoot_offset = def.shoot_offset or 0, + floats = def.floats or 1, -- floats in water by default + replace_rate = def.replace_rate, + replace_what = def.replace_what, + replace_with = def.replace_with, + replace_offset = def.replace_offset or 0, + timer = 0, + env_damage_timer = 0, -- only used when state = "attack" + attack = {player = nil, dist = nil}, + state = "stand", + tamed = false, + pause_timer = 0, + horny = false, + hornytimer = 0, + child = false, + gotten = false, + health = 0, + reach = def.reach or 3, - if math.deg(math.acos(an)) > (self.fov / 2) then - return false - end - return true - end, -]] - set_animation = function(self, type) - if not self.animation then - return - end - if not self.animation.current then - self.animation.current = "" - end - if type == "stand" - and self.animation.current ~= "stand" then - if self.animation.stand_start - and self.animation.stand_end - and self.animation.speed_normal then - self.object:set_animation({ - x = self.animation.stand_start, - y = self.animation.stand_end}, - self.animation.speed_normal, 0) - self.animation.current = "stand" - end - elseif type == "walk" - and self.animation.current ~= "walk" then - if self.animation.walk_start - and self.animation.walk_end - and self.animation.speed_normal then - self.object:set_animation({ - x = self.animation.walk_start, - y = self.animation.walk_end}, - self.animation.speed_normal, 0) - self.animation.current = "walk" - end - elseif type == "run" - and self.animation.current ~= "run" then - if self.animation.run_start - and self.animation.run_end - and self.animation.speed_run then - self.object:set_animation({ - x = self.animation.run_start, - y = self.animation.run_end}, - self.animation.speed_run, 0) - self.animation.current = "run" - end - elseif type == "punch" - and self.animation.current ~= "punch" then - if self.animation.punch_start - and self.animation.punch_end - and self.animation.speed_normal then - self.object:set_animation({ - x = self.animation.punch_start, - y = self.animation.punch_end}, - self.animation.speed_normal, 0) - self.animation.current = "punch" - end - end - end, - - on_step = function(self, dtime) - - if (self.type == "monster" and peaceful_only) - or not within_limits(self.object:getpos(), 0) then - self.object:remove() - return - end - - -- if lifetimer run out and not npc; tamed or attacking then remove mob - if self.type ~= "npc" - and not self.tamed then - self.lifetimer = self.lifetimer - dtime - if self.lifetimer <= 0 - and self.state ~= "attack" then - minetest.log("action", - "lifetimer expired, removed "..self.name) - effect(self.object:getpos(), 15, "tnt_smoke.png") - self.object:remove() - return - end - end - - -- check for mob drop/replace (used for chicken egg and sheep eating grass/wheat) - if self.replace_rate - and self.child == false - and math.random(1,self.replace_rate) == 1 then - local pos = self.object:getpos() - pos.y = pos.y + self.replace_offset - -- print ("replace node = ".. minetest.get_node(pos).name, pos.y) - if self.replace_what - and self.object:getvelocity().y == 0 - and #minetest.find_nodes_in_area(pos, pos, self.replace_what) > 0 then - --and self.state == "stand" then - minetest.set_node(pos, {name = self.replace_with}) - end - end - - local yaw = 0 - - if not self.fly then - -- floating in water (or falling) - local pos = self.object:getpos() - local nod = minetest.get_node_or_nil(pos) - if nod then nod = nod.name else nod = "default:dirt" end - local nodef = minetest.registered_nodes[nod] - - local v = self.object:getvelocity() - if v.y > 0.1 then - self.object:setacceleration({ - x = 0, - y = self.fall_speed, - z = 0 - }) - end - if nodef.groups.water then - if self.floats == 1 then - self.object:setacceleration({ - x = 0, - y = -self.fall_speed / (math.max(1, v.y) ^ 2), - z = 0 - }) - end - else - self.object:setacceleration({ - x = 0, - y = self.fall_speed, - z = 0 - }) - - -- fall damage - if self.fall_damage == 1 - and self.object:getvelocity().y == 0 then - local d = (self.old_y or 0) - self.object:getpos().y - if d > 5 then - self.object:set_hp(self.object:get_hp() - math.floor(d - 5)) - effect(self.object:getpos(), 5, "tnt_smoke.png") - check_for_death(self) - end - self.old_y = self.object:getpos().y - end - end - end - - -- knockback timer - if self.pause_timer > 0 then - self.pause_timer = self.pause_timer - dtime - if self.pause_timer < 1 then - self.pause_timer = 0 - end - return - end - - -- attack timer - self.timer = self.timer + dtime - if self.state ~= "attack" then - if self.timer < 1 then - return - end - self.timer = 0 - end - - if self.sounds.random - and math.random(1, 100) <= 1 then - minetest.sound_play(self.sounds.random, { + do_attack = function(self, player) + if self.state ~= "attack" then + if math.random(0,100) < 90 + and self.sounds.war_cry then + minetest.sound_play(self.sounds.war_cry,{ object = self.object, max_hear_distance = self.sounds.distance }) end - - local do_env_damage = function(self) + self.state = "attack" + self.attack.player = player + end + end, - local pos = self.object:getpos() - local tod = minetest.get_timeofday() - - -- daylight above ground - if self.light_damage ~= 0 - and pos.y > 0 - and tod > 0.2 - and tod < 0.8 - and (minetest.get_node_light(pos) or 0) > 12 then - self.object:set_hp(self.object:get_hp() - self.light_damage) - effect(pos, 5, "tnt_smoke.png") - if check_for_death(self) then return end - end - - pos.y = pos.y + self.collisionbox[2] -- foot level - local nod = minetest.get_node_or_nil(pos) - if not nod then return end ; -- print ("standing in "..nod.name) - local nodef = minetest.registered_nodes[nod.name] - pos.y = pos.y + 1 - - -- water - if self.water_damage ~= 0 - and nodef.groups.water then - self.object:set_hp(self.object:get_hp() - self.water_damage) - effect(pos, 5, "bubble.png") - if check_for_death(self) then return end - end - - -- lava or fire - if self.lava_damage ~= 0 - and (nodef.groups.lava - or nod.name == "fire:basic_flame" - or nod.name == "xanadu:safe_fire") then - self.object:set_hp(self.object:get_hp() - self.lava_damage) - effect(pos, 5, "fire_basic_flame.png") - if check_for_death(self) then return end - end + set_velocity = function(self, v) + v = (v or 0) + if def.drawtype + and def.drawtype == "side" then + self.rotate = math.rad(90) + end + local yaw = self.object:getyaw() + self.rotate + local x = math.sin(yaw) * -v + local z = math.cos(yaw) * v + self.object:setvelocity({x = x, y = self.object:getvelocity().y, z = z}) + end, + get_velocity = function(self) + local v = self.object:getvelocity() + return (v.x ^ 2 + v.z ^ 2) ^ (0.5) + end, +--[[ + in_fov = function(self, pos) + -- checks if POS is in self's FOV + local yaw = self.object:getyaw() + self.rotate + local vx = math.sin(yaw) + local vz = math.cos(yaw) + local ds = math.sqrt(vx ^ 2 + vz ^ 2) + local ps = math.sqrt(pos.x ^ 2 + pos.z ^ 2) + local d = {x = vx / ds, z = vz / ds} + local p = {x = pos.x / ps, z = pos.z / ps} + local an = (d.x * p.x) + (d.z * p.z) + if math.deg(math.acos(an)) > (self.fov / 2) then + return false + end + return true + end, +]] + set_animation = function(self, type) + if not self.animation then + return + end + if not self.animation.current then + self.animation.current = "" + end + if type == "stand" + and self.animation.current ~= "stand" then + if self.animation.stand_start + and self.animation.stand_end + and self.animation.speed_normal then + self.object:set_animation({ + x = self.animation.stand_start, + y = self.animation.stand_end}, + self.animation.speed_normal, 0) + self.animation.current = "stand" end - - local do_jump = function(self) - if self.fly then - return - end + elseif type == "walk" + and self.animation.current ~= "walk" then + if self.animation.walk_start + and self.animation.walk_end + and self.animation.speed_normal then + self.object:set_animation({ + x = self.animation.walk_start, + y = self.animation.walk_end}, + self.animation.speed_normal, 0) + self.animation.current = "walk" + end + elseif type == "run" + and self.animation.current ~= "run" then + if self.animation.run_start + and self.animation.run_end + and self.animation.speed_run then + self.object:set_animation({ + x = self.animation.run_start, + y = self.animation.run_end}, + self.animation.speed_run, 0) + self.animation.current = "run" + end + elseif type == "punch" + and self.animation.current ~= "punch" then + if self.animation.punch_start + and self.animation.punch_end + and self.animation.speed_normal then + self.object:set_animation({ + x = self.animation.punch_start, + y = self.animation.punch_end}, + self.animation.speed_normal, 0) + self.animation.current = "punch" + end + end + end, - self.jumptimer = (self.jumptimer or 0) + 1 - if self.jumptimer < 3 then - local pos = self.object:getpos() - pos.y = (pos.y + self.collisionbox[2]) - 0.2 - local nod = minetest.get_node_or_nil(pos) ---print ("standing on:", nod.name, pos.y) - if not nod - or not minetest.registered_nodes[nod.name] - or minetest.registered_nodes[nod.name].walkable == false then - return - end - if self.direction then - pos.y = pos.y + 0.5 - local nod = minetest.get_node_or_nil({ - x = pos.x + self.direction.x, - y = pos.y, - z = pos.z + self.direction.z - }) ---print ("in front:", nod.name, pos.y) - if nod and nod.name and - (nod.name ~= "air" - or self.walk_chance == 0) then - local def = minetest.registered_items[nod.name] - if (def - and def.walkable - and not nod.name:find("fence")) - or self.walk_chance == 0 then - local v = self.object:getvelocity() - v.y = self.jump_height + 1 - v.x = v.x * 2.2 - v.z = v.z * 2.2 - self.object:setvelocity(v) - if self.sounds.jump then - minetest.sound_play(self.sounds.jump, { - object = self.object, - max_hear_distance = self.sounds.distance - }) - end - end - end - end - else - self.jumptimer = 0 - end - end - - -- environmental damage timer (every 1 second) - self.env_damage_timer = self.env_damage_timer + dtime - if self.state == "attack" - and self.env_damage_timer > 1 then - self.env_damage_timer = 0 - do_env_damage(self) - -- custom function (defined in mob lua file) - if self.do_custom then - self.do_custom(self) - end - elseif self.state ~= "attack" then - do_env_damage(self) - -- custom function - if self.do_custom then - self.do_custom(self) - end - end - - -- find someone to attack - if self.type == "monster" - and damage_enabled + on_step = function(self, dtime) + + -- remove monsters if playing on peaceful + if (self.type == "monster" and peaceful_only) + or not within_limits(self.object:getpos(), 0) then + self.object:remove() + return + end + + -- when lifetimer expires remove mob (except npc and tamed) + if self.type ~= "npc" + and not self.tamed then + self.lifetimer = self.lifetimer - dtime + if self.lifetimer <= 0 and self.state ~= "attack" then + minetest.log("action", + "lifetimer expired, removed "..self.name) + effect(self.object:getpos(), 15, "tnt_smoke.png") + self.object:remove() + return + end + end - local s = self.object:getpos() - local p, sp, dist - local player = nil - local type = nil - local obj = nil - local min_dist = self.view_range + 1 - local min_player = nil + -- node replace check (chicken lays egg, cows eating grass etc) + if self.replace_rate + and self.child == false + and math.random(1, self.replace_rate) == 1 then + local pos = self.object:getpos() + pos.y = pos.y + self.replace_offset + -- print ("replace node = ".. minetest.get_node(pos).name, pos.y) + if self.replace_what + and self.object:getvelocity().y == 0 + and #minetest.find_nodes_in_area(pos, pos, self.replace_what) > 0 then + minetest.set_node(pos, {name = self.replace_with}) + end + end - for _,oir in ipairs(minetest.get_objects_inside_radius(s, self.view_range)) do + local yaw = 0 - if oir:is_player() then - player = oir - type = "player" - else - obj = oir:get_luaentity() - if obj then - player = obj.object - type = obj.type - end - end - - if type == "player" - or type == "npc" then - s = self.object:getpos() - p = player:getpos() - sp = s - p.y = p.y + 1 - sp.y = sp.y + 1 -- aim higher to make looking up hills more realistic - dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - if dist < self.view_range then - -- and self.in_fov(self,p) then - -- choose closest player to attack - if minetest.line_of_sight(sp, p, 2) == true - and dist < min_dist then - min_dist = dist - min_player = player - end - end - end + if not self.fly then + -- floating in water (or falling) + local pos = self.object:getpos() + local nod = minetest.get_node_or_nil(pos) + if nod then nod = nod.name else nod = "default:dirt" end + local nodef = minetest.registered_nodes[nod] + + local v = self.object:getvelocity() + if v.y > 0.1 then + self.object:setacceleration({ + x = 0, + y = self.fall_speed, + z = 0 + }) + end + if nodef.groups.water then + if self.floats == 1 then + self.object:setacceleration({ + x = 0, + y = -self.fall_speed / (math.max(1, v.y) ^ 2), + z = 0 + }) end - -- attack player - if min_player then - self.do_attack(self, min_player, min_dist) + else + self.object:setacceleration({ + x = 0, + y = self.fall_speed, + z = 0 + }) + + -- fall damage + if self.fall_damage == 1 + and self.object:getvelocity().y == 0 then + local d = (self.old_y or 0) - self.object:getpos().y + if d > 5 then + self.object:set_hp(self.object:get_hp() - math.floor(d - 5)) + effect(self.object:getpos(), 5, "tnt_smoke.png") + check_for_death(self) + end + self.old_y = self.object:getpos().y end end - - -- npc, find closest monster to attack + end + + -- knockback timer + if self.pause_timer > 0 then + self.pause_timer = self.pause_timer - dtime + if self.pause_timer < 1 then + self.pause_timer = 0 + end + return + end + + -- attack timer + self.timer = self.timer + dtime + if self.state ~= "attack" then + if self.timer < 1 then + return + end + self.timer = 0 + end + + if self.sounds.random + and math.random(1, 100) <= 1 then + minetest.sound_play(self.sounds.random, { + object = self.object, + max_hear_distance = self.sounds.distance + }) + end + + local do_env_damage = function(self) + + local pos = self.object:getpos() + local tod = minetest.get_timeofday() + + -- daylight above ground + if self.light_damage ~= 0 + and pos.y > 0 + and tod > 0.2 + and tod < 0.8 + and (minetest.get_node_light(pos) or 0) > 12 then + self.object:set_hp(self.object:get_hp() - self.light_damage) + effect(pos, 5, "tnt_smoke.png") + if check_for_death(self) then return end + end + +if self.water_damage ~= 0 or self.lava_damage ~= 0 then + pos.y = pos.y + self.collisionbox[2] -- foot level + local nod = minetest.get_node_or_nil(pos) + if not nod then return end ; -- print ("standing in "..nod.name) + local nodef = minetest.registered_nodes[nod.name] + pos.y = pos.y + 1 + + -- water + if self.water_damage ~= 0 + and nodef.groups.water then + self.object:set_hp(self.object:get_hp() - self.water_damage) + effect(pos, 5, "bubble.png") + if check_for_death(self) then return end + end + + -- lava or fire + if self.lava_damage ~= 0 + and (nodef.groups.lava + or nod.name == "fire:basic_flame" + or nod.name == "xanadu:safe_fire") then + self.object:set_hp(self.object:get_hp() - self.lava_damage) + effect(pos, 5, "fire_basic_flame.png") + if check_for_death(self) then return end + end +end + end + + local do_jump = function(self) + if self.fly then + return + end + + local pos = self.object:getpos() + pos.y = (pos.y + self.collisionbox[2]) - 0.2 + local nod = minetest.get_node_or_nil(pos) +--print ("standing on:", nod.name, pos.y) + if not nod + or not minetest.registered_nodes[nod.name] + or minetest.registered_nodes[nod.name].walkable == false then + return + end + if self.direction then + pos.y = pos.y + 0.5 + local nod = minetest.get_node_or_nil({ + x = pos.x + self.direction.x, + y = pos.y, + z = pos.z + self.direction.z + }) +--print ("in front:", nod.name, pos.y) + if nod and nod.name and + (nod.name ~= "air" + or self.walk_chance == 0) then + local def = minetest.registered_items[nod.name] + if (def + and def.walkable + and not nod.name:find("fence")) + or self.walk_chance == 0 then + local v = self.object:getvelocity() + v.y = self.jump_height + 1 + v.x = v.x * 2.2 + v.z = v.z * 2.2 + self.object:setvelocity(v) + if self.sounds.jump then + minetest.sound_play(self.sounds.jump, { + object = self.object, + max_hear_distance = self.sounds.distance + }) + end + end + end + end + end + + -- environmental damage timer (every 1 second) + self.env_damage_timer = self.env_damage_timer + dtime + if self.state == "attack" + and self.env_damage_timer > 1 then + self.env_damage_timer = 0 + do_env_damage(self) + -- custom function (defined in mob lua file) + if self.do_custom then + self.do_custom(self) + end + elseif self.state ~= "attack" then + do_env_damage(self) + -- custom function + if self.do_custom then + self.do_custom(self) + end + end + + -- find someone to attack + if self.type == "monster" + and damage_enabled + and self.state ~= "attack" then + + local s = self.object:getpos() + local p, sp, dist + local player = nil + local type = nil + local obj = nil local min_dist = self.view_range + 1 local min_player = nil - if self.type == "npc" - and self.attacks_monsters - and self.state ~= "attack" then - local s = self.object:getpos() - local obj = nil - for _, oir in pairs(minetest.get_objects_inside_radius(s,self.view_range)) do + for _,oir in ipairs(minetest.get_objects_inside_radius(s, self.view_range)) do + + if oir:is_player() then + player = oir + type = "player" + else obj = oir:get_luaentity() - if obj - and obj.type == "monster" then - -- attack monster - p = obj.object:getpos() - dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - if dist < min_dist then - min_dist = dist - min_player = obj.object - end + if obj then + player = obj.object + type = obj.type end end - if min_player then - self.do_attack(self, min_player, min_dist) - end - end - -- horny animal can mate for 40 seconds, afterwards horny animal cannot mate again for 200 seconds - if self.horny == true - and self.hornytimer < 240 - and self.child == false then - self.hornytimer = self.hornytimer + 1 - if self.hornytimer >= 240 then - self.hornytimer = 0 - self.horny = false - end - end - - -- if animal is child take 240 seconds before growing into adult - if self.child == true then - self.hornytimer = self.hornytimer + 1 - if self.hornytimer > 240 then - self.child = false - self.hornytimer = 0 - self.object:set_properties({ - textures = self.base_texture, - mesh = self.base_mesh, - visual_size = self.base_size, - collisionbox = self.base_colbox, - }) - -- jump when grown to now fall into ground - local v = self.object:getvelocity() - v.y = self.jump_height - v.x = 0 ; v.z = 0 - self.object:setvelocity(v) - end - end - - -- if animal is horny, find another same animal who is horny and mate - if self.horny == true - and self.hornytimer <= 40 then - local pos = self.object:getpos() - effect({x = pos.x, y = pos.y + 1, z = pos.z}, 4, "heart.png") - local ents = minetest.get_objects_inside_radius(pos, self.view_range) - local num = 0 - local ent = nil - for i,obj in ipairs(ents) do - ent = obj:get_luaentity() - - -- check for same animal with different colour - local canmate = false - if ent then - if ent.name == self.name then - canmate = true - else - local entname = string.split(ent.name,":") - local selfname = string.split(self.name,":") - if entname[1] == selfname[1] then - entname = string.split(entname[2],"_") - selfname = string.split(selfname[2],"_") - if entname[1] == selfname[1] then - canmate = true - end - end - end - end - - if ent - and canmate == true - and ent.horny == true - and ent.hornytimer <= 40 then - num = num + 1 - end - if num > 1 then - self.hornytimer = 41 - ent.hornytimer = 41 - minetest.after(7, function(dtime) - local mob = minetest.add_entity(pos, self.name) - local ent2 = mob:get_luaentity() - local textures = self.base_texture - if def.child_texture then - textures = def.child_texture[1] - end - mob:set_properties({ - textures = textures, - visual_size = { - x = self.base_size.x / 2, - y = self.base_size.y / 2 - }, - collisionbox = { - self.base_colbox[1] / 2, - self.base_colbox[2] / 2, - self.base_colbox[3] / 2, - self.base_colbox[4] / 2, - self.base_colbox[5] / 2, - self.base_colbox[6] / 2 - }, - }) - ent2.child = true - ent2.tamed = true - ent2.owner = self.owner - end) - num = 0 - break - end - end - end - - -- find player to follow - if (self.follow ~= "" - or self.order == "follow") - and not self.following - and self.state ~= "attack" then - local s, p, dist - for _,player in pairs(minetest.get_connected_players()) do + if type == "player" + or type == "npc" then s = self.object:getpos() p = player:getpos() + sp = s + p.y = p.y + 1 + sp.y = sp.y + 1 -- aim higher to make looking up hills more realistic dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 if dist < self.view_range then - self.following = player - break + -- and self.in_fov(self,p) then + -- choose closest player to attack + if minetest.line_of_sight(sp, p, 2) == true + and dist < min_dist then + min_dist = dist + min_player = player + end end end end + -- attack player + if min_player then + self.do_attack(self, min_player) + end + end - if self.type == "npc" - and self.order == "follow" - and self.state ~= "attack" - and self.owner ~= "" then - -- npc stop following player if not owner - if self.following - and self.owner - and self.owner ~= self.following:get_player_name() then - self.following = nil - end - else - -- stop following player if not holding specific item - if self.following - and self.following.is_player - --and self.following:get_wielded_item():get_name() ~= self.follow then - and follow_holding(self, self.following) == false then - self.following = nil + -- npc, find closest monster to attack + local min_dist = self.view_range + 1 + local min_player = nil + + if self.type == "npc" + and self.attacks_monsters + and self.state ~= "attack" then + local s = self.object:getpos() + local obj = nil + for _, oir in pairs(minetest.get_objects_inside_radius(s,self.view_range)) do + obj = oir:get_luaentity() + if obj + and obj.type == "monster" then + -- attack monster + p = obj.object:getpos() + dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 + if dist < min_dist then + min_dist = dist + min_player = obj.object + end end end + if min_player then + self.do_attack(self, min_player) + end + end - -- follow player or mob - if self.following then - local s = self.object:getpos() - local p + -- horny animal can mate for 40 seconds, afterwards horny animal cannot mate again for 200 seconds + if self.horny == true + and self.hornytimer < 240 + and self.child == false then + self.hornytimer = self.hornytimer + 1 + if self.hornytimer >= 240 then + self.hornytimer = 0 + self.horny = false + end + end - if self.following.is_player - and self.following:is_player() then - p = self.following:getpos() - elseif self.following.object then - p = self.following.object:getpos() - end + -- if animal is child take 240 seconds before growing into adult + if self.child == true then + self.hornytimer = self.hornytimer + 1 + if self.hornytimer > 240 then + self.child = false + self.hornytimer = 0 + self.object:set_properties({ + textures = self.base_texture, + mesh = self.base_mesh, + visual_size = self.base_size, + collisionbox = self.base_colbox, + }) + -- jump when grown so not to fall into ground + local v = self.object:getvelocity() + v.y = self.jump_height + v.x = 0 ; v.z = 0 + self.object:setvelocity(v) + end + end - if p then - local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - if dist > self.view_range then - self.following = nil + -- if animal is horny, find another same animal who is horny and mate + if self.horny == true + and self.hornytimer <= 40 then + local pos = self.object:getpos() + effect({x = pos.x, y = pos.y + 1, z = pos.z}, 4, "heart.png") + local ents = minetest.get_objects_inside_radius(pos, self.view_range) + local num = 0 + local ent = nil + for i,obj in ipairs(ents) do + ent = obj:get_luaentity() + + -- check for same animal with different colour + local canmate = false + if ent then + if ent.name == self.name then + canmate = true else - local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} - yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate - if p.x > s.x then - yaw = yaw + math.pi - end - self.object:setyaw(yaw) - - -- anyone but standing npc's can move along - if dist > 3 -- was 2 - and self.order ~= "stand" then - if (self.jump - and self.get_velocity(self) <= 0.5 - and self.object:getvelocity().y == 0) - or (self.object:getvelocity().y == 0 - and self.jump_chance > 0) then - self.direction = { - x = math.sin(yaw) * -1, - y = -20, - z = math.cos(yaw) - } - do_jump(self) + local entname = string.split(ent.name,":") + local selfname = string.split(self.name,":") + if entname[1] == selfname[1] then + entname = string.split(entname[2],"_") + selfname = string.split(selfname[2],"_") + if entname[1] == selfname[1] then + canmate = true end - self.set_velocity(self, self.walk_velocity) - if self.walk_chance ~= 0 then - self:set_animation("walk") - end - else - self.set_velocity(self, 0) - self:set_animation("stand") end - return end end + + if ent + and canmate == true + and ent.horny == true + and ent.hornytimer <= 40 then + num = num + 1 + end + if num > 1 then + self.hornytimer = 41 + ent.hornytimer = 41 + minetest.after(7, function(dtime) + local mob = minetest.add_entity(pos, self.name) + local ent2 = mob:get_luaentity() + local textures = self.base_texture + if def.child_texture then + textures = def.child_texture[1] + end + mob:set_properties({ + textures = textures, + visual_size = { + x = self.base_size.x / 2, + y = self.base_size.y / 2 + }, + collisionbox = { + self.base_colbox[1] / 2, + self.base_colbox[2] / 2, + self.base_colbox[3] / 2, + self.base_colbox[4] / 2, + self.base_colbox[5] / 2, + self.base_colbox[6] / 2 + }, + }) + ent2.child = true + ent2.tamed = true + ent2.owner = self.owner + end) + num = 0 + break + end end + end - if self.state == "stand" then - -- randomly turn - if math.random(1, 4) == 1 then - -- if there is a player nearby look at them - local lp = nil - local s = self.object:getpos() - - if self.type == "npc" then - local o = minetest.get_objects_inside_radius(self.object:getpos(), 3) - - local yaw = 0 - for _,o in ipairs(o) do - if o:is_player() then - lp = o:getpos() - break - end - end - end - - if lp ~= nil then - local vec = {x = lp.x - s.x, y = lp.y - s.y, z = lp.z - s.z} - yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate - if lp.x > s.x then - yaw = yaw + math.pi - end - else - yaw = self.object:getyaw() + ((math.random(0, 360) - 180) / 180 * math.pi) - end - self.object:setyaw(yaw) + -- find player to follow + if (self.follow ~= "" + or self.order == "follow") + and not self.following + and self.state ~= "attack" then + local s, p, dist + for _,player in pairs(minetest.get_connected_players()) do + s = self.object:getpos() + p = player:getpos() + dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 + if dist < self.view_range then + self.following = player + break end + end + end - self.set_velocity(self, 0) - self.set_animation(self, "stand") + if self.type == "npc" + and self.order == "follow" + and self.state ~= "attack" + and self.owner ~= "" then + -- npc stop following player if not owner + if self.following + and self.owner + and self.owner ~= self.following:get_player_name() then + self.following = nil + end + else + -- stop following player if not holding specific item + if self.following + and self.following.is_player + and follow_holding(self, self.following) == false then + self.following = nil + end + end - -- npc's ordered to stand stay standing - if self.type == "npc" - and self.order == "stand" then - self.set_velocity(self, 0) - self.state = "stand" - self:set_animation("stand") + -- follow player or mob + if self.following then + local s = self.object:getpos() + local p + + if self.following.is_player + and self.following:is_player() then + p = self.following:getpos() + elseif self.following.object then + p = self.following.object:getpos() + end + + if p then + local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 + + -- dont follow if out of range + if dist > self.view_range then + self.following = nil else - if self.walk_chance ~= 0 - and math.random(1, 100) <= self.walk_chance then - self.set_velocity(self, self.walk_velocity) - self.state = "walk" - self.set_animation(self, "walk") - end - - -- jumping mobs only --- if self.jump and math.random(1, 100) <= self.jump_chance then --- self.direction = {x = 0, y = 0, z = 0} --- do_jump(self) --- self.set_velocity(self, self.walk_velocity) --- end - end - - elseif self.state == "walk" then - local s = self.object:getpos() - local lp = minetest.find_node_near(s, 1, {"group:water"}) - --- water swimmers cannot move out of water -if self.fly -and self.fly_in == "default:water_source" -and not lp then - print ("out of water") - self.set_velocity(self, 0) - self.state = "flop" -- change to undefined state so nothing more happens - self:set_animation("stand") - return -end - -- if water nearby then turn away - if lp then - local vec = {x = lp.x - s.x, y = lp.y - s.y, z = lp.z - s.z} - yaw = math.atan(vec.z / vec.x) + 3 * math.pi / 2 - self.rotate - if lp.x > s.x then + local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} + yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate + if p.x > s.x then yaw = yaw + math.pi end self.object:setyaw(yaw) - -- otherwise randomly turn - elseif math.random(1, 100) <= 30 then - self.object:setyaw(self.object:getyaw() + ((math.random(0, 360) - 180) / 180 * math.pi)) + -- anyone but standing npc's can move along + if dist > 3 + and self.order ~= "stand" then + if (self.jump + and self.get_velocity(self) <= 0.5 + and self.object:getvelocity().y == 0) + or (self.object:getvelocity().y == 0 + and self.jump_chance > 0) then + self.direction = { + x = math.sin(yaw) * -1, + y = 0, + z = math.cos(yaw) + } + do_jump(self) + end + self.set_velocity(self, self.walk_velocity) + if self.walk_chance ~= 0 then + self:set_animation("walk") + end + else + self.set_velocity(self, 0) + self:set_animation("stand") + end + return end - if self.jump and self.get_velocity(self) <= 0.5 - and self.object:getvelocity().y == 0 then + end + end + + if self.state == "stand" then + + if math.random(1, 4) == 1 then + local lp = nil + local s = self.object:getpos() + + if self.type == "npc" then + local o = minetest.get_objects_inside_radius(self.object:getpos(), 3) + local yaw = 0 + for _,o in ipairs(o) do + if o:is_player() then + lp = o:getpos() + break + end + end + end + + -- look at any players nearby, otherwise turn randomly + if lp ~= nil then + local vec = {x = lp.x - s.x, y = lp.y - s.y, z = lp.z - s.z} + yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate + if lp.x > s.x then + yaw = yaw + math.pi + end + else + yaw = self.object:getyaw() + ((math.random(0, 360) - 180) / 180 * math.pi) + end + self.object:setyaw(yaw) + end + + self.set_velocity(self, 0) + self.set_animation(self, "stand") + + -- npc's ordered to stand stay standing + if self.type == "npc" + and self.order == "stand" then + self.set_velocity(self, 0) + self.state = "stand" + self:set_animation("stand") + else + if self.walk_chance ~= 0 + and math.random(1, 100) <= self.walk_chance then + self.set_velocity(self, self.walk_velocity) + self.state = "walk" + self.set_animation(self, "walk") + end + end + + elseif self.state == "walk" then + local s = self.object:getpos() + local lp = minetest.find_node_near(s, 1, {"group:water"}) + + -- water swimmers cannot move out of water + if self.fly + and self.fly_in == "default:water_source" + and not lp then + print ("out of water") + self.set_velocity(self, 0) + -- change to undefined state so nothing more happens + self.state = "flop" + self:set_animation("stand") + return + end + + -- if water nearby then turn away + if lp then + local vec = {x = lp.x - s.x, y = lp.y - s.y, z = lp.z - s.z} + yaw = math.atan(vec.z / vec.x) + 3 * math.pi / 2 - self.rotate + if lp.x > s.x then + yaw = yaw + math.pi + end + self.object:setyaw(yaw) + -- otherwise randomly turn + elseif math.random(1, 100) <= 30 then + self.object:setyaw(self.object:getyaw() + ((math.random(0, 360) - 180) / 180 * math.pi)) + end + + -- jump when walking comes to a halt + if self.jump and self.get_velocity(self) <= 0.5 + and self.object:getvelocity().y == 0 then + self.direction = { + x = math.sin(yaw) * -1, + y = 0, + z = math.cos(yaw) + } + do_jump(self) + end + + self:set_animation("walk") + self.set_velocity(self, self.walk_velocity) + if math.random(1, 100) <= 30 then + self.set_velocity(self, 0) + self.state = "stand" + self:set_animation("stand") + end + + -- attack routines (explode, dogfight, shoot, dogshoot) + elseif self.state == "attack" then + + -- calculate distance from mob and enemy + local s = self.object:getpos() + local p = self.attack.player:getpos() + if not p then p = s end + local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 + + -- stop attacking if no player or out of range + if dist > self.view_range + or not self.attack.player + or not self.attack.player:getpos() + or self.attack.player:get_hp() <= 0 then + --print(" ** stop attacking **", dist, self.view_range) + self.state = "stand" + self.set_velocity(self, 0) + self:set_animation("stand") + self.attack = {player = nil, dist = nil} + self.v_start = false + self.timer = 0 + self.blinktimer = 0 + return + end + + if self.attack_type == "explode" then + + local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} + yaw = math.atan(vec.z / vec.x) + math.pi / 2 - self.rotate + if p.x > s.x then + yaw = yaw + math.pi + end + self.object:setyaw(yaw) + + if dist > self.reach then + if not self.v_start then + self.v_start = true + self.set_velocity(self, self.run_velocity) + self.timer = 0 + self.blinktimer = 0 + else + self.timer = 0 + self.blinktimer = 0 + if self.get_velocity(self) <= 0.5 + and self.object:getvelocity().y == 0 then + local v = self.object:getvelocity() + v.y = 5 + self.object:setvelocity(v) + end + self.set_velocity(self, self.run_velocity) + end + self:set_animation("run") + else + self.set_velocity(self, 0) + self.timer = self.timer + dtime + self.blinktimer = (self.blinktimer or 0) + dtime + if self.blinktimer > 0.2 then + self.blinktimer = 0 + if self.blinkstatus then + self.object:settexturemod("") + else + self.object:settexturemod("^[brighten") + end + self.blinkstatus = not self.blinkstatus + end + if self.timer > 3 then + local pos = vector.round(self.object:getpos()) + -- hurt player/mobs caught in blast area + entity_physics(pos, 3) + if minetest.find_node_near(pos, 1, {"group:water"}) + or minetest.is_protected(pos, "") then + self.object:remove() + if self.sounds.explode ~= "" then + minetest.sound_play(self.sounds.explode, { + pos = pos, + gain = 1.0, + max_hear_distance = 16 + }) + end + effect(pos, 15, "tnt_smoke.png", 5) + return + end + self.object:remove() + pos.y = pos.y - 1 + mobs:explosion(pos, 2, 0, 1, self.sounds.explode) + end + end + + elseif self.attack_type == "dogfight" + or (self.attack_type == "dogshoot" and dist <= self.reach) then + + -- fly bit modified from BlockMens creatures mod + if self.fly + and dist > self.reach then + + local nod = minetest.get_node_or_nil(s) + local p1 = s + local me_y = math.floor(p1.y) + local p2 = p + local p_y = math.floor(p2.y + 1) + local v = self.object:getvelocity() + if nod + and nod.name == self.fly_in then + if me_y < p_y then + self.object:setvelocity({ + x = v.x, + y = 1 * self.walk_velocity, + z = v.z + }) + elseif me_y > p_y then + self.object:setvelocity({ + x = v.x, + y = -1 * self.walk_velocity, + z = v.z + }) + end + else + if me_y < p_y then + self.object:setvelocity({ + x = v.x, + y = 0.01, + z = v.z + }) + elseif me_y > p_y then + self.object:setvelocity({ + x = v.x, + y = -0.01, + z = v.z + }) + end + end + + end + -- end fly bit + + -- ignore enemy if out of range + if dist > self.view_range + or self.attack.player:get_hp() <= 0 then + self.state = "stand" + self.set_velocity(self, 0) + self.attack = {player = nil, dist = nil} + self:set_animation("stand") + return + end + + local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} + yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate + if p.x > s.x then + yaw = yaw + math.pi + end + self.object:setyaw(yaw) + + -- move towards enemy if beyond mob reach + -- set reach for each mob (default is 3) + if dist > self.reach then + -- jump attack + if (self.jump + and self.get_velocity(self) <= 0.5 + and self.object:getvelocity().y == 0) + or (self.object:getvelocity().y == 0 + and self.jump_chance > 0) then self.direction = { x = math.sin(yaw) * -1, - y = -20, + y = 0, z = math.cos(yaw) } do_jump(self) end - - self:set_animation("walk") - self.set_velocity(self, self.walk_velocity) - if math.random(1, 100) <= 30 then - self.set_velocity(self, 0) - self.state = "stand" - self:set_animation("stand") - end - - -- exploding mobs - elseif self.state == "attack" and self.attack_type == "explode" then - if not self.attack.player - or not self.attack.player:is_player() then - self.state = "stand" - self:set_animation("stand") - self.timer = 0 - self.blinktimer = 0 - return - end - local s = self.object:getpos() - local p = self.attack.player:getpos() - local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - if dist > self.view_range or self.attack.player:get_hp() <= 0 then - self.state = "stand" - self.v_start = false - self.set_velocity(self, 0) - self.timer = 0 - self.blinktimer = 0 - self.attack = {player = nil, dist = nil} - self:set_animation("stand") - return - else - self:set_animation("walk") - self.attack.dist = dist - end - - local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} - yaw = math.atan(vec.z / vec.x) + math.pi / 2 - self.rotate - if p.x > s.x then - yaw = yaw + math.pi - end - self.object:setyaw(yaw) - if self.attack.dist > 3 then - if not self.v_start then - self.v_start = true - self.set_velocity(self, self.run_velocity) - self.timer = 0 - self.blinktimer = 0 - else - self.timer = 0 - self.blinktimer = 0 - if self.get_velocity(self) <= 0.5 - and self.object:getvelocity().y == 0 then - local v = self.object:getvelocity() - v.y = 5 - self.object:setvelocity(v) - end - self.set_velocity(self, self.run_velocity) - end - self:set_animation("run") - else - self.set_velocity(self, 0) - self.timer = self.timer + dtime - self.blinktimer = (self.blinktimer or 0) + dtime - if self.blinktimer > 0.2 then - self.blinktimer = 0 - if self.blinkstatus then - self.object:settexturemod("") - else - self.object:settexturemod("^[brighten") - end - self.blinkstatus = not self.blinkstatus - end - if self.timer > 3 then - local pos = vector.round(self.object:getpos()) - entity_physics(pos, 3) -- hurt player/mobs caught in blast area - if minetest.find_node_near(pos, 1, {"group:water"}) - or minetest.is_protected(pos, "") then - self.object:remove() - if self.sounds.explode ~= "" then - minetest.sound_play(self.sounds.explode, { - pos = pos, - gain = 1.0, - max_hear_distance = 16 - }) - end - effect(pos, 15, "tnt_smoke.png", 5) - return - end - self.object:remove() - pos.y = pos.y - 1 - mobs:explosion(pos, 2, 0, 1, self.sounds.explode) - end - end - -- end of exploding mobs - - elseif self.state == "attack" - and self.attack_type == "dogfight" then - if not self.attack.player - or not self.attack.player:getpos() then - print("stop attacking") - self.state = "stand" - self:set_animation("stand") - return - end - local s = self.object:getpos() - local p = self.attack.player:getpos() - local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - - -- fly bit modified from BlockMens creatures mod - if self.fly - and dist > 2 then - - local nod = minetest.get_node_or_nil(s) - local p1 = s - local me_y = math.floor(p1.y) - local p2 = p - local p_y = math.floor(p2.y + 1) - local v = self.object:getvelocity() - if nod - and nod.name == self.fly_in then - if me_y < p_y then - self.object:setvelocity({ - x = v.x, - y = 1 * self.walk_velocity, - z = v.z - }) - elseif me_y > p_y then - self.object:setvelocity({ - x = v.x, - y = -1 * self.walk_velocity, - z = v.z - }) - end - else - if me_y < p_y then - self.object:setvelocity({ - x = v.x, - y = 0.01, - z = v.z - }) - elseif me_y > p_y then - self.object:setvelocity({ - x = v.x, - y = -0.01, - z = v.z - }) - end - end - - end - -- end fly bit - - if dist > self.view_range - or self.attack.player:get_hp() <= 0 then - self.state = "stand" - self.set_velocity(self, 0) - self.attack = {player = nil, dist = nil} - self:set_animation("stand") - return - else - self.attack.dist = dist - end - - local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} - yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate - if p.x > s.x then - yaw = yaw + math.pi - end - self.object:setyaw(yaw) - -- attack distance is 2 + half of mob width so the bigger mobs can attack (like slimes) - if self.attack.dist > ((-self.collisionbox[1] + self.collisionbox[4]) / 2) + 2 then - -- jump attack - if (self.jump - and self.get_velocity(self) <= 0.5 - and self.object:getvelocity().y == 0) - or (self.object:getvelocity().y == 0 - and self.jump_chance > 0) then - self.direction = { - x = math.sin(yaw) * -1, - y = -20, - z = math.cos(yaw) - } - do_jump(self) - end - self.set_velocity(self, self.run_velocity) - self:set_animation("run") - else - self.set_velocity(self, 0) - self:set_animation("punch") - if self.timer > 1 then - self.timer = 0 - local p2 = p - local s2 = s - p2.y = p2.y + 1.5 - s2.y = s2.y + 1.5 - if minetest.line_of_sight(p2, s2) == true then - if self.sounds.attack then - minetest.sound_play(self.sounds.attack, { - object = self.object, - max_hear_distance = self.sounds.distance - }) - end - self.attack.player:punch(self.object, 1.0, { - full_punch_interval=1.0, - damage_groups = {fleshy=self.damage} - }, vec) - if self.attack.player:get_hp() <= 0 then - self.state = "stand" - self:set_animation("stand") - end - end - end - end - - elseif self.state == "attack" - and self.attack_type == "shoot" then - - local s = self.object:getpos() - local p = self.attack.player:getpos() - if not p then - self.state = "stand" - return - end - p.y = p.y - .5 - s.y = s.y + .5 - local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - if dist > self.view_range - or self.attack.player:get_hp() <= 0 then - self.state = "stand" - self.set_velocity(self, 0) - self:set_animation("stand") - return - else - self.attack.dist = dist - end - - local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} - yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate - if p.x > s.x then - yaw = yaw + math.pi - end - self.object:setyaw(yaw) - self.set_velocity(self, 0) - - if self.shoot_interval - and self.timer > self.shoot_interval - and math.random(1, 100) <= 60 then - self.timer = 0 - - self:set_animation("punch") - - if self.sounds.attack then - minetest.sound_play(self.sounds.attack, { - object = self.object, - max_hear_distance = self.sounds.distance - }) - end - - local p = self.object:getpos() - p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2 - local obj = minetest.add_entity(p, self.arrow) - local amount = (vec.x ^ 2 + vec.y ^ 2 + vec.z ^ 2) ^ 0.5 - local v = obj:get_luaentity().velocity - vec.y = vec.y + self.shoot_offset -- this makes shoot aim accurate - vec.x = vec.x *v / amount - vec.y = vec.y *v / amount - vec.z = vec.z *v / amount - obj:setvelocity(vec) - end - end - end, - - on_activate = function(self, staticdata, dtime_s) - - if self.type == "monster" - and peaceful_only then - self.object:remove() - end - - -- load entity variables - if staticdata then - local tmp = minetest.deserialize(staticdata) - if tmp then - for _,stat in pairs(tmp) do - self[_] = stat - end - end - end - - -- select random texture, set model and size - if not self.base_texture then - self.base_texture = def.textures[math.random(1, #def.textures)] - self.base_mesh = def.mesh - self.base_size = self.visual_size - self.base_colbox = self.collisionbox - end - - -- set texture, model and size - local textures = self.base_texture - local mesh = self.base_mesh - local vis_size = self.base_size - local colbox = self.base_colbox - - -- specific texture if gotten - if self.gotten == true - and def.gotten_texture then - textures = def.gotten_texture - end - - -- specific mesh if gotten - if self.gotten == true - and def.gotten_mesh then - mesh = def.gotten_mesh - end - - -- if object is child then set half size - if self.child == true then - vis_size = { - x = self.base_size.x / 2, - y = self.base_size.y / 2 - } - if def.child_texture then - textures = def.child_texture[1] - end - colbox = { - self.base_colbox[1] / 2, - self.base_colbox[2] / 2, - self.base_colbox[3] / 2, - self.base_colbox[4] / 2, - self.base_colbox[5] / 2, - self.base_colbox[6] / 2 - } - end - - if self.health == 0 then - self.health = math.random (self.hp_min, self.hp_max) - end - - self.object:set_hp( self.health ) - self.object:set_armor_groups({fleshy = self.armor}) - self.state = "stand" - --self.order = "stand" - self.following = nil - self.old_y = self.object:getpos().y - self.object:setyaw(math.random(1, 360) / 180 * math.pi) - self.sounds.distance = (self.sounds.distance or 10) - self.textures = textures - self.mesh = mesh - self.collisionbox = colbox - self.visual_size = vis_size - -- set anything changed above - self.object:set_properties(self) - - end, - - get_staticdata = function(self) - - -- remove mob when out of range unless tamed - if mobs.remove and self.remove_ok and not self.tamed then - print ("REMOVED", self.remove_ok, self.name) - self.object:remove() - end - self.remove_ok = true - self.attack = nil - self.following = nil - - local tmp = {} - for _,stat in pairs(self) do - local t = type(stat) - if t ~= 'function' - and t ~= 'nil' - and t ~= 'userdata' then - tmp[_] = self[_] - end - end - -- print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n') - return minetest.serialize(tmp) - end, - - on_punch = function(self, hitter, tflp, tool_capabilities, dir) - -- weapon wear - local weapon = hitter:get_wielded_item() - local punch_interval = tool_capabilities.full_punch_interval or 1.4 - if weapon:get_definition().tool_capabilities ~= nil then - local wear = (punch_interval / 75) * 9000 - weapon:add_wear(wear) - hitter:set_wielded_item(weapon) - end - - -- weapon sounds - if weapon:get_definition().sounds ~= nil then - local s = math.random(0, #weapon:get_definition().sounds) - minetest.sound_play(weapon:get_definition().sounds[s], { - object=hitter, - max_hear_distance = 8 - }) + self.set_velocity(self, self.run_velocity) + self:set_animation("run") else - minetest.sound_play("default_punch", { - object = hitter, - max_hear_distance = 5 - }) + self.set_velocity(self, 0) + self:set_animation("punch") + if self.timer > 1 then + self.timer = 0 + local p2 = p + local s2 = s + p2.y = p2.y + 1.5 + s2.y = s2.y + 1.5 + if minetest.line_of_sight(p2, s2) == true then + if self.sounds.attack then + minetest.sound_play(self.sounds.attack, { + object = self.object, + max_hear_distance = self.sounds.distance + }) + end + self.attack.player:punch(self.object, 1.0, { + full_punch_interval=1.0, + damage_groups = {fleshy=self.damage} + }, vec) + if self.attack.player:get_hp() <= 0 then + self.state = "stand" + self:set_animation("stand") + end + end + end end - -- exit here if dead - if check_for_death(self) then + elseif self.attack_type == "shoot" + or (self.attack_type == "dogshoot" and dist > self.reach) then + + local s = self.object:getpos() + local p = self.attack.player:getpos() + if not p then + self.state = "stand" return end + p.y = p.y - .5 + s.y = s.y + .5 + local dist = ((p.x - s.x) ^ 2 + (p.y - s.y) ^ 2 + (p.z - s.z) ^ 2) ^ 0.5 - -- blood_particles - if self.blood_amount > 0 - and not disable_blood then - local pos = self.object:getpos() - pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) / 2 - effect(pos, self.blood_amount, self.blood_texture) + local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} + yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate + if p.x > s.x then + yaw = yaw + math.pi end + self.object:setyaw(yaw) + self.set_velocity(self, 0) - -- knock back effect - if self.knock_back > 0 then - local kb = self.knock_back - local r = self.recovery_time - local v = self.object:getvelocity() - if tflp < tool_capabilities.full_punch_interval then - if kb > 0 then - kb = kb * ( tflp / tool_capabilities.full_punch_interval ) - end - r = r * ( tflp / tool_capabilities.full_punch_interval ) + if self.shoot_interval + and self.timer > self.shoot_interval + and math.random(1, 100) <= 60 then + self.timer = 0 + self:set_animation("punch") + + if self.sounds.attack then + minetest.sound_play(self.sounds.attack, { + object = self.object, + max_hear_distance = self.sounds.distance + }) end - self.object:setvelocity({x = dir.x * kb,y = 0,z = dir.z * kb}) - self.pause_timer = r + + local p = self.object:getpos() + p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2 + local obj = minetest.add_entity(p, self.arrow) + local amount = (vec.x ^ 2 + vec.y ^ 2 + vec.z ^ 2) ^ 0.5 + local v = obj:get_luaentity().velocity + -- offset makes shoot aim accurate + vec.y = vec.y + self.shoot_offset + vec.x = vec.x *v / amount + vec.y = vec.y *v / amount + vec.z = vec.z *v / amount + obj:setvelocity(vec) end + end - -- attack puncher and call other mobs for help - if self.passive == false - and not self.tamed then - if self.state ~= "attack" then - self.do_attack(self, hitter, 1) + end -- END if self.state == "attack" + end, + + on_activate = function(self, staticdata, dtime_s) + + if self.type == "monster" + and peaceful_only then + self.object:remove() + end + + -- load entity variables + if staticdata then + local tmp = minetest.deserialize(staticdata) + if tmp then + for _,stat in pairs(tmp) do + self[_] = stat end - -- alert others to the attack - local obj = nil - for _, oir in pairs(minetest.get_objects_inside_radius(hitter:getpos(), 5)) do - obj = oir:get_luaentity() - if obj then - if obj.group_attack == true - and obj.state ~= "attack" then - obj.do_attack(obj, hitter, 1) - end + end + end + + -- select random texture, set model and size + if not self.base_texture then + self.base_texture = def.textures[math.random(1, #def.textures)] + self.base_mesh = def.mesh + self.base_size = self.visual_size + self.base_colbox = self.collisionbox + end + + -- set texture, model and size + local textures = self.base_texture + local mesh = self.base_mesh + local vis_size = self.base_size + local colbox = self.base_colbox + + -- specific texture if gotten + if self.gotten == true + and def.gotten_texture then + textures = def.gotten_texture + end + + -- specific mesh if gotten + if self.gotten == true + and def.gotten_mesh then + mesh = def.gotten_mesh + end + + -- set child objects to half size + if self.child == true then + vis_size = { + x = self.base_size.x / 2, + y = self.base_size.y / 2 + } + if def.child_texture then + textures = def.child_texture[1] + end + colbox = { + self.base_colbox[1] / 2, + self.base_colbox[2] / 2, + self.base_colbox[3] / 2, + self.base_colbox[4] / 2, + self.base_colbox[5] / 2, + self.base_colbox[6] / 2 + } + end + + if self.health == 0 then + self.health = math.random (self.hp_min, self.hp_max) + end + + self.object:set_hp(self.health) + self.object:set_armor_groups({fleshy = self.armor}) + self.state = "stand" + self.following = nil + self.old_y = self.object:getpos().y + self.object:setyaw(math.random(1, 360) / 180 * math.pi) + self.sounds.distance = (self.sounds.distance or 10) + self.textures = textures + self.mesh = mesh + self.collisionbox = colbox + self.visual_size = vis_size + -- set anything changed above + self.object:set_properties(self) + end, + + get_staticdata = function(self) + + -- remove mob when out of range unless tamed + if mobs.remove and self.remove_ok and not self.tamed then + print ("REMOVED", self.remove_ok, self.name) + self.object:remove() + end + self.remove_ok = true + self.attack = {player = nil, dist = nil} + self.following = nil + + local tmp = {} + for _,stat in pairs(self) do + local t = type(stat) + if t ~= 'function' + and t ~= 'nil' + and t ~= 'userdata' then + tmp[_] = self[_] + end + end + -- print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n') + return minetest.serialize(tmp) + end, + + on_punch = function(self, hitter, tflp, tool_capabilities, dir) + -- weapon wear + local weapon = hitter:get_wielded_item() + local punch_interval = tool_capabilities.full_punch_interval or 1.4 + if weapon:get_definition().tool_capabilities ~= nil then + local wear = (punch_interval / 75) * 9000 + weapon:add_wear(wear) + hitter:set_wielded_item(weapon) + end + + -- weapon sounds + if weapon:get_definition().sounds ~= nil then + local s = math.random(0, #weapon:get_definition().sounds) + minetest.sound_play(weapon:get_definition().sounds[s], { + object=hitter, + max_hear_distance = 8 + }) + else + minetest.sound_play("default_punch", { + object = hitter, + max_hear_distance = 5 + }) + end + + -- exit here if dead + if check_for_death(self) then + return + end + + -- blood_particles + if self.blood_amount > 0 + and not disable_blood then + local pos = self.object:getpos() + pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) / 2 + effect(pos, self.blood_amount, self.blood_texture) + end + + -- knock back effect + if self.knock_back > 0 then + local kb = self.knock_back + local r = self.recovery_time + local v = self.object:getvelocity() + if tflp < tool_capabilities.full_punch_interval then + if kb > 0 then + kb = kb * ( tflp / tool_capabilities.full_punch_interval ) + end + r = r * ( tflp / tool_capabilities.full_punch_interval ) + end + self.object:setvelocity({x = dir.x * kb,y = 0,z = dir.z * kb}) + self.pause_timer = r + end + + -- attack puncher and call other mobs for help + if self.passive == false + and not self.tamed then + if self.state ~= "attack" then + self.do_attack(self, hitter) + end + -- alert others to the attack + local obj = nil + for _, oir in pairs(minetest.get_objects_inside_radius(hitter:getpos(), 5)) do + obj = oir:get_luaentity() + if obj then + if obj.group_attack == true + and obj.state ~= "attack" then + obj.do_attack(obj, hitter) end end end - end, - }) -end + end + end, +}) + +end -- END mobs:register_mob function mobs.spawning_mobs = {} @@ -1539,7 +1515,7 @@ function mobs:register_egg(mob, desc, background, addegg) local mob = minetest.add_entity(pos, mob) local ent = mob:get_luaentity() if ent.type ~= "monster" then - -- set owner + -- set owner and tame ent.owner = placer:get_player_name() ent.tamed = true end diff --git a/bunny.lua b/bunny.lua index 8162ebf..952e878 100644 --- a/bunny.lua +++ b/bunny.lua @@ -4,6 +4,7 @@ mobs:register_mob("mobs:bunny", { type = "animal", passive = true, + reach = 1, hp_min = 1, hp_max = 4, armor = 200, @@ -55,7 +56,6 @@ mobs:register_mob("mobs:bunny", { textures = {"mobs_bunny_evil.png"}, }) self.type = "monster" - self.state = "attack" self.object:set_hp(20) return end diff --git a/cow.lua b/cow.lua index 94991ca..bf386ca 100644 --- a/cow.lua +++ b/cow.lua @@ -5,6 +5,7 @@ mobs:register_mob("mobs:cow", { type = "animal", passive = false, attack_type = "dogfight", + reach = 2, damage = 4, hp_min = 5, hp_max = 20, diff --git a/dirtmonster.lua b/dirtmonster.lua index cd1a0e4..b57dd08 100644 --- a/dirtmonster.lua +++ b/dirtmonster.lua @@ -5,6 +5,7 @@ mobs:register_mob("mobs:dirt_monster", { type = "monster", passive = false, attack_type = "dogfight", + reach = 2, damage = 2, hp_min = 3, hp_max = 27, diff --git a/dungeonmaster.lua b/dungeonmaster.lua index 9cb9e88..ebbb824 100644 --- a/dungeonmaster.lua +++ b/dungeonmaster.lua @@ -5,7 +5,8 @@ mobs:register_mob("mobs:dungeon_master", { type = "monster", passive = false, damage = 4, - attack_type = "shoot", + attack_type = "dogshoot", + reach = 3, shoot_interval = 2.5, arrow = "mobs:fireball", shoot_offset = 1, diff --git a/lava_flan.lua b/lava_flan.lua index f4bc18d..7fbaa2e 100644 --- a/lava_flan.lua +++ b/lava_flan.lua @@ -5,6 +5,7 @@ mobs:register_mob("mobs:lava_flan", { type = "monster", passive = false, attack_type = "dogfight", + reach = 2, damage = 3, hp_min = 10, hp_max = 35, diff --git a/oerkki.lua b/oerkki.lua index 9f37f68..7917fcf 100644 --- a/oerkki.lua +++ b/oerkki.lua @@ -5,6 +5,7 @@ mobs:register_mob("mobs:oerkki", { type = "monster", passive = false, attack_type = "dogfight", + reach = 2, damage = 4, hp_min = 8, hp_max = 34, diff --git a/sandmonster.lua b/sandmonster.lua index 206ac7f..25e4663 100644 --- a/sandmonster.lua +++ b/sandmonster.lua @@ -5,6 +5,7 @@ mobs:register_mob("mobs:sand_monster", { type = "monster", passive = false, attack_type = "dogfight", + reach = 2, damage = 1, hp_min = 4, hp_max = 20, diff --git a/spider.lua b/spider.lua index 059d96e..115936e 100644 --- a/spider.lua +++ b/spider.lua @@ -5,6 +5,7 @@ mobs:register_mob("mobs:spider", { type = "monster", passive = false, attack_type = "dogfight", + reach = 2, damage = 3, hp_min = 20, hp_max = 40, diff --git a/stonemonster.lua b/stonemonster.lua index 015d1cb..9c13019 100644 --- a/stonemonster.lua +++ b/stonemonster.lua @@ -5,6 +5,7 @@ mobs:register_mob("mobs:stone_monster", { type = "monster", passive = false, attack_type = "dogfight", + reach = 2, damage = 3, hp_min = 12, hp_max = 35, diff --git a/treemonster.lua b/treemonster.lua index d2f1438..6df4dd1 100644 --- a/treemonster.lua +++ b/treemonster.lua @@ -5,6 +5,7 @@ mobs:register_mob("mobs:tree_monster", { type = "monster", passive = false, attack_type = "dogfight", + reach = 2, damage = 2, hp_min = 7, hp_max = 33, diff --git a/warthog.lua b/warthog.lua index fa43e19..2e8b714 100644 --- a/warthog.lua +++ b/warthog.lua @@ -5,6 +5,7 @@ mobs:register_mob("mobs:pumba", { type = "animal", passive = false, attack_type = "dogfight", + reach = 2, damage = 2, hp_min = 5, hp_max = 15,