diff --git a/mods/ENTITIES/extra_mobs/strider.lua b/mods/ENTITIES/extra_mobs/strider.lua index 184716156..d6c854207 100644 --- a/mods/ENTITIES/extra_mobs/strider.lua +++ b/mods/ENTITIES/extra_mobs/strider.lua @@ -124,7 +124,6 @@ local strider = { if wielditem:get_name() ~= controlitem then if mobs:feed_tame(self, clicker, 1, true, true) then return end end - if mobs:protect(self, clicker) then return end if self.child then return @@ -190,9 +189,6 @@ local strider = { inv:set_stack("main",self.driver:get_wield_index(), wielditem) end return - - elseif not self.driver and clicker:get_wielded_item():get_name() ~= "" then - mobs:capture_mob(self, clicker, 0, 5, 60, false, nil) end end, } diff --git a/mods/ENTITIES/mcl_mobs/api.txt b/mods/ENTITIES/mcl_mobs/api.txt index eda74aeb4..5d49ca7ef 100644 --- a/mods/ENTITIES/mcl_mobs/api.txt +++ b/mods/ENTITIES/mcl_mobs/api.txt @@ -502,20 +502,6 @@ and damages any entity caught inside the blast radius. Protection will limit node destruction but not entity damage. -mobs:capture_mob ----------------- - -mobs:capture_mob(...) - -Does nothing and returns false. - -This function is provided for compability with Mobs Redo for an attempt to -capture a mob. -Mobs cannot be captured in MineClone 2. - -In Mobs Redo, this is generally called inside the on_rightclick section of the mob -api code, it provides a chance of capturing the mob. See Mobs Redo documentation -of parameters. Feeding and Taming/Breeding --------------------------- @@ -535,19 +521,6 @@ Will return true when mob is fed with item it likes. them up -Protecting Mobs ---------------- - -mobs:protect(self, clicker) - -This function can be used to right-click any tamed mob with mobs:protector item, -this will protect the mob from harm inside of a protected area from other -players. Will return true when mob right-clicked with mobs:protector item. - - 'self' mob information - 'clicker' player information - - Riding Mobs ----------- @@ -781,8 +754,5 @@ mobs:register_mob("mob_horse:horse", { inv:remove_item("main", "mobs:saddle") end end - - -- used to capture horse with magic lasso - mobs:capture_mob(self, clicker, 0, 0, 80, false, nil) end }) diff --git a/mods/ENTITIES/mcl_mobs/api/api.lua b/mods/ENTITIES/mcl_mobs/api/api.lua new file mode 100644 index 000000000..b6054f281 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/api/api.lua @@ -0,0 +1,686 @@ +-- API for Mobs Redo: MineClone 2 Delux 2.0 DRM Free Early Access Super Extreme Edition + +-- current state of things: How to? + +-- mobs library +mobs = {} + +-- lua locals - can grab from this to easily plop them into the api lua files + +--localize minetest functions +local minetest_settings = minetest.settings +local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius +local minetest_get_modpath = minetest.get_modpath +local minetest_registered_nodes = minetest.registered_nodes +local minetest_get_node = minetest.get_node +local minetest_get_item_group = minetest.get_item_group +local minetest_registered_entities = minetest.registered_entities +local minetest_line_of_sight = minetest.line_of_sight +local minetest_after = minetest.after +local minetest_sound_play = minetest.sound_play +local minetest_add_particlespawner = minetest.add_particlespawner +local minetest_registered_items = minetest.registered_items +local minetest_set_node = minetest.set_node +local minetest_add_item = minetest.add_item +local minetest_get_craft_result = minetest.get_craft_result +local minetest_find_path = minetest.find_path +local minetest_is_protected = minetest.is_protected +local minetest_is_creative_enabled = minetest.is_creative_enabled +local minetest_find_node_near = minetest.find_node_near +local minetest_find_nodes_in_area_under_air = minetest.find_nodes_in_area_under_air +local minetest_raycast = minetest.raycast +local minetest_get_us_time = minetest.get_us_time +local minetest_add_entity = minetest.add_entity +local minetest_get_natural_light = minetest.get_natural_light +local minetest_get_node_or_nil = minetest.get_node_or_nil + +-- localize math functions +local math_pi = math.pi +local math_sin = math.sin +local math_cos = math.cos +local math_abs = math.abs +local math_min = math.min +local math_max = math.max +local math_atan = math.atan +local math_random = math.random +local math_floor = math.floor + +-- localize vector functions +local vector_new = vector.new +local vector_length = vector.length +local vector_direction = vector.direction +local vector_normalize = vector.normalize +local vector_multiply = vector.multiply + +-- mob constants +local MAX_MOB_NAME_LENGTH = 30 +local BREED_TIME = 30 +local BREED_TIME_AGAIN = 300 +local CHILD_GROW_TIME = 60*20 +local DEATH_DELAY = 0.5 +local DEFAULT_FALL_SPEED = -10 +local FLOP_HEIGHT = 5.0 +local FLOP_HOR_SPEED = 1.5 +local GRAVITY = minetest_settings:get("movement_gravity")-- + 9.81 + + +local MOB_CAP = {} +MOB_CAP.hostile = 70 +MOB_CAP.passive = 10 +MOB_CAP.ambient = 15 +MOB_CAP.water = 15 + +-- Load main settings +local damage_enabled = minetest_settings:get_bool("enable_damage") +local disable_blood = minetest_settings:get_bool("mobs_disable_blood") +local mobs_drop_items = minetest_settings:get_bool("mobs_drop_items") ~= false +local mobs_griefing = minetest_settings:get_bool("mobs_griefing") ~= false +local spawn_protected = minetest_settings:get_bool("mobs_spawn_protected") ~= false +local remove_far = true +local difficulty = tonumber(minetest_settings:get("mob_difficulty")) or 1.0 +local show_health = false +local max_per_block = tonumber(minetest_settings:get("max_objects_per_block") or 64) +local mobs_spawn_chance = tonumber(minetest_settings:get("mobs_spawn_chance") or 2.5) + +-- pathfinding settings +local enable_pathfinding = true +local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching +local stuck_path_timeout = 10 -- how long will mob follow path before giving up + +-- default nodes +local node_ice = "mcl_core:ice" +local node_snowblock = "mcl_core:snowblock" +local node_snow = "mcl_core:snow" +mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt" + +local mod_weather = minetest_get_modpath("mcl_weather") ~= nil +local mod_explosions = minetest_get_modpath("mcl_explosions") ~= nil +local mod_mobspawners = minetest_get_modpath("mcl_mobspawners") ~= nil +local mod_hunger = minetest_get_modpath("mcl_hunger") ~= nil +local mod_worlds = minetest_get_modpath("mcl_worlds") ~= nil +local mod_armor = minetest_get_modpath("mcl_armor") ~= nil +local mod_experience = minetest_get_modpath("mcl_experience") ~= nil + + +-- random locals I found +local los_switcher = false +local height_switcher = false + +-- Get translator +local S = minetest.get_translator("mcl_mobs") + +-- CMI support check +local use_cmi = minetest.global_exists("cmi") + + +-- Invisibility mod check +mobs.invis = {} +if minetest.global_exists("invisibility") then + mobs.invis = invisibility +end + + +-- creative check +function mobs.is_creative(name) + return minetest.is_creative_enabled(name) +end + + +local atan = function(x) + if not x or x ~= x then + return 0 + else + return math_atan(x) + end +end + + + + +-- Shows helpful debug info above each mob +local mobs_debug = minetest_settings:get_bool("mobs_debug", false) + +-- Peaceful mode message so players will know there are no monsters +if minetest_settings:get_bool("only_peaceful_mobs", false) then + minetest.register_on_joinplayer(function(player) + minetest.chat_send_player(player:get_player_name(), + S("Peaceful mode active! No monsters will spawn.")) + end) +end + + +local api_path = minetest.get_modpath(minetest.get_current_modname()).."/api/mob_functions/" + +--ignite all parts of the api +dofile(api_path .. "ai.lua") +dofile(api_path .. "animation.lua") +dofile(api_path .. "collision.lua") +dofile(api_path .. "environment.lua") +dofile(api_path .. "interaction.lua") +dofile(api_path .. "movement.lua") +dofile(api_path .. "set_up.lua") +dofile(api_path .. "attack_type_instructions.lua") +dofile(api_path .. "sound_handling.lua") + + +mobs.spawning_mobs = {} + + + + +-- register mob entity +function mobs:register_mob(name, def) + + local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25} + + -- Workaround for : + -- Increase upper Y limit to avoid mobs glitching through solid nodes. + -- FIXME: Remove workaround if it's no longer needed. + + if collisionbox[5] < 0.79 then + collisionbox[5] = 0.79 + end + + mobs.spawning_mobs[name] = true + + local can_despawn + if def.can_despawn ~= nil then + can_despawn = def.can_despawn + elseif def.spawn_class == "passive" then + can_despawn = false + else + can_despawn = true + end + + local function scale_difficulty(value, default, min, special) + if (not value) or (value == default) or (value == special) then + return default + else + return math_max(min, value * difficulty) + end + end + + minetest.register_entity(name, { + + use_texture_alpha = def.use_texture_alpha, + stepheight = def.stepheight or 0.6, + name = name, + type = def.type, + attack_type = def.attack_type, + fly = def.fly, + fly_in = def.fly_in or {"air", "__airlike"}, + owner = def.owner or "", + order = def.order or "", + on_die = def.on_die, + spawn_small_alternative = def.spawn_small_alternative, + do_custom = def.do_custom, + jump_height = def.jump_height or 4, -- was 6 + rotate = def.rotate or 0, -- 0=front, 90=side, 180=back, 270=side2 + lifetimer = def.lifetimer or 57.73, + hp_min = scale_difficulty(def.hp_min, 5, 1), + hp_max = scale_difficulty(def.hp_max, 10, 1), + xp_min = def.xp_min or 0, + xp_max = def.xp_max or 3, + xp_timestamp = 0, + breath_max = def.breath_max or 15, + breathes_in_water = def.breathes_in_water or false, + physical = true, + collisionbox = collisionbox, + collide_with_objects = def.collide_with_objects or false, + selectionbox = def.selectionbox or 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 16, + walk_velocity = def.walk_velocity or 1, + run_velocity = def.run_velocity or 2, + damage = scale_difficulty(def.damage, 0, 0), + light_damage = def.light_damage or 0, + sunlight_damage = def.sunlight_damage or 0, + water_damage = def.water_damage or 0, + lava_damage = def.lava_damage or 8, + fire_damage = def.fire_damage or 1, + suffocation = def.suffocation or true, + fall_damage = def.fall_damage or 1, + fall_speed = def.fall_speed or DEFAULT_FALL_SPEED, -- must be lower than -2 + drops = def.drops or {}, + armor = def.armor or 100, + on_rightclick = mobs.create_mob_on_rightclick(def.on_rightclick), + arrow = def.arrow, + shoot_interval = def.shoot_interval, + sounds = def.sounds or {}, + animation = def.animation, + follow = def.follow, + jump = def.jump ~= false, + walk_chance = def.walk_chance or 50, + attacks_monsters = def.attacks_monsters or false, + group_attack = def.group_attack or false, + passive = def.passive or false, + knock_back = def.knock_back ~= false, + shoot_offset = def.shoot_offset or 0, + floats = def.floats or 1, -- floats in water by default + floats_on_lava = def.floats_on_lava or 0, + replace_rate = def.replace_rate, + replace_what = def.replace_what, + replace_with = def.replace_with, + replace_offset = def.replace_offset or 0, + on_replace = def.on_replace, + timer = 0, + state_timer = 0, + env_damage_timer = 0, + tamed = false, + pause_timer = 0, + horny = false, + hornytimer = 0, + gotten = false, + health = 0, + reach = def.reach or 3, + htimer = 0, + texture_list = def.textures, + child_texture = def.child_texture, + docile_by_day = def.docile_by_day or false, + time_of_day = 0.5, + fear_height = def.fear_height or 0, + runaway = def.runaway, + runaway_timer = 0, + pathfinding = def.pathfinding, + immune_to = def.immune_to or {}, + explosion_radius = def.explosion_radius, -- LEGACY + explosion_damage_radius = def.explosion_damage_radius, -- LEGACY + explosiontimer_reset_radius = def.explosiontimer_reset_radius, + explosion_timer = def.explosion_timer or 3, + allow_fuse_reset = def.allow_fuse_reset ~= false, + stop_to_explode = def.stop_to_explode ~= false, + custom_attack = def.custom_attack, + double_melee_attack = def.double_melee_attack, + dogshoot_switch = def.dogshoot_switch, + dogshoot_count = 0, + dogshoot_count_max = def.dogshoot_count_max or 5, + dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5), + attack_animals = def.attack_animals or false, + specific_attack = def.specific_attack, + runaway_from = def.runaway_from, + owner_loyal = def.owner_loyal, + facing_fence = false, + + + _cmi_is_mob = true, + + pushable = def.pushable or true, + + --j4i stuff + yaw = 0, + automatic_face_movement_dir = def.rotate or 0, -- 0=front, 90=side, 180=back, 270=side2 + automatic_face_movement_max_rotation_per_sec = 360, --degrees + backface_culling = true, + walk_timer = 0, + stand_timer = 0, + current_animation = "", + gravity = GRAVITY, + swim = def.swim, + swim_in = def.swim_in or {mobs_mc.items.water_source, "mcl_core:water_flowing", mobs_mc.items.river_water_source}, + pitch_switch = "static", + jump_only = def.jump_only, + hostile = def.hostile, + neutral = def.neutral, + attacking = nil, + visual_size_origin = def.visual_size or {x = 1, y = 1, z = 1}, + punch_timer_cooloff = def.punch_timer_cooloff or 0.5, + projectile_cooldown = def.projectile_cooldown or 2, + --end j4i stuff + + -- MCL2 extensions + teleport = teleport, + do_teleport = def.do_teleport, + spawn_class = def.spawn_class, + ignores_nametag = def.ignores_nametag or false, + rain_damage = def.rain_damage or 0, + glow = def.glow, + can_despawn = can_despawn, + child = def.child or false, + texture_mods = {}, + shoot_arrow = def.shoot_arrow, + sounds_child = def.sounds_child, + explosion_strength = def.explosion_strength, + suffocation_timer = 0, + follow_velocity = def.follow_velocity or 2.4, + instant_death = def.instant_death or false, + fire_resistant = def.fire_resistant or false, + fire_damage_resistant = def.fire_damage_resistant or false, + ignited_by_sunlight = def.ignited_by_sunlight or false, + eye_height = def.eye_height or 1.5, + defuse_reach = def.defuse_reach or 4, + hostile_cooldown = def.hostile_cooldown or 15, + tilt_fly = def.tilt_fly, + tilt_swim = def.tilt_swim, + -- End of MCL2 extensions + + on_spawn = def.on_spawn, + + --on_blast = def.on_blast or do_tnt, + + on_step = mobs.mob_step, + + --do_punch = def.do_punch, + + on_punch = mobs.mob_punch, + + --on_breed = def.on_breed, + + --on_grown = def.on_grown, + + --on_detach_child = mob_detach_child, + + on_activate = function(self, staticdata, dtime) + self.object:set_acceleration(vector_new(0,-GRAVITY, 0)) + return mobs.mob_activate(self, staticdata, def, dtime) + end, + + get_staticdata = function(self) + return mobs.mob_staticdata(self) + end, + + --harmed_by_heal = def.harmed_by_heal, + }) + + if minetest_get_modpath("doc_identifier") ~= nil then + doc.sub.identifier.register_object(name, "basics", "mobs") + end + +end -- END mobs:register_mob function + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +-- register arrow for shoot attack +function mobs:register_arrow(name, def) + + -- errorcheck + if not name or not def then + print("failed to register arrow entity") + return + end + + minetest.register_entity(name.."_entity", { + + physical = false, + visual = def.visual, + visual_size = def.visual_size, + textures = def.textures, + velocity = def.velocity, + hit_player = def.hit_player, + hit_node = def.hit_node, + hit_mob = def.hit_mob, + hit_object = def.hit_object, + drop = def.drop or false, -- drops arrow as registered item when true + collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows + timer = 0, + switch = 0, + owner_id = def.owner_id, + rotate = def.rotate, + speed = def.speed or nil, + on_step = function(self) + + local vel = self.object:get_velocity() + + local pos = self.object:get_pos() + + if self.timer > 150 + or not mobs.within_limits(pos, 0) then + mcl_burning.extinguish(self.object) + print("removing 1") + self.object:remove(); + + return + end + + -- does arrow have a tail (fireball) + if def.tail + and def.tail == 1 + and def.tail_texture then + + minetest.add_particle({ + pos = pos, + velocity = {x = 0, y = 0, z = 0}, + acceleration = {x = 0, y = 0, z = 0}, + expirationtime = def.expire or 0.25, + collisiondetection = false, + texture = def.tail_texture, + size = def.tail_size or 5, + glow = def.glow or 0, + }) + end + + if self.hit_node then + + local node = minetest_get_node(pos).name + + if minetest_registered_nodes[node].walkable then + + self.hit_node(self, pos, node) + + if self.drop == true then + + pos.y = pos.y + 1 + + self.lastpos = (self.lastpos or pos) + + minetest_add_item(self.lastpos, self.object:get_luaentity().name) + end + + self.object:remove(); + + return + end + end + + if self.hit_player or self.hit_mob or self.hit_object then + + for _,player in pairs(minetest_get_objects_inside_radius(pos, 1.5)) do + + if self.hit_player + and player:is_player() then + + mobs.arrow_hit(self, player) + + print("wow everything is fucked") + self.object:remove(); + return + end + + --[[ + local entity = player:get_luaentity() + + if entity + and self.hit_mob + and entity._cmi_is_mob == true + and tostring(player) ~= self.owner_id + and entity.name ~= self.object:get_luaentity().name + and (self._shooter and entity.name ~= self._shooter:get_luaentity().name) then + + --self.hit_mob(self, player) + self.object:remove(); + return + end + ]]-- + + --[[ + if entity + and self.hit_object + and (not entity._cmi_is_mob) + and tostring(player) ~= self.owner_id + and entity.name ~= self.object:get_luaentity().name + and (self._shooter and entity.name ~= self._shooter:get_luaentity().name) then + + --self.hit_object(self, player) + self.object:remove(); + return + end + ]]-- + end + end + + self.lastpos = pos + end + }) +end + +-- Register spawn eggs + +-- Note: This also introduces the “spawn_egg” group: +-- * spawn_egg=1: Spawn egg (generic mob, no metadata) +-- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata) +function mobs:register_egg(mob, desc, background, addegg, no_creative) + + local grp = {spawn_egg = 1} + + -- do NOT add this egg to creative inventory (e.g. dungeon master) + if no_creative == true then + grp.not_in_creative_inventory = 1 + end + + local invimg = background + + if addegg == 1 then + invimg = "mobs_chicken_egg.png^(" .. invimg .. + "^[mask:mobs_chicken_egg_overlay.png)" + end + + -- register old stackable mob egg + minetest.register_craftitem(mob, { + + description = desc, + inventory_image = invimg, + groups = grp, + + _doc_items_longdesc = S("This allows you to place a single mob."), + _doc_items_usagehelp = S("Just place it where you want the mob to appear. Animals will spawn tamed, unless you hold down the sneak key while placing. If you place this on a mob spawner, you change the mob it spawns."), + + on_place = function(itemstack, placer, pointed_thing) + + local pos = pointed_thing.above + + -- am I clicking on something with existing on_rightclick function? + local under = minetest_get_node(pointed_thing.under) + local def = minetest_registered_nodes[under.name] + if def and def.on_rightclick then + return def.on_rightclick(pointed_thing.under, under, placer, itemstack) + end + + if pos + --and within_limits(pos, 0) + and not minetest_is_protected(pos, placer:get_player_name()) then + + local name = placer:get_player_name() + local privs = minetest.get_player_privs(name) + if mod_mobspawners and under.name == "mcl_mobspawners:spawner" then + if minetest_is_protected(pointed_thing.under, name) then + minetest.record_protection_violation(pointed_thing.under, name) + return itemstack + end + if not privs.maphack then + minetest.chat_send_player(name, S("You need the “maphack” privilege to change the mob spawner.")) + return itemstack + end + mcl_mobspawners.setup_spawner(pointed_thing.under, itemstack:get_name()) + if not mobs.is_creative(name) then + itemstack:take_item() + end + return itemstack + end + + if not minetest_registered_entities[mob] then + return itemstack + end + + if minetest_settings:get_bool("only_peaceful_mobs", false) + and minetest_registered_entities[mob].type == "monster" then + minetest.chat_send_player(name, S("Only peaceful mobs allowed!")) + return itemstack + end + + pos.y = pos.y - 0.5 + + local mob = minetest_add_entity(pos, mob) + minetest.log("action", "Mob spawned: "..name.." at "..minetest.pos_to_string(pos)) + local ent = mob:get_luaentity() + + -- don't set owner if monster or sneak pressed + --[[ + if ent.type ~= "monster" + and not placer:get_player_control().sneak then + ent.owner = placer:get_player_name() + ent.tamed = true + end + ]]-- + + -- set nametag + local nametag = itemstack:get_meta():get_string("name") + if nametag ~= "" then + if string.len(nametag) > MAX_MOB_NAME_LENGTH then + nametag = string.sub(nametag, 1, MAX_MOB_NAME_LENGTH) + end + ent.nametag = nametag + update_tag(ent) + end + + -- if not in creative then take item + if not mobs.is_creative(placer:get_player_name()) then + itemstack:take_item() + end + end + + return itemstack + end, + }) + +end + + diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/ai.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/ai.lua new file mode 100644 index 000000000..11c61f20f --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/ai.lua @@ -0,0 +1,682 @@ +local math_random = math.random +local math_pi = math.pi + +local vector_multiply = vector.multiply +local vector_add = vector.add +local vector_new = vector.new + +local minetest_yaw_to_dir = minetest.yaw_to_dir +local minetest_get_item_group = minetest.get_item_group +local minetest_get_node = minetest.get_node +local minetest_line_of_sight = minetest.line_of_sight + +local DOUBLE_PI = math.pi * 2 +local THIRTY_SECONDTH_PI = DOUBLE_PI * 0.03125 + + +--a simple helper function which is too small to move into movement.lua +local quick_rotate = function(self,dtime) + self.yaw = self.yaw + THIRTY_SECONDTH_PI + if self.yaw > DOUBLE_PI then + self.yaw = self.yaw - DOUBLE_PI + end +end + + +--[[ + _ _ +| | | | +| | __ _ _ __ __| | +| | / _` | '_ \ / _` | +| |___| (_| | | | | (_| | +\_____/\__,_|_| |_|\__,_| +]]-- + +--this is basically reverse jump_check +local cliff_check = function(self,dtime) + --mobs will flip out if they are falling without this + if self.object:get_velocity().y ~= 0 then + return false + end + + local pos = self.object:get_pos() + local dir = minetest_yaw_to_dir(self.yaw) + local collisionbox = self.object:get_properties().collisionbox + local radius = collisionbox[4] + 0.5 + + dir = vector_multiply(dir,radius) + + local free_fall, blocker = minetest_line_of_sight( + {x = pos.x + dir.x, y = pos.y, z = pos.z + dir.z}, + {x = pos.x + dir.x, y = pos.y - self.fear_height, z = pos.z + dir.z}) + + return free_fall +end + + +-- state switching logic (stand, walk, run, attacks) +local land_state_list_wandering = {"stand", "walk"} + +local land_state_switch = function(self, dtime) + + if self.hostile and self.attacking then + self.state = "attack" + return + end + + --do math after sure not attacking + self.state_timer = self.state_timer - dtime + + if self.state_timer <= 0 then + self.state_timer = math.random(4,10) + math.random() + self.state = land_state_list_wandering[math.random(1,#land_state_list_wandering)] + end + +end + +-- states are executed here +local land_state_execution = function(self,dtime) + + local pos = self.object:get_pos() + local collisionbox = self.object:get_properties().collisionbox + --get the center of the mob + pos.y = pos.y + (collisionbox[2] + collisionbox[5] / 2) + local current_node = minetest_get_node(pos).name + local float_now = false + + --recheck if in water or lava + if minetest_get_item_group(current_node, "water") ~= 0 or minetest_get_item_group(current_node, "lava") ~= 0 then + float_now = true + end + + if self.state == "stand" then + + --do animation + mobs.set_mob_animation(self, "stand") + + --set the velocity of the mob + mobs.set_velocity(self,0) + + --animation fixes for explosive mobs + if self.attack_type == "explode" then + mobs.reverse_explosion_animation(self,dtime) + end + + elseif self.state == "walk" then + + self.walk_timer = self.walk_timer - dtime + + --reset the walk timer + if self.walk_timer <= 0 then + + --re-randomize the walk timer + self.walk_timer = math.random(1,6) + math.random() + + --set the mob into a random direction + self.yaw = (math_random() * (math.pi * 2)) + end + + --do animation + mobs.set_mob_animation(self, "walk") + + --enable rotation locking + mobs.movement_rotation_lock(self) + + --check for nodes to jump over + local node_in_front_of = mobs.jump_check(self) + + if node_in_front_of == 1 then + + mobs.jump(self) + + --turn if on the edge of cliff + --(this is written like this because unlike + --jump_check which simply tells the mob to jump + --this requires a mob to turn, removing the + --ease of a full implementation for it in a single + --function) + elseif node_in_front_of == 2 or (self.fear_height ~= 0 and cliff_check(self,dtime)) then + --turn 45 degrees if so + quick_rotate(self,dtime) + --stop the mob so it doesn't fall off + mobs.set_velocity(self,0) + end + + --only move forward if path is clear + if node_in_front_of == 0 or node_in_front_of == 1 then + --set the velocity of the mob + mobs.set_velocity(self,self.walk_velocity) + end + + --animation fixes for explosive mobs + if self.attack_type == "explode" then + mobs.reverse_explosion_animation(self,dtime) + end + + elseif self.state == "run" then + + print("run") + + elseif self.state == "attack" then + + --execute mob attack type + if self.attack_type == "explode" then + + mobs.explode_attack_walk(self, dtime) + + elseif self.attack_type == "punch" then + + mobs.punch_attack_walk(self,dtime) + + elseif self.attack_type == "projectile" then + + mobs.projectile_attack_walk(self,dtime) + + end + + end + + if float_now then + mobs.float(self) + end +end + + + + +--[[ + _____ _ +/ ___| (_) +\ `--.__ ___ _ __ ___ + `--. \ \ /\ / / | '_ ` _ \ +/\__/ /\ V V /| | | | | | | +\____/ \_/\_/ |_|_| |_| |_| +]]-- + + + +-- state switching logic (stand, walk, run, attacks) +local swim_state_list_wandering = {"stand", "swim"} + +local swim_state_switch = function(self, dtime) + self.state_timer = self.state_timer - dtime + if self.state_timer <= 0 then + self.state_timer = math.random(4,10) + math.random() + self.state = swim_state_list_wandering[math.random(1,#swim_state_list_wandering)] + end +end + + +--check if a mob needs to turn while swimming +local swim_turn_check = function(self,dtime) + + local pos = self.object:get_pos() + pos.y = pos.y + 0.1 + local dir = minetest_yaw_to_dir(self.yaw) + + local collisionbox = self.object:get_properties().collisionbox + local radius = collisionbox[4] + 0.5 + + vector_multiply(dir, radius) + + local test_dir = vector.add(pos,dir) + + local green_flag_1 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") ~= 0 + + return(green_flag_1) +end + +--this is to swap the built in engine acceleration modifier +local swim_physics_swapper = function(self,inside_swim_node) + + --should be swimming, gravity is applied, switch to floating + if inside_swim_node and self.object:get_acceleration().y ~= 0 then + self.object:set_acceleration(vector_new(0,0,0)) + --not be swim, gravity isn't applied, switch to falling + elseif not inside_swim_node and self.object:get_acceleration().y == 0 then + self.pitch = 0 + self.object:set_acceleration(vector_new(0,-self.gravity,0)) + end +end + + +local random_pitch_multiplier = {-1,1} +-- states are executed here +local swim_state_execution = function(self,dtime) + + local pos = self.object:get_pos() + + pos.y = pos.y + self.object:get_properties().collisionbox[5] + local current_node = minetest_get_node(pos).name + local inside_swim_node = false + + --quick scan everything to see if inside swim node + for _,id in pairs(self.swim_in) do + if id == current_node then + inside_swim_node = true + break + end + end + + --turn gravity on or off + swim_physics_swapper(self,inside_swim_node) + + --swim properly if inside swim node + if inside_swim_node then + + if self.state == "stand" then + + --do animation + mobs.set_mob_animation(self, "stand") + + mobs.set_swim_velocity(self,0) + + if self.tilt_swim then + mobs.set_static_pitch(self) + end + + elseif self.state == "swim" then + + self.walk_timer = self.walk_timer - dtime + + --reset the walk timer + if self.walk_timer <= 0 then + + --re-randomize the walk timer + self.walk_timer = math.random(1,6) + math.random() + + --set the mob into a random direction + self.yaw = (math_random() * (math.pi * 2)) + + --create a truly random pitch, since there is no easy access to pitch math that I can find + self.pitch = math_random() * random_pitch_multiplier[math_random(1,2)] + end + + --do animation + mobs.set_mob_animation(self, "walk") + + --do a quick turn to make mob continuously move + --if in a fish tank or something + if swim_turn_check(self,dtime) then + quick_rotate(self,dtime) + end + + mobs.set_swim_velocity(self,self.walk_velocity) + + --only enable tilt swimming if enabled + if self.tilt_swim then + mobs.set_dynamic_pitch(self) + end + end + --flop around if not inside swim node + else + --do animation + mobs.set_mob_animation(self, "stand") + + mobs.flop(self) + + if self.tilt_swim then + mobs.set_static_pitch(self) + end + end + +end + + +--[[ +______ _ +| ___| | +| |_ | |_ _ +| _| | | | | | +| | | | |_| | +\_| |_|\__, | + __/ | + |___/ +]]-- + +-- state switching logic (stand, walk, run, attacks) +local fly_state_list_wandering = {"stand", "fly"} + +local fly_state_switch = function(self, dtime) + + if self.hostile and self.attacking then + self.state = "attack" + return + end + + self.state_timer = self.state_timer - dtime + if self.state_timer <= 0 then + self.state_timer = math.random(4,10) + math.random() + self.state = fly_state_list_wandering[math.random(1,#fly_state_list_wandering)] + end +end + + +--check if a mob needs to turn while flying +local fly_turn_check = function(self,dtime) + + local pos = self.object:get_pos() + pos.y = pos.y + 0.1 + local dir = minetest_yaw_to_dir(self.yaw) + + local collisionbox = self.object:get_properties().collisionbox + local radius = collisionbox[4] + 0.5 + + vector_multiply(dir, radius) + + local test_dir = vector.add(pos,dir) + + local green_flag_1 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") ~= 0 + + return(green_flag_1) +end + +--this is to swap the built in engine acceleration modifier +local fly_physics_swapper = function(self,inside_fly_node) + + --should be flyming, gravity is applied, switch to floating + if inside_fly_node and self.object:get_acceleration().y ~= 0 then + self.object:set_acceleration(vector_new(0,0,0)) + --not be fly, gravity isn't applied, switch to falling + elseif not inside_fly_node and self.object:get_acceleration().y == 0 then + self.pitch = 0 + self.object:set_acceleration(vector_new(0,-self.gravity,0)) + end +end + + +local random_pitch_multiplier = {-1,1} +-- states are executed here +local fly_state_execution = function(self,dtime) + local pos = self.object:get_pos() + local current_node = minetest_get_node(pos).name + local inside_fly_node = minetest_get_item_group(current_node, "solid") == 0 + + local float_now = false + --recheck if in water or lava + if minetest_get_item_group(current_node, "water") ~= 0 or minetest_get_item_group(current_node, "lava") ~= 0 then + inside_fly_node = false + float_now = true + end + + --turn gravity on or off + fly_physics_swapper(self,inside_fly_node) + + --fly properly if inside fly node + if inside_fly_node then + if self.state == "stand" then + + --do animation + mobs.set_mob_animation(self, "stand") + + mobs.set_fly_velocity(self,0) + + if self.tilt_fly then + mobs.set_static_pitch(self) + end + + elseif self.state == "fly" then + + self.walk_timer = self.walk_timer - dtime + + --reset the walk timer + if self.walk_timer <= 0 then + + --re-randomize the walk timer + self.walk_timer = math.random(1,6) + math.random() + + --set the mob into a random direction + self.yaw = (math_random() * (math.pi * 2)) + + --create a truly random pitch, since there is no easy access to pitch math that I can find + self.pitch = math_random() * random_pitch_multiplier[math_random(1,2)] + end + + --do animation + mobs.set_mob_animation(self, "walk") + + --do a quick turn to make mob continuously move + --if in a bird cage or something + if fly_turn_check(self,dtime) then + quick_rotate(self,dtime) + end + + if self.tilt_fly then + mobs.set_dynamic_pitch(self) + end + + mobs.set_fly_velocity(self,self.walk_velocity) + elseif self.state == "attack" then + + --execute mob attack type + --if self.attack_type == "explode" then + + --mobs.explode_attack_fly(self, dtime) + + --elseif self.attack_type == "punch" then + + --mobs.punch_attack_fly(self,dtime) + + if self.attack_type == "projectile" then + + mobs.projectile_attack_fly(self,dtime) + + end + end + else + --make the mob float + if self.floats and float_now then + mobs.set_velocity(self, 0) + + mobs.float(self) + + if self.tilt_fly then + mobs.set_static_pitch(self) + end + end + end +end + + +--[[ + ___ + |_ | + | |_ _ _ __ ___ _ __ + | | | | | '_ ` _ \| '_ \ +/\__/ / |_| | | | | | | |_) | +\____/ \__,_|_| |_| |_| .__/ + | | + |_| +]]-- + + +--check if a mob needs to turn while jumping +local jump_turn_check = function(self,dtime) + + local pos = self.object:get_pos() + pos.y = pos.y + 0.1 + local dir = minetest_yaw_to_dir(self.yaw) + + local collisionbox = self.object:get_properties().collisionbox + local radius = collisionbox[4] + 0.5 + + vector_multiply(dir, radius) + + local test_dir = vector.add(pos,dir) + + local green_flag_1 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") ~= 0 + + return(green_flag_1) +end + +-- state switching logic (stand, jump, run, attacks) +local jump_state_list_wandering = {"stand", "jump"} + +local jump_state_switch = function(self, dtime) + self.state_timer = self.state_timer - dtime + if self.state_timer <= 0 then + self.state_timer = math.random(4,10) + math.random() + self.state = jump_state_list_wandering[math.random(1,#jump_state_list_wandering)] + end +end + +-- states are executed here +local jump_state_execution = function(self,dtime) + + local pos = self.object:get_pos() + local collisionbox = self.object:get_properties().collisionbox + --get the center of the mob + pos.y = pos.y + (collisionbox[2] + collisionbox[5] / 2) + local current_node = minetest_get_node(pos).name + + local float_now = false + + --recheck if in water or lava + if minetest_get_item_group(current_node, "water") ~= 0 or minetest_get_item_group(current_node, "lava") ~= 0 then + float_now = true + end + + if self.state == "stand" then + + --do animation + mobs.set_mob_animation(self, "stand") + + --set the velocity of the mob + mobs.set_velocity(self,0) + + elseif self.state == "jump" then + + self.walk_timer = self.walk_timer - dtime + + --reset the jump timer + if self.walk_timer <= 0 then + + --re-randomize the jump timer + self.walk_timer = math.random(1,6) + math.random() + + --set the mob into a random direction + self.yaw = (math_random() * (math.pi * 2)) + end + + --do animation + mobs.set_mob_animation(self, "walk") + + --enable rotation locking + mobs.movement_rotation_lock(self) + + --jumping mobs are more loosey goosey + if node_in_front_of == 1 then + quick_rotate(self,dtime) + end + + --only move forward if path is clear + mobs.jump_move(self,self.walk_velocity) + + elseif self.state == "run" then + + print("run") + + elseif self.state == "attack" then + + print("attack") + + end + + if float_now then + mobs.float(self) + end +end + + + + +--[[ +___ ___ _ _ _ +| \/ | (_) | | (_) +| . . | __ _ _ _ __ | | ___ __ _ _ ___ +| |\/| |/ _` | | '_ \ | | / _ \ / _` | |/ __| +| | | | (_| | | | | | | |___| (_) | (_| | | (__ +\_| |_/\__,_|_|_| |_| \_____/\___/ \__, |_|\___| + __/ | + |___/ +]]-- + +--the main loop +mobs.mob_step = function(self, dtime) + + --do not continue if non-existent + if not self or not self.object or not self.object:get_luaentity() then + self.object:remove() + return false + end + + local attacking = nil + + if self.hostile then + --true for line_of_sight is debug + attacking = mobs.detect_closest_player_within_radius(self,true,self.view_range,self.eye_height) + + --go get the closest player ROAR >:O + if attacking then + + --set initial punch timer + if self.attacking == nil then + if self.attack_type == "punch" then + self.punch_timer = -1 + end + end + + self.attacking = attacking + --no player in area + else + + --reset states when coming out of hostile state + if self.attacking ~= nil then + self.state_timer = -1 + end + + self.attacking = nil + end + end + + --count down hostile cooldown timer when no players in range + if self.neutral and self.hostile and not attacking and self.hostile_cooldown_timer then + + self.hostile_cooldown_timer = self.hostile_cooldown_timer - dtime + + if self.hostile_cooldown_timer <= 0 then + self.hostile = false + self.hostile_cooldown_timer = 0 + end + end + + + --jump only (like slimes) + if self.jump_only then + jump_state_switch(self, dtime) + jump_state_execution(self, dtime) + --swimming + elseif self.swim then + swim_state_switch(self, dtime) + swim_state_execution(self, dtime) + --flying + elseif self.fly then + fly_state_switch(self, dtime) + fly_state_execution(self,dtime) + --regular mobs that walk around + else + land_state_switch(self, dtime) + land_state_execution(self,dtime) + end + + + + + -- can mob be pushed, if so calculate direction -- do this last (overrides everything) + if self.pushable then + mobs.collision(self) + end + + self.old_velocity = self.object:get_velocity() + self.old_pos = self.object:get_pos() +end diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/animation.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/animation.lua new file mode 100644 index 000000000..b7b8fa574 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/animation.lua @@ -0,0 +1,203 @@ +local math_pi = math.pi +local math_floor = math.floor +local math_random = math.random +local HALF_PI = math_pi/2 + + +local vector_direction = vector.direction +local vector_distance = vector.distance +local vector_new = vector.new + +local minetest_dir_to_yaw = minetest.dir_to_yaw + +-- set defined animation +mobs.set_mob_animation = function(self, anim, fixed_frame) + + if not self.animation or not anim then + return + end + + if self.state == "die" and anim ~= "die" and anim ~= "stand" then + return + end + + + if (not self.animation[anim .. "_start"] or not self.animation[anim .. "_end"]) then + return + end + + --animations break if they are constantly set + --so we put this return gate to check if it is + --already at the animation we are trying to implement + if self.current_animation == anim then + return + end + + local a_start = self.animation[anim .. "_start"] + local a_end + + if fixed_frame then + a_end = a_start + else + a_end = self.animation[anim .. "_end"] + end + + self.object:set_animation({ + x = a_start, + y = a_end}, + self.animation[anim .. "_speed"] or self.animation.speed_normal or 15, + 0, self.animation[anim .. "_loop"] ~= false) + + + self.current_animation = anim +end + + + + +mobs.death_effect = function(pos, yaw, collisionbox, rotate) + local min, max + if collisionbox then + min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]} + max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]} + else + min = { x = -0.5, y = 0, z = -0.5 } + max = { x = 0.5, y = 0.5, z = 0.5 } + end + if rotate then + min = vector.rotate(min, {x=0, y=yaw, z=math_pi/2}) + max = vector.rotate(max, {x=0, y=yaw, z=math_pi/2}) + min, max = vector.sort(min, max) + min = vector.multiply(min, 0.5) + max = vector.multiply(max, 0.5) + end + + minetest_add_particlespawner({ + amount = 50, + time = 0.001, + minpos = vector.add(pos, min), + maxpos = vector.add(pos, max), + minvel = vector_new(-5,-5,-5), + maxvel = vector_new(5,5,5), + minexptime = 1.1, + maxexptime = 1.5, + minsize = 1, + maxsize = 2, + collisiondetection = false, + vertical = false, + texture = "mcl_particles_mob_death.png^[colorize:#000000:255", + }) + + minetest_sound_play("mcl_mobs_mob_poof", { + pos = pos, + gain = 1.0, + max_hear_distance = 8, + }, true) +end + + +--this allows auto facedir rotation while making it so mobs +--don't look like wet noodles flopping around +mobs.movement_rotation_lock = function(self) + + local current_engine_yaw = self.object:get_yaw() + local current_lua_yaw = self.yaw + + if current_engine_yaw > math.pi * 2 then + current_engine_yaw = current_engine_yaw - (math.pi * 2) + end + + if math.abs(current_engine_yaw - current_lua_yaw) <= 0.05 and self.object:get_properties().automatic_face_movement_dir then + self.object:set_properties{automatic_face_movement_dir = false} + elseif math.abs(current_engine_yaw - current_lua_yaw) > 0.05 and self.object:get_properties().automatic_face_movement_dir == false then + self.object:set_properties{automatic_face_movement_dir = self.rotate} + end +end + + +--this is used when a mob is chasing a player +mobs.set_yaw_while_attacking = function(self) + + if self.object:get_properties().automatic_face_movement_dir then + self.object:set_properties{automatic_face_movement_dir = false} + end + + --turn positions into pseudo 2d vectors + local pos1 = self.object:get_pos() + pos1.y = 0 + + local pos2 = self.attacking:get_pos() + pos2.y = 0 + + local new_direction = vector_direction(pos1,pos2) + local new_yaw = minetest_dir_to_yaw(new_direction) + + self.object:set_yaw(new_yaw) + self.yaw = new_yaw +end + + +local calculate_pitch = function(self) + local pos = self.object:get_pos() + local pos2 = self.old_pos + + if pos == nil or pos2 == nil then + return false + end + + return(minetest_dir_to_yaw(vector_new(vector_distance(vector_new(pos.x,0,pos.z),vector_new(pos2.x,0,pos2.z)),0,pos.y - pos2.y)) + HALF_PI) +end + +--this is a helper function used to make mobs pitch rotation dynamically flow when flying/swimming +mobs.set_dynamic_pitch = function(self) + local pitch = calculate_pitch(self) + + if not pitch then + return + end + + local current_rotation = self.object:get_rotation() + + current_rotation.x = pitch + + self.object:set_rotation(current_rotation) + + self.pitch_switch = "dynamic" +end + +--this is a helper function used to make mobs pitch rotation reset when flying/swimming +mobs.set_static_pitch = function(self) + + if self.pitch_switch == "static" then + return + end + + local current_rotation = self.object:get_rotation() + + current_rotation.x = 0 + current_rotation.z = 0 + + self.object:set_rotation(current_rotation) + self.pitch_switchfdas = "static" +end + +--this is a helper function for mobs explosion animation +mobs.handle_explosion_animation = function(self) + + --secondary catch-all + if not self.explosion_animation then + self.explosion_animation = 0 + end + + --the timer works from 0 for sense of a 0 based counting + --but this just bumps it up so it's usable in here + local explosion_timer_adjust = self.explosion_animation + 1 + + + local visual_size_modified = table.copy(self.visual_size_origin) + + visual_size_modified.x = visual_size_modified.x * (explosion_timer_adjust ^ 3) + visual_size_modified.y = visual_size_modified.y * explosion_timer_adjust + + self.object:set_properties({visual_size = visual_size_modified}) +end \ No newline at end of file diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/attack_type_instructions.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/attack_type_instructions.lua new file mode 100644 index 000000000..e60c46aab --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/attack_type_instructions.lua @@ -0,0 +1,303 @@ +local vector_direction = vector.direction +local minetest_dir_to_yaw = minetest.dir_to_yaw +local vector_distance = vector.distance +local vector_multiply = vector.multiply + +--[[ + _ _ _ _ +| | | | | | | | +| | | | __ _ _ __ __| | | | +| | | | / _` | '_ \ / _` | | | +|_| | |___| (_| | | | | (_| | |_| +(_) \_____/\__,_|_| |_|\__,_| (_) +]]-- + + + +--[[ + _____ _ _ +| ___| | | | | +| |____ ___ __ | | ___ __| | ___ +| __\ \/ / '_ \| |/ _ \ / _` |/ _ \ +| |___> <| |_) | | (_) | (_| | __/ +\____/_/\_\ .__/|_|\___/ \__,_|\___| + | | + |_| +]]-- + +mobs.explode_attack_walk = function(self,dtime) + + --this needs an exception + if self.attacking == nil or not self.attacking:is_player() then + self.attacking = nil + return + end + + mobs.set_yaw_while_attacking(self) + + local distance_from_attacking = vector_distance(self.object:get_pos(), self.attacking:get_pos()) + + --make mob walk up to player within 2 nodes distance then start exploding + if distance_from_attacking >= self.reach and + --don't allow explosion to cancel unless out of the reach boundary + not (self.explosion_animation ~= nil and self.explosion_animation > 0 and distance_from_attacking <= self.defuse_reach) then + + mobs.set_velocity(self, self.run_velocity) + mobs.set_mob_animation(self,"run") + + mobs.reverse_explosion_animation(self,dtime) + else + mobs.set_velocity(self,0) + + --this is the only way I can reference this without dumping extra data on all mobs + if not self.explosion_animation then + self.explosion_animation = 0 + end + + --play ignite sound + if self.explosion_animation == 0 then + mobs.play_sound(self,"attack") + end + + mobs.set_mob_animation(self,"stand") + + mobs.handle_explosion_animation(self) + + self.explosion_animation = self.explosion_animation + (dtime/2.5) + end + + --make explosive mobs jump + --check for nodes to jump over + --explosive mobs will just ride against walls for now + local node_in_front_of = mobs.jump_check(self) + if node_in_front_of == 1 then + mobs.jump(self) + end + + + --do biggening explosion thing + if self.explosion_animation and self.explosion_animation > self.explosion_timer then + mcl_explosions.explode(self.object:get_pos(), self.explosion_strength,{ drop_chance = 1.0 }) + self.object:remove() + end +end + + +--this is a small helper function to make working with explosion animations easier +mobs.reverse_explosion_animation = function(self,dtime) + + --if explosion animation was greater than 0 then reverse it + if self.explosion_animation ~= nil and self.explosion_animation > 0 then + self.explosion_animation = self.explosion_animation - dtime + if self.explosion_animation < 0 then + self.explosion_animation = 0 + end + end + + mobs.handle_explosion_animation(self) +end + + + + +--[[ +______ _ +| ___ \ | | +| |_/ / _ _ __ ___| |__ +| __/ | | | '_ \ / __| '_ \ +| | | |_| | | | | (__| | | | +\_| \__,_|_| |_|\___|_| |_| +]]-- + + + +mobs.punch_attack_walk = function(self,dtime) + + --this needs an exception + if self.attacking == nil or not self.attacking:is_player() then + self.attacking = nil + return + end + + mobs.set_yaw_while_attacking(self) + + mobs.set_velocity(self, self.run_velocity) + + mobs.set_mob_animation(self, "run") + + --make punchy mobs jump + --check for nodes to jump over + --explosive mobs will just ride against walls for now + local node_in_front_of = mobs.jump_check(self) + if node_in_front_of == 1 then + mobs.jump(self) + end + + + if self.punch_timer > 0 then + self.punch_timer = self.punch_timer - dtime + end +end + +mobs.punch_attack = function(self) + + self.attacking:punch(self.object, 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = self.damage} + }, nil) + + self.punch_timer = self.punch_timer_cooloff + + + --knockback + local pos1 = self.object:get_pos() + pos1.y = 0 + local pos2 = self.attacking:get_pos() + pos2.y = 0 + local dir = vector_direction(pos1,pos2) + + dir = vector_multiply(dir,3) + + if self.attacking:get_velocity().y <= 1 then + dir.y = 5 + end + + self.attacking:add_velocity(dir) +end + + + + +--[[ +______ _ _ _ _ +| ___ \ (_) | | (_) | +| |_/ / __ ___ _ ___ ___| |_ _| | ___ +| __/ '__/ _ \| |/ _ \/ __| __| | |/ _ \ +| | | | | (_) | | __/ (__| |_| | | __/ +\_| |_| \___/| |\___|\___|\__|_|_|\___| + _/ | + |__/ +]]-- + + +mobs.projectile_attack_walk = function(self,dtime) + + --this needs an exception + if self.attacking == nil or not self.attacking:is_player() then + self.attacking = nil + return + end + + mobs.set_yaw_while_attacking(self) + + local distance_from_attacking = vector_distance(self.object:get_pos(), self.attacking:get_pos()) + + + if distance_from_attacking >= self.reach then + mobs.set_velocity(self, self.run_velocity) + mobs.set_mob_animation(self,"run") + else + mobs.set_velocity(self,0) + mobs.set_mob_animation(self,"stand") + end + + --do this to not load data into other mobs + if not self.projectile_timer then + self.projectile_timer = self.projectile_cooldown + end + + --run projectile timer + if self.projectile_timer > 0 then + self.projectile_timer = self.projectile_timer - dtime + + --shoot + if self.projectile_timer <= 0 then + --reset timer + self.projectile_timer = self.projectile_cooldown + mobs.shoot_projectile(self) + end + end + + --make shooty mobs jump + --check for nodes to jump over + --explosive mobs will just ride against walls for now + local node_in_front_of = mobs.jump_check(self) + if node_in_front_of == 1 then + mobs.jump(self) + end + +end + + + + + + + + + +--[[ + _ ______ _ _ +| | | ___| | | | +| | | |_ | |_ _ | | +| | | _| | | | | | | | +|_| | | | | |_| | |_| +(_) \_| |_|\__, | (_) + __/ | + |___/ +]]-- + + + + +--[[ +______ _ _ _ _ +| ___ \ (_) | | (_) | +| |_/ / __ ___ _ ___ ___| |_ _| | ___ +| __/ '__/ _ \| |/ _ \/ __| __| | |/ _ \ +| | | | | (_) | | __/ (__| |_| | | __/ +\_| |_| \___/| |\___|\___|\__|_|_|\___| + _/ | + |__/ +]]-- + +mobs.projectile_attack_fly = function(self, dtime) + + --this needs an exception + if self.attacking == nil or not self.attacking:is_player() then + self.attacking = nil + return + end + + local distance_from_attacking = vector_distance(self.object:get_pos(), self.attacking:get_pos()) + + if distance_from_attacking >= self.reach then + mobs.set_yaw_while_attacking(self) + mobs.set_pitch_while_attacking(self) + mobs.set_fly_velocity(self, self.run_velocity) + mobs.set_mob_animation(self,"run") + else + mobs.set_yaw_while_attacking(self) + mobs.set_pitch_while_attacking(self) + mobs.set_fly_velocity(self, 0) + mobs.set_mob_animation(self,"stand") + end + + + --do this to not load data into other mobs + if not self.projectile_timer then + self.projectile_timer = self.projectile_cooldown + end + + --run projectile timer + if self.projectile_timer > 0 then + self.projectile_timer = self.projectile_timer - dtime + + --shoot + if self.projectile_timer <= 0 then + --reset timer + self.projectile_timer = self.projectile_cooldown + mobs.shoot_projectile(self) + end + end +end \ No newline at end of file diff --git a/mods/ENTITIES/mcl_mobs/api.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/backup_code_api.lua similarity index 58% rename from mods/ENTITIES/mcl_mobs/api.lua rename to mods/ENTITIES/mcl_mobs/api/mob_functions/backup_code_api.lua index 8aed37288..802201688 100644 --- a/mods/ENTITIES/mcl_mobs/api.lua +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/backup_code_api.lua @@ -1,118 +1,3 @@ - --- API for Mobs Redo: MineClone 2 Edition (MRM) - -mobs = {} -mobs.mod = "mrm" -mobs.version = "20210106" -- don't rely too much on this, rarely updated, if ever - -local MAX_MOB_NAME_LENGTH = 30 -local HORNY_TIME = 30 -local HORNY_AGAIN_TIME = 300 -local CHILD_GROW_TIME = 60*20 -local DEATH_DELAY = 0.5 -local DEFAULT_FALL_SPEED = -10 -local FLOP_HEIGHT = 5.0 -local FLOP_HOR_SPEED = 1.5 - -local MOB_CAP = {} -MOB_CAP.hostile = 70 -MOB_CAP.passive = 10 -MOB_CAP.ambient = 15 -MOB_CAP.water = 15 - --- Localize -local S = minetest.get_translator("mcl_mobs") - --- CMI support check -local use_cmi = minetest.global_exists("cmi") - - --- Invisibility mod check -mobs.invis = {} -if minetest.global_exists("invisibility") then - mobs.invis = invisibility -end - - --- creative check -function mobs.is_creative(name) - return minetest.is_creative_enabled(name) -end - - --- localize math functions -local pi = math.pi -local sin = math.sin -local cos = math.cos -local abs = math.abs -local min = math.min -local max = math.max -local atann = math.atan -local random = math.random -local floor = math.floor -local atan = function(x) - if not x or x ~= x then - return 0 - else - return atann(x) - end -end - - --- Load settings -local damage_enabled = minetest.settings:get_bool("enable_damage") -local disable_blood = minetest.settings:get_bool("mobs_disable_blood") -local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false -local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false -local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false -local remove_far = true -local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0 -local show_health = false -local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 64) -local mobs_spawn_chance = tonumber(minetest.settings:get("mobs_spawn_chance") or 2.5) - --- Shows helpful debug info above each mob -local mobs_debug = minetest.settings:get_bool("mobs_debug", false) - --- Peaceful mode message so players will know there are no monsters -if minetest.settings:get_bool("only_peaceful_mobs", false) then - minetest.register_on_joinplayer(function(player) - minetest.chat_send_player(player:get_player_name(), - S("Peaceful mode active! No monsters will spawn.")) - end) -end - --- pathfinding settings -local enable_pathfinding = true -local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching -local stuck_path_timeout = 10 -- how long will mob follow path before giving up - --- default nodes -local node_ice = "mcl_core:ice" -local node_snowblock = "mcl_core:snowblock" -local node_snow = "mcl_core:snow" -mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt" - -local mod_weather = minetest.get_modpath("mcl_weather") ~= nil -local mod_explosions = minetest.get_modpath("mcl_explosions") ~= nil -local mod_mobspawners = minetest.get_modpath("mcl_mobspawners") ~= nil -local mod_hunger = minetest.get_modpath("mcl_hunger") ~= nil -local mod_worlds = minetest.get_modpath("mcl_worlds") ~= nil -local mod_armor = minetest.get_modpath("mcl_armor") ~= nil -local mod_experience = minetest.get_modpath("mcl_experience") ~= nil - -----For Water Flowing: -local enable_physics = function(object, luaentity, ignore_check) - if luaentity.physical_state == false or ignore_check == true then - luaentity.physical_state = true - object:set_properties({ - physical = true - }) - object:set_velocity({x=0,y=0,z=0}) - object:set_acceleration({x=0,y=-9.81,z=0}) - end -end - local disable_physics = function(object, luaentity, ignore_check, reset_movement) if luaentity.physical_state == true or ignore_check == true then luaentity.physical_state = false @@ -126,1928 +11,313 @@ local disable_physics = function(object, luaentity, ignore_check, reset_movement end end - --- play sound -local mob_sound = function(self, soundname, is_opinion, fixed_pitch) - - local soundinfo - if self.sounds_child and self.child then - soundinfo = self.sounds_child - elseif self.sounds then - soundinfo = self.sounds - end - if not soundinfo then - return - end - local sound = soundinfo[soundname] - if sound then - if is_opinion and self.opinion_sound_cooloff > 0 then - return - end - local pitch - if not fixed_pitch then - local base_pitch = soundinfo.base_pitch - if not base_pitch then - base_pitch = 1 - end - if self.child and (not self.sounds_child) then - -- Children have higher pitch - pitch = base_pitch * 1.5 - else - pitch = base_pitch - end - -- randomize the pitch a bit - pitch = pitch + math.random(-10, 10) * 0.005 - end - minetest.sound_play(sound, { - object = self.object, - gain = 1.0, - max_hear_distance = self.sounds.distance, - pitch = pitch, - }, true) - self.opinion_sound_cooloff = 1 +----For Water Flowing: +local enable_physics = function(object, luaentity, ignore_check) + if luaentity.physical_state == false or ignore_check == true then + luaentity.physical_state = true + object:set_properties({ + physical = true + }) + object:set_velocity({x=0,y=0,z=0}) + object:set_acceleration({x=0,y=-9.81,z=0}) end end --- Return true if object is in view_range -local function object_in_range(self, object) - if not object then - return false - end - local factor - -- Apply view range reduction for special player armor - if object:is_player() and mod_armor then - factor = armor:get_mob_view_range_factor(object, self.name) - end - -- Distance check - local dist - if factor and factor == 0 then - return false - elseif factor then - dist = self.view_range * factor - else - dist = self.view_range - end - - local p1, p2 = self.object:get_pos(), object:get_pos() - return p1 and p2 and (vector.distance(p1, p2) <= dist) -end - --- attack player/mob -local do_attack = function(self, player) - - if self.state == "attack" or self.state == "die" then - return - end - - self.attack = player - self.state = "attack" - - -- TODO: Implement war_cry sound without being annoying - --if random(0, 100) < 90 then - --mob_sound(self, "war_cry", true) - --end -end - - --- collision function borrowed amended from jordan4ibanez open_ai mod -local collision = function(self) - - local pos = self.object:get_pos() - local vel = self.object:get_velocity() - local x = 0 - local z = 0 - local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5 - - for _,object in pairs(minetest.get_objects_inside_radius(pos, width)) do - - if object:is_player() - or (object:get_luaentity()._cmi_is_mob == true and object ~= self.object) then - - local pos2 = object:get_pos() - local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z} - local force = (width + 0.5) - vector.distance( - {x = pos.x, y = 0, z = pos.z}, - {x = pos2.x, y = 0, z = pos2.z}) - - x = x + (vec.x * force) - z = z + (vec.z * force) - end - end - - return({x,z}) -end - --- move mob in facing direction -local set_velocity = function(self, v) - - local c_x, c_y = 0, 0 - - -- can mob be pushed, if so calculate direction - if self.pushable then - c_x, c_y = unpack(collision(self)) - end - - -- halt mob if it has been ordered to stay - if self.order == "stand" then - self.object:set_velocity({x = 0, y = 0, z = 0}) - return - end - - local yaw = (self.object:get_yaw() or 0) + self.rotate - - self.object:set_velocity({ - x = (sin(yaw) * -v) + c_x, - y = self.object:get_velocity().y, - z = (cos(yaw) * v) + c_y, - }) -end - - - --- calculate mob velocity -local get_velocity = function(self) - - local v = self.object:get_velocity() - if v then - return (v.x * v.x + v.z * v.z) ^ 0.5 - end - - return 0 -end - -local function update_roll(self) - local is_Fleckenstein = self.nametag == "Fleckenstein" - local was_Fleckenstein = false - - local rot = self.object:get_rotation() - rot.z = is_Fleckenstein and pi or 0 - self.object:set_rotation(rot) - - local cbox = table.copy(self.collisionbox) - local acbox = self.object:get_properties().collisionbox - - if math.abs(cbox[2] - acbox[2]) > 0.1 then - was_Fleckenstein = true - end - - if is_Fleckenstein ~= was_Fleckenstein then - local pos = self.object:get_pos() - pos.y = pos.y + (acbox[2] + acbox[5]) - self.object:set_pos(pos) - end - - if is_Fleckenstein then - cbox[2], cbox[5] = -cbox[5], -cbox[2] - end - - self.object:set_properties({collisionbox = cbox}) -end - --- set and return valid yaw -local set_yaw = function(self, yaw, delay, dtime) - - if not yaw or yaw ~= yaw then - yaw = 0 - end - - delay = delay or 0 - - if delay == 0 then - if self.shaking and dtime then - yaw = yaw + (math.random() * 2 - 1) * 5 * dtime - end - self.object:set_yaw(yaw) - update_roll(self) - return yaw - end - - self.target_yaw = yaw - self.delay = delay - - return self.target_yaw -end - --- global function to set mob yaw -function mobs:yaw(self, yaw, delay, dtime) - set_yaw(self, yaw, delay, dtime) -end - -local add_texture_mod = function(self, mod) - local full_mod = "" - local already_added = false - for i=1, #self.texture_mods do - if mod == self.texture_mods[i] then - already_added = true - end - full_mod = full_mod .. self.texture_mods[i] - end - if not already_added then - full_mod = full_mod .. mod - table.insert(self.texture_mods, mod) - end - self.object:set_texture_mod(full_mod) -end -local remove_texture_mod = function(self, mod) - local full_mod = "" - local remove = {} - for i=1, #self.texture_mods do - if self.texture_mods[i] ~= mod then - full_mod = full_mod .. self.texture_mods[i] - else - table.insert(remove, i) - end - end - for i=#remove, 1 do - table.remove(self.texture_mods, remove[i]) - end - self.object:set_texture_mod(full_mod) -end - --- set defined animation -local set_animation = function(self, anim, fixed_frame) - if not self.animation or not anim then - return - end - if self.state == "die" and anim ~= "die" and anim ~= "stand" then - return - end - - self.animation.current = self.animation.current or "" - - if (anim == self.animation.current - or not self.animation[anim .. "_start"] - or not self.animation[anim .. "_end"]) and self.state ~= "die" then - return - end - - self.animation.current = anim - - local a_start = self.animation[anim .. "_start"] - local a_end - if fixed_frame then - a_end = a_start - else - a_end = self.animation[anim .. "_end"] - end - - self.object:set_animation({ - x = a_start, - y = a_end}, - self.animation[anim .. "_speed"] or self.animation.speed_normal or 15, - 0, self.animation[anim .. "_loop"] ~= false) -end - - --- above function exported for mount.lua -function mobs:set_animation(self, anim) - set_animation(self, anim) -end - --- Returns true is node can deal damage to self -local is_node_dangerous = function(self, nodename) - local nn = nodename - if self.lava_damage > 0 then - if minetest.get_item_group(nn, "lava") ~= 0 then - return true - end - end - if self.fire_damage > 0 then - if minetest.get_item_group(nn, "fire") ~= 0 then - return true - end - end - if minetest.registered_nodes[nn] and minetest.registered_nodes[nn].damage_per_second and minetest.registered_nodes[nn].damage_per_second > 0 then - return true - end - return false -end - - --- Returns true if node is a water hazard -local is_node_waterhazard = function(self, nodename) - local nn = nodename - if self.water_damage > 0 then - if minetest.get_item_group(nn, "water") ~= 0 then - return true - end - end - if minetest.registered_nodes[nn] and minetest.registered_nodes[nn].drowning and minetest.registered_nodes[nn].drowning > 0 then - if self.breath_max ~= -1 then - -- check if the mob is water-breathing _and_ the block is water; only return true if neither is the case - -- this will prevent water-breathing mobs to classify water or e.g. sand below them as dangerous - if not self.breathes_in_water and minetest.get_item_group(nn, "water") ~= 0 then - return true +--[[ +local timer = 0 +minetest.register_globalstep(function(dtime) + timer = timer + dtime + if timer < 1 then return end + for _, player in pairs(minetest.get_connected_players()) do + local pos = player:get_pos() + for _, obj in pairs(minetest_get_objects_inside_radius(pos, 47)) do + local lua = obj:get_luaentity() + if lua and lua._cmi_is_mob then + lua.lifetimer = math.max(20, lua.lifetimer) + lua.despawn_immediately = false end end end - return false -end + timer = 0 +end) +]]-- +-- compatibility function for old entities to new modpack entities +function mobs:alias_mob(old_name, new_name) --- check line of sight (BrunoMine) -local line_of_sight = function(self, pos1, pos2, stepsize) + -- spawn egg + minetest.register_alias(old_name, new_name) - stepsize = stepsize or 1 + -- entity + minetest.register_entity(":" .. old_name, { - local s, pos = minetest.line_of_sight(pos1, pos2, stepsize) + physical = false, - -- normal walking and flying mobs can see you through air - if s == true then - return true - end + on_step = function(self) - -- New pos1 to be analyzed - local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z} - - local r, pos = minetest.line_of_sight(npos1, pos2, stepsize) - - -- Checks the return - if r == true then return true end - - -- Nodename found - local nn = minetest.get_node(pos).name - - -- Target Distance (td) to travel - local td = vector.distance(pos1, pos2) - - -- Actual Distance (ad) traveled - local ad = 0 - - -- It continues to advance in the line of sight in search of a real - -- obstruction which counts as 'normal' nodebox. - while minetest.registered_nodes[nn] - and minetest.registered_nodes[nn].walkable == false do - - -- Check if you can still move forward - if td < ad + stepsize then - return true -- Reached the target - end - - -- Moves the analyzed pos - local d = vector.distance(pos1, pos2) - - npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x - npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y - npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z - - -- NaN checks - if d == 0 - or npos1.x ~= npos1.x - or npos1.y ~= npos1.y - or npos1.z ~= npos1.z then - return false - end - - ad = ad + stepsize - - -- scan again - r, pos = minetest.line_of_sight(npos1, pos2, stepsize) - - if r == true then return true end - - -- New Nodename found - nn = minetest.get_node(pos).name - - end - - return false -end - - --- are we flying in what we are suppose to? (taikedz) -local flight_check = function(self) - - local nod = self.standing_in - local def = minetest.registered_nodes[nod] - - if not def then return false end -- nil check - - local fly_in - if type(self.fly_in) == "string" then - fly_in = { self.fly_in } - elseif type(self.fly_in) == "table" then - fly_in = self.fly_in - else - return false - end - - for _,checknode in pairs(fly_in) do - if nod == checknode then - return true - elseif checknode == "__airlike" and def.walkable == false and - (def.liquidtype == "none" or minetest.get_item_group(nod, "fake_liquid") == 1) then - return true - end - end - - return false -end - - --- custom particle effects -local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow, go_down) - - radius = radius or 2 - min_size = min_size or 0.5 - max_size = max_size or 1 - gravity = gravity or -10 - glow = glow or 0 - go_down = go_down or false - - local ym - if go_down then - ym = 0 - else - ym = -radius - end - - minetest.add_particlespawner({ - amount = amount, - time = 0.25, - minpos = pos, - maxpos = pos, - minvel = {x = -radius, y = ym, z = -radius}, - maxvel = {x = radius, y = radius, z = radius}, - minacc = {x = 0, y = gravity, z = 0}, - maxacc = {x = 0, y = gravity, z = 0}, - minexptime = 0.1, - maxexptime = 1, - minsize = min_size, - maxsize = max_size, - texture = texture, - glow = glow, - }) -end - -local damage_effect = function(self, damage) - -- damage particles - if (not disable_blood) and damage > 0 then - - local amount_large = math.floor(damage / 2) - local amount_small = damage % 2 - - local pos = self.object:get_pos() - - pos.y = pos.y + (self.collisionbox[5] - self.collisionbox[2]) * .5 - - local texture = "mobs_blood.png" - -- full heart damage (one particle for each 2 HP damage) - if amount_large > 0 then - effect(pos, amount_large, texture, 2, 2, 1.75, 0, nil, true) - end - -- half heart damage (one additional particle if damage is an odd number) - if amount_small > 0 then - -- TODO: Use "half heart" - effect(pos, amount_small, texture, 1, 1, 1.75, 0, nil, true) - end - end -end - -mobs.death_effect = function(pos, yaw, collisionbox, rotate) - local min, max - if collisionbox then - min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]} - max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]} - else - min = { x = -0.5, y = 0, z = -0.5 } - max = { x = 0.5, y = 0.5, z = 0.5 } - end - if rotate then - min = vector.rotate(min, {x=0, y=yaw, z=pi/2}) - max = vector.rotate(max, {x=0, y=yaw, z=pi/2}) - min, max = vector.sort(min, max) - min = vector.multiply(min, 0.5) - max = vector.multiply(max, 0.5) - end - - minetest.add_particlespawner({ - amount = 50, - time = 0.001, - minpos = vector.add(pos, min), - maxpos = vector.add(pos, max), - minvel = vector.new(-5,-5,-5), - maxvel = vector.new(5,5,5), - minexptime = 1.1, - maxexptime = 1.5, - minsize = 1, - maxsize = 2, - collisiondetection = false, - vertical = false, - texture = "mcl_particles_mob_death.png^[colorize:#000000:255", - }) - - minetest.sound_play("mcl_mobs_mob_poof", { - pos = pos, - gain = 1.0, - max_hear_distance = 8, - }, true) -end - -local update_tag = function(self) - local tag - if mobs_debug then - tag = "nametag = '"..tostring(self.nametag).."'\n".. - "state = '"..tostring(self.state).."'\n".. - "order = '"..tostring(self.order).."'\n".. - "attack = "..tostring(self.attack).."\n".. - "health = "..tostring(self.health).."\n".. - "breath = "..tostring(self.breath).."\n".. - "gotten = "..tostring(self.gotten).."\n".. - "tamed = "..tostring(self.tamed).."\n".. - "horny = "..tostring(self.horny).."\n".. - "hornytimer = "..tostring(self.hornytimer).."\n".. - "runaway_timer = "..tostring(self.runaway_timer).."\n".. - "following = "..tostring(self.following) - else - tag = self.nametag - end - self.object:set_properties({ - nametag = tag, - }) - - update_roll(self) -end - --- drop items -local item_drop = function(self, cooked, looting_level) - - -- no drops if disabled by setting - if not mobs_drop_items then return end - - looting_level = looting_level or 0 - - -- no drops for child mobs (except monster) - if (self.child and self.type ~= "monster") then - return - end - - local obj, item, num - local pos = self.object:get_pos() - - self.drops = self.drops or {} -- nil check - - for n = 1, #self.drops do - local dropdef = self.drops[n] - local chance = 1 / dropdef.chance - local looting_type = dropdef.looting - - if looting_level > 0 then - local chance_function = dropdef.looting_chance_function - if chance_function then - chance = chance_function(looting_level) - elseif looting_type == "rare" then - chance = chance + (dropdef.looting_factor or 0.01) * looting_level - end - end - - local num = 0 - local do_common_looting = (looting_level > 0 and looting_type == "common") - if random() < chance then - num = random(dropdef.min or 1, dropdef.max or 1) - elseif not dropdef.looting_ignore_chance then - do_common_looting = false - end - - if do_common_looting then - num = num + math.floor(math.random(0, looting_level) + 0.5) - end - - if num > 0 then - item = dropdef.name - - -- cook items when true - if cooked then - - local output = minetest.get_craft_result({ - method = "cooking", width = 1, items = {item}}) - - if output and output.item and not output.item:is_empty() then - item = output.item:get_name() - end + if minetest_registered_entities[new_name] then + minetest_add_entity(self.object:get_pos(), new_name) end - -- add item if it exists - for x = 1, num do - obj = minetest.add_item(pos, ItemStack(item .. " " .. 1)) - end - - if obj and obj:get_luaentity() then - - obj:set_velocity({ - x = random(-10, 10) / 9, - y = 6, - z = random(-10, 10) / 9, - }) - elseif obj then - obj:remove() -- item does not exist - end - end - end - - self.drops = {} -end - - --- check if mob is dead or only hurt -local check_for_death = function(self, cause, cmi_cause) - - if self.state == "die" then - return true - end - - -- has health actually changed? - if self.health == self.old_health and self.health > 0 then - return false - end - - local damaged = self.health < self.old_health - self.old_health = self.health - - -- still got some health? - if self.health > 0 then - - -- make sure health isn't higher than max - if self.health > self.hp_max then - self.health = self.hp_max - end - - -- play damage sound if health was reduced and make mob flash red. - if damaged then - add_texture_mod(self, "^[colorize:red:130") - minetest.after(.2, function(self) - if self and self.object then - remove_texture_mod(self, "^[colorize:red:130") - end - end, self) - mob_sound(self, "damage") - end - - -- backup nametag so we can show health stats - if not self.nametag2 then - self.nametag2 = self.nametag or "" - end - - if show_health - and (cmi_cause and cmi_cause.type == "punch") then - - self.htimer = 2 - self.nametag = "♥ " .. self.health .. " / " .. self.hp_max - - update_tag(self) - end - - return false - end - - mob_sound(self, "death") - - local function death_handle(self) - -- dropped cooked item if mob died in fire or lava - if cause == "lava" or cause == "fire" then - item_drop(self, true, 0) - else - local wielditem = ItemStack() - if cause == "hit" then - local puncher = cmi_cause.puncher - if puncher then - wielditem = puncher:get_wielded_item() - end - end - local cooked = mcl_burning.is_burning(self.object) or mcl_enchanting.has_enchantment(wielditem, "fire_aspect") - local looting = mcl_enchanting.get_enchantment(wielditem, "looting") - item_drop(self, cooked, looting) - - if mod_experience and ((not self.child) or self.type ~= "animal") and (minetest.get_us_time() - self.xp_timestamp <= 5000000) then - mcl_experience.throw_experience(self.object:get_pos(), math.random(self.xp_min, self.xp_max)) - end - end - end - - -- execute custom death function - if self.on_die then - - local pos = self.object:get_pos() - local on_die_exit = self.on_die(self, pos, cmi_cause) - if on_die_exit ~= true then - death_handle(self) - end - - if use_cmi then - cmi.notify_die(self.object, cmi_cause) - end - - if on_die_exit == true then - self.state = "die" - mcl_burning.extinguish(self.object) self.object:remove() - return true end + }) +end + + +-- Spawn a child +function mobs:spawn_child(pos, mob_type) + local child = minetest_add_entity(pos, mob_type) + if not child then + return end - local collisionbox - if self.collisionbox then - collisionbox = table.copy(self.collisionbox) + local ent = child:get_luaentity() + effect(pos, 15, "mcl_particles_smoke.png", 1, 2, 2, 15, 5) + + ent.child = true + + local textures + -- using specific child texture (if found) + if ent.child_texture then + textures = ent.child_texture[1] end - self.state = "die" - self.attack = nil - self.v_start = false - self.fall_speed = DEFAULT_FALL_SPEED - self.timer = 0 - self.blinktimer = 0 - remove_texture_mod(self, "^[colorize:#FF000040") - remove_texture_mod(self, "^[brighten") - self.passive = true - - self.object:set_properties({ - pointable = false, - collide_with_objects = false, + -- and resize to half height + child:set_properties({ + textures = textures, + visual_size = { + x = ent.base_size.x * .5, + y = ent.base_size.y * .5, + }, + collisionbox = { + ent.base_colbox[1] * .5, + ent.base_colbox[2] * .5, + ent.base_colbox[3] * .5, + ent.base_colbox[4] * .5, + ent.base_colbox[5] * .5, + ent.base_colbox[6] * .5, + }, + selectionbox = { + ent.base_selbox[1] * .5, + ent.base_selbox[2] * .5, + ent.base_selbox[3] * .5, + ent.base_selbox[4] * .5, + ent.base_selbox[5] * .5, + ent.base_selbox[6] * .5, + }, }) - set_velocity(self, 0) - local acc = self.object:get_acceleration() - acc.x, acc.y, acc.z = 0, DEFAULT_FALL_SPEED, 0 - self.object:set_acceleration(acc) - - local length - -- default death function and die animation (if defined) - if self.instant_death then - length = 0 - elseif self.animation - and self.animation.die_start - and self.animation.die_end then - - local frames = self.animation.die_end - self.animation.die_start - local speed = self.animation.die_speed or 15 - length = max(frames / speed, 0) + DEATH_DELAY - set_animation(self, "die") - else - local rot = self.object:get_rotation() - rot.z = pi/2 - self.object:set_rotation(rot) - length = 1 + DEATH_DELAY - set_animation(self, "stand", true) - end - - - -- Remove body after a few seconds and drop stuff - local kill = function(self) - if not self.object:get_luaentity() then - return - end - if use_cmi then - cmi.notify_die(self.object, cmi_cause) - end - - death_handle(self) - local dpos = self.object:get_pos() - local cbox = self.collisionbox - local yaw = self.object:get_rotation().y - mcl_burning.extinguish(self.object) - self.object:remove() - mobs.death_effect(dpos, yaw, cbox, not self.instant_death) - end - if length <= 0 then - kill(self) - else - minetest.after(length, kill, self) - end - - return true + return child end --- check if within physical map limits (-30911 to 30927) -local within_limits, wmin, wmax = nil, -30913, 30928 -within_limits = function(pos, radius) - if mcl_vars then - if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then - wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max - within_limits = function(pos, radius) - return pos - and (pos.x - radius) > wmin and (pos.x + radius) < wmax - and (pos.y - radius) > wmin and (pos.y + radius) < wmax - and (pos.z - radius) > wmin and (pos.z + radius) < wmax - end - end - end - return pos - and (pos.x - radius) > wmin and (pos.x + radius) < wmax - and (pos.y - radius) > wmin and (pos.y + radius) < wmax - and (pos.z - radius) > wmin and (pos.z + radius) < wmax -end - --- is mob facing a cliff or danger -local is_at_cliff_or_danger = function(self) - - if self.fear_height == 0 then -- 0 for no falling protection! +-- feeding, taming and breeding (thanks blert2112) +function mobs:feed_tame(self, clicker, feed_count, breed, tame) + if not self.follow then return false end - if not self.object:get_luaentity() then - return false - end - local yaw = self.object:get_yaw() - local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5) - local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5) - local pos = self.object:get_pos() - local ypos = pos.y + self.collisionbox[2] -- just above floor + -- can eat/tame with item in hand + if follow_holding(self, clicker) then - local free_fall, blocker = minetest.line_of_sight( - {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z}, - {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}) - if free_fall then - return true - else - local bnode = minetest.get_node(blocker) - local danger = is_node_dangerous(self, bnode.name) - if danger then - return true - else - local def = minetest.registered_nodes[bnode.name] - if def and def.walkable then - return false + -- if not in creative then take item + if not mobs.is_creative(clicker:get_player_name()) then + + local item = clicker:get_wielded_item() + + item:take_item() + + clicker:set_wielded_item(item) + end + + mob_sound(self, "eat", nil, true) + + -- increase health + self.health = self.health + 4 + + if self.health >= self.hp_max then + + self.health = self.hp_max + + if self.htimer < 1 then + self.htimer = 5 end end - end - return false -end - - --- copy the 'mob facing cliff_or_danger check' from above, and rework to avoid water -local is_at_water_danger = function(self) - - - if not self.object:get_luaentity() then - return false - end - local yaw = self.object:get_yaw() - local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5) - local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5) - local pos = self.object:get_pos() - local ypos = pos.y + self.collisionbox[2] -- just above floor - - local free_fall, blocker = minetest.line_of_sight( - {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z}, - {x = pos.x + dir_x, y = ypos - 3, z = pos.z + dir_z}) - if free_fall then - return true - else - local bnode = minetest.get_node(blocker) - local waterdanger = is_node_waterhazard(self, bnode.name) - if - waterdanger and (is_node_waterhazard(self, self.standing_in) or is_node_waterhazard(self, self.standing_on)) then - return false - elseif waterdanger and (is_node_waterhazard(self, self.standing_in) or is_node_waterhazard(self, self.standing_on)) == false then - return true - else - local def = minetest.registered_nodes[bnode.name] - if def and def.walkable then - return false - end - end - end - - return false -end - - --- get node but use fallback for nil or unknown -local node_ok = function(pos, fallback) - - fallback = fallback or mobs.fallback_node - - local node = minetest.get_node_or_nil(pos) - - if node and minetest.registered_nodes[node.name] then - return node - end - - return minetest.registered_nodes[fallback] -end - -local function get_light(pos, tod) - if math.abs(pos.x) < 31000 and math.abs(pos.y) < 31000 and math.abs(pos.z) < 31000 then - local lightfunc = minetest.get_natural_light or minetest.get_node_light - return lightfunc(pos, tod) - else - return 0 - end -end - --- environmental damage (water, lava, fire, light etc.) -local do_env_damage = function(self) - - -- feed/tame text timer (so mob 'full' messages dont spam chat) - if self.htimer > 0 then - self.htimer = self.htimer - 1 - end - - -- reset nametag after showing health stats - if self.htimer < 1 and self.nametag2 then - - self.nametag = self.nametag2 - self.nametag2 = nil + self.object:set_hp(self.health) update_tag(self) - end - local pos = self.object:get_pos() + -- make children grow quicker + if self.child == true then - self.time_of_day = minetest.get_timeofday() + -- deduct 10% of the time to adulthood + self.hornytimer = self.hornytimer + ((CHILD_GROW_TIME - self.hornytimer) * 0.1) + + return true + end + + -- feed and tame + self.food = (self.food or 0) + 1 + if self.food >= feed_count then + + self.food = 0 + + if breed and self.hornytimer == 0 then + self.horny = true + end + + if tame then + + self.tamed = true + + if not self.owner or self.owner == "" then + self.owner = clicker:get_player_name() + end + end + + -- make sound when fed so many times + mob_sound(self, "random", true) + end - -- remove mob if beyond map limits - if not within_limits(pos, 0) then - mcl_burning.extinguish(self.object) - self.object:remove() return true end + return false +end - -- Deal light damage to mob, returns true if mob died - local deal_light_damage = function(self, pos, damage) - if not (mod_weather and (mcl_weather.rain.raining or mcl_weather.state == "snow") and mcl_weather.is_outdoor(pos)) then - self.health = self.health - damage +-- no damage to nodes explosion +function mobs:safe_boom(self, pos, strength) + minetest_sound_play(self.sounds and self.sounds.explode or "tnt_explode", { + pos = pos, + gain = 1.0, + max_hear_distance = self.sounds and self.sounds.distance or 32 + }, true) + local radius = strength + entity_physics(pos, radius) + effect(pos, 32, "mcl_particles_smoke.png", radius * 3, radius * 5, radius, 1, 0) +end - effect(pos, 5, "mcl_particles_smoke.png") - if check_for_death(self, "light", {type = "light"}) then - return true - end - end - end - - -- Use get_node_light for Minetest version 5.3 where get_natural_light - -- does not exist yet. - local sunlight = get_light(pos, self.time_of_day) - - -- bright light harms mob - if self.light_damage ~= 0 and (sunlight or 0) > 12 then - if deal_light_damage(self, pos, self.light_damage) then - return true - end - end - local _, dim = nil, "overworld" - if mod_worlds then - _, dim = mcl_worlds.y_to_layer(pos.y) - end - if (self.sunlight_damage ~= 0 or self.ignited_by_sunlight) and (sunlight or 0) >= minetest.LIGHT_MAX and dim == "overworld" then - if self.ignited_by_sunlight then - mcl_burning.set_on_fire(self.object, 10) +-- make explosion with protection and tnt mod check +function mobs:boom(self, pos, strength, fire) + self.object:remove() + if mod_explosions then + if mobs_griefing and not minetest_is_protected(pos, "") then + mcl_explosions.explode(pos, strength, { drop_chance = 1.0, fire = fire }, self.object) else - deal_light_damage(self, pos, self.sunlight_damage) - return true - end - end - - local y_level = self.collisionbox[2] - - if self.child then - y_level = self.collisionbox[2] * 0.5 - end - - -- what is mob standing in? - pos.y = pos.y + y_level + 0.25 -- foot level - local pos2 = {x=pos.x, y=pos.y-1, z=pos.z} - self.standing_in = node_ok(pos, "air").name - self.standing_on = node_ok(pos2, "air").name - - -- don't fall when on ignore, just stand still - if self.standing_in == "ignore" then - self.object:set_velocity({x = 0, y = 0, z = 0}) - end - - local nodef = minetest.registered_nodes[self.standing_in] - - -- rain - if self.rain_damage > 0 and mod_weather then - if mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then - - self.health = self.health - self.rain_damage - - if check_for_death(self, "rain", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end - end - end - - pos.y = pos.y + 1 -- for particle effect position - - -- water damage - if self.water_damage > 0 - and nodef.groups.water then - - if self.water_damage ~= 0 then - - self.health = self.health - self.water_damage - - effect(pos, 5, "mcl_particles_smoke.png", nil, nil, 1, nil) - - if check_for_death(self, "water", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end - end - - -- lava damage - elseif self.lava_damage > 0 - and (nodef.groups.lava) then - - if self.lava_damage ~= 0 then - - self.health = self.health - self.lava_damage - - effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil) - - if check_for_death(self, "lava", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end - end - - -- fire damage - elseif self.fire_damage > 0 - and (nodef.groups.fire) then - - if self.fire_damage ~= 0 then - - self.health = self.health - self.fire_damage - - effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil) - - if check_for_death(self, "fire", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end - end - - -- damage_per_second node check - elseif nodef.damage_per_second ~= 0 and not nodef.groups.lava and not nodef.groups.fire then - - self.health = self.health - nodef.damage_per_second - - effect(pos, 5, "mcl_particles_smoke.png") - - if check_for_death(self, "dps", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end - end - - -- Drowning damage - if self.breath_max ~= -1 then - local drowning = false - if self.breathes_in_water then - if minetest.get_item_group(self.standing_in, "water") == 0 then - drowning = true - end - elseif nodef.drowning > 0 then - drowning = true - end - if drowning then - - self.breath = math.max(0, self.breath - 1) - - effect(pos, 2, "bubble.png", nil, nil, 1, nil) - if self.breath <= 0 then - local dmg - if nodef.drowning > 0 then - dmg = nodef.drowning - else - dmg = 4 - end - damage_effect(self, dmg) - self.health = self.health - dmg - end - if check_for_death(self, "drowning", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end - else - self.breath = math.min(self.breath_max, self.breath + 1) - end - end - - --- suffocation inside solid node - -- FIXME: Redundant with mcl_playerplus - if (self.suffocation == true) - and (nodef.walkable == nil or nodef.walkable == true) - and (nodef.collision_box == nil or nodef.collision_box.type == "regular") - and (nodef.node_box == nil or nodef.node_box.type == "regular") - and (nodef.groups.disable_suffocation ~= 1) - and (nodef.groups.opaque == 1) then - - -- Short grace period before starting to take suffocation damage. - -- This is different from players, who take damage instantly. - -- This has been done because mobs might briefly be inside solid nodes - -- when e.g. climbing up stairs. - -- This is a bit hacky because it assumes that do_env_damage - -- is called roughly every second only. - self.suffocation_timer = self.suffocation_timer + 1 - if self.suffocation_timer >= 3 then - -- 2 damage per second - -- TODO: Deal this damage once every 1/2 second - self.health = self.health - 2 - - if check_for_death(self, "suffocation", {type = "environment", - pos = pos, node = self.standing_in}) then - return true - end + mobs:safe_boom(self, pos, strength) end else - self.suffocation_timer = 0 - end - - return check_for_death(self, "", {type = "unknown"}) -end - - --- jump if facing a solid node (not fences or gates) -local do_jump = function(self) - - if not self.jump - or self.jump_height == 0 - or self.fly - or (self.child and self.type ~= "monster") - or self.order == "stand" then - return false - end - - self.facing_fence = false - - -- something stopping us while moving? - if self.state ~= "stand" - and get_velocity(self) > 0.5 - and self.object:get_velocity().y ~= 0 then - return false - end - - local pos = self.object:get_pos() - local yaw = self.object:get_yaw() - - -- what is mob standing on? - pos.y = pos.y + self.collisionbox[2] - 0.2 - - local nod = node_ok(pos) - - if minetest.registered_nodes[nod.name].walkable == false then - return false - end - - -- where is front - local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5) - local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5) - - -- what is in front of mob? - nod = node_ok({ - x = pos.x + dir_x, - y = pos.y + 0.5, - z = pos.z + dir_z - }) - - -- this is used to detect if there's a block on top of the block in front of the mob. - -- If there is, there is no point in jumping as we won't manage. - local nodTop = node_ok({ - x = pos.x + dir_x, - y = pos.y + 1.5, - z = pos.z + dir_z - }, "air") - - -- we don't attempt to jump if there's a stack of blocks blocking - if minetest.registered_nodes[nodTop.name].walkable == true then - return false - end - - -- thin blocks that do not need to be jumped - if nod.name == node_snow then - return false - end - - if self.walk_chance == 0 - or minetest.registered_items[nod.name].walkable then - - if minetest.get_item_group(nod.name, "fence") == 0 - and minetest.get_item_group(nod.name, "fence_gate") == 0 - and minetest.get_item_group(nod.name, "wall") == 0 then - - local v = self.object:get_velocity() - - v.y = self.jump_height - - set_animation(self, "jump") -- only when defined - - self.object:set_velocity(v) - - -- when in air move forward - minetest.after(0.3, function(self, v) - if (not self.object) or (not self.object:get_luaentity()) or (self.state == "die") then - return - end - self.object:set_acceleration({ - x = v.x * 2, - y = -10, - z = v.z * 2, - }) - end, self, v) - - if self.jump_sound_cooloff <= 0 then - mob_sound(self, "jump") - self.jump_sound_cooloff = 0.5 - end - else - self.facing_fence = true - end - - -- if we jumped against a block/wall 4 times then turn - if self.object:get_velocity().x ~= 0 - and self.object:get_velocity().z ~= 0 then - - self.jump_count = (self.jump_count or 0) + 1 - - if self.jump_count == 4 then - - local yaw = self.object:get_yaw() or 0 - - yaw = set_yaw(self, yaw + 1.35, 8) - - self.jump_count = 0 - end - end - - return true - end - - return false -end - - --- blast damage to entities nearby -local entity_physics = function(pos, radius) - - radius = radius * 2 - - local objs = minetest.get_objects_inside_radius(pos, radius) - local obj_pos, dist - - for n = 1, #objs do - - obj_pos = objs[n]:get_pos() - - dist = vector.distance(pos, obj_pos) - if dist < 1 then dist = 1 end - - local damage = floor((4 / dist) * radius) - local ent = objs[n]:get_luaentity() - - -- punches work on entities AND players - objs[n]:punch(objs[n], 1.0, { - full_punch_interval = 1.0, - damage_groups = {fleshy = damage}, - }, pos) + mobs:safe_boom(self, pos, strength) end end +-- falling and fall damage +-- returns true if mob died +local falling = function(self, pos) --- should mob follow what I'm holding ? -local follow_holding = function(self, clicker) - - if mobs.invis[clicker:get_player_name()] then - return false + if self.fly and self.state ~= "die" then + return end - local item = clicker:get_wielded_item() - local t = type(self.follow) - - -- single item - if t == "string" - and item:get_name() == self.follow then - return true - - -- multiple items - elseif t == "table" then - - for no = 1, #self.follow do - - if self.follow[no] == item:get_name() then - return true - end + if mcl_portals ~= nil then + if mcl_portals.nether_portal_cooloff(self.object) then + return false -- mob has teleported through Nether portal - it's 99% not falling end end - return false -end + -- floating in water (or falling) + local v = self.object:get_velocity() + if v.y > 0 then --- find two animals of same type and breed if nearby and horny -local breed = function(self) + -- apply gravity when moving up + self.object:set_acceleration({ + x = 0, + y = -10, + z = 0 + }) - -- child takes a long time before growing into adult - if self.child == true then + elseif v.y <= 0 and v.y > self.fall_speed then - -- When a child, hornytimer is used to count age until adulthood - self.hornytimer = self.hornytimer + 1 + -- fall downwards at set speed + self.object:set_acceleration({ + x = 0, + y = self.fall_speed, + z = 0 + }) + else + -- stop accelerating once max fall speed hit + self.object:set_acceleration({x = 0, y = 0, z = 0}) + end - if self.hornytimer >= CHILD_GROW_TIME then + if minetest_registered_nodes[node_ok(pos).name].groups.lava then - self.child = false - self.hornytimer = 0 + if self.floats_on_lava == 1 then - self.object:set_properties({ - textures = self.base_texture, - mesh = self.base_mesh, - visual_size = self.base_size, - collisionbox = self.base_colbox, - selectionbox = self.base_selbox, + self.object:set_acceleration({ + x = 0, + y = -self.fall_speed / (max(1, v.y) ^ 2), + z = 0 }) - - -- custom function when child grows up - if self.on_grown then - self.on_grown(self) - else - -- jump when fully grown so as not to fall into ground - self.object:set_velocity({ - x = 0, - y = self.jump_height, - z = 0 - }) - end - end - - return - end - - -- horny animal can mate for HORNY_TIME seconds, - -- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds - if self.horny == true - and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then - - self.hornytimer = self.hornytimer + 1 - - if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then - self.hornytimer = 0 - self.horny = false end end - -- find another same animal who is also horny and mate if nearby - if self.horny == true - and self.hornytimer <= HORNY_TIME then + -- in water then float up + if minetest_registered_nodes[node_ok(pos).name].groups.water then - local pos = self.object:get_pos() + if self.floats == 1 then - effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1) + self.object:set_acceleration({ + x = 0, + y = -self.fall_speed / (math_max(1, v.y) ^ 2), + z = 0 + }) + end + else - local objs = minetest.get_objects_inside_radius(pos, 3) - local num = 0 - local ent = nil + -- fall damage onto solid ground + if self.fall_damage == 1 + and self.object:get_velocity().y == 0 then - for n = 1, #objs do + local d = (self.old_y or 0) - self.object:get_pos().y - ent = objs[n]:get_luaentity() + if d > 5 then - -- check for same animal with different colour - local canmate = false + local add = minetest_get_item_group(self.standing_on, "fall_damage_add_percent") + local damage = d - 5 + if add ~= 0 then + damage = damage + damage * (add/100) + end + damage = math_floor(damage) + if damage > 0 then + self.health = self.health - damage - if ent then + effect(pos, 5, "mcl_particles_smoke.png", 1, 2, 2, nil) - 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 + if check_for_death(self, "fall", {type = "fall"}) then + return true end end end - if ent - and canmate == true - and ent.horny == true - and ent.hornytimer <= HORNY_TIME then - num = num + 1 - end - - -- found your mate? then have a baby - if num > 1 then - - self.hornytimer = HORNY_TIME + 1 - ent.hornytimer = HORNY_TIME + 1 - - -- spawn baby - minetest.after(5, function(parent1, parent2, pos) - if not parent1.object:get_luaentity() then - return - end - if not parent2.object:get_luaentity() then - return - end - - -- Give XP - if mod_experience then - mcl_experience.throw_experience(pos, math.random(1, 7)) - end - - -- custom breed function - if parent1.on_breed then - -- when false, skip going any further - if parent1.on_breed(parent1, parent2) == false then - return - end - end - - local child = mobs:spawn_child(pos, parent1.name) - - local ent_c = child:get_luaentity() - - - -- Use texture of one of the parents - local p = math.random(1, 2) - if p == 1 then - ent_c.base_texture = parent1.base_texture - else - ent_c.base_texture = parent2.base_texture - end - child:set_properties({ - textures = ent_c.base_texture - }) - - -- tamed and owned by parents' owner - ent_c.tamed = true - ent_c.owner = parent1.owner - end, self, ent, pos) - - num = 0 - - break - end + self.old_y = self.object:get_pos().y end end end - --- find and replace what mob is looking for (grass, wheat etc.) -local replace = function(self, pos) - - if not self.replace_rate - or not self.replace_what - or self.child == true - or self.object:get_velocity().y ~= 0 - or random(1, self.replace_rate) > 1 then - return - end - - local what, with, y_offset - - if type(self.replace_what[1]) == "table" then - - local num = random(#self.replace_what) - - what = self.replace_what[num][1] or "" - with = self.replace_what[num][2] or "" - y_offset = self.replace_what[num][3] or 0 - else - what = self.replace_what - with = self.replace_with or "" - y_offset = self.replace_offset or 0 - end - - pos.y = pos.y + y_offset - - local node = minetest.get_node(pos) - if node.name == what then - - local oldnode = {name = what, param2 = node.param2} - local newnode = {name = with, param2 = node.param2} - local on_replace_return - - if self.on_replace then - on_replace_return = self.on_replace(self, pos, oldnode, newnode) - end - - if on_replace_return ~= false then - - if mobs_griefing then - minetest.set_node(pos, newnode) - end - +local teleport = function(self, target) + if self.do_teleport then + if self.do_teleport(self, target) == false then + return end end end --- check if daytime and also if mob is docile during daylight hours -local day_docile = function(self) - - if self.docile_by_day == false then - - return false - - elseif self.docile_by_day == true - and self.time_of_day > 0.2 - and self.time_of_day < 0.8 then - - return true - end -end - - -local los_switcher = false -local height_switcher = false - --- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3 -local smart_mobs = function(self, s, p, dist, dtime) - - local s1 = self.path.lastpos - - local target_pos = self.attack:get_pos() - - -- is it becoming stuck? - if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then - self.path.stuck_timer = self.path.stuck_timer + dtime - else - self.path.stuck_timer = 0 - end - - self.path.lastpos = {x = s.x, y = s.y, z = s.z} - - local use_pathfind = false - local has_lineofsight = minetest.line_of_sight( - {x = s.x, y = (s.y) + .5, z = s.z}, - {x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2) - - -- im stuck, search for path - if not has_lineofsight then - - if los_switcher == true then - use_pathfind = true - los_switcher = false - end -- cannot see target! - else - if los_switcher == false then - - los_switcher = true - use_pathfind = false - - minetest.after(1, function(self) - if not self.object:get_luaentity() then - return - end - if has_lineofsight then self.path.following = false end - end, self) - end -- can see target! - end - - if (self.path.stuck_timer > stuck_timeout and not self.path.following) then - - use_pathfind = true - self.path.stuck_timer = 0 - - minetest.after(1, function(self) - if not self.object:get_luaentity() then - return - end - if has_lineofsight then self.path.following = false end - end, self) - end - - if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then - - use_pathfind = true - self.path.stuck_timer = 0 - - minetest.after(1, function(self) - if not self.object:get_luaentity() then - return - end - if has_lineofsight then self.path.following = false end - end, self) - end - - if math.abs(vector.subtract(s,target_pos).y) > self.stepheight then - - if height_switcher then - use_pathfind = true - height_switcher = false - end - else - if not height_switcher then - use_pathfind = false - height_switcher = true - end - end - - if use_pathfind then - -- lets try find a path, first take care of positions - -- since pathfinder is very sensitive - local sheight = self.collisionbox[5] - self.collisionbox[2] - - -- round position to center of node to avoid stuck in walls - -- also adjust height for player models! - s.x = floor(s.x + 0.5) - s.z = floor(s.z + 0.5) - - local ssight, sground = minetest.line_of_sight(s, { - x = s.x, y = s.y - 4, z = s.z}, 1) - - -- determine node above ground - if not ssight then - s.y = sground.y + 1 - end - - local p1 = self.attack:get_pos() - - p1.x = floor(p1.x + 0.5) - p1.y = floor(p1.y + 0.5) - p1.z = floor(p1.z + 0.5) - - local dropheight = 12 - if self.fear_height ~= 0 then dropheight = self.fear_height end - local jumpheight = 0 - if self.jump and self.jump_height >= 4 then - jumpheight = math.min(math.ceil(self.jump_height / 4), 4) - elseif self.stepheight > 0.5 then - jumpheight = 1 - end - self.path.way = minetest.find_path(s, p1, 16, jumpheight, dropheight, "A*_noprefetch") - - self.state = "" - do_attack(self, self.attack) - - -- no path found, try something else - if not self.path.way then - - self.path.following = false - - -- lets make way by digging/building if not accessible - if self.pathfinding == 2 and mobs_griefing then - - -- is player higher than mob? - if s.y < p1.y then - - -- build upwards - if not minetest.is_protected(s, "") then - - local ndef1 = minetest.registered_nodes[self.standing_in] - - if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then - - minetest.set_node(s, {name = mobs.fallback_node}) - end - end - - local sheight = math.ceil(self.collisionbox[5]) + 1 - - -- assume mob is 2 blocks high so it digs above its head - s.y = s.y + sheight - - -- remove one block above to make room to jump - if not minetest.is_protected(s, "") then - - local node1 = node_ok(s, "air").name - local ndef1 = minetest.registered_nodes[node1] - - if node1 ~= "air" - and node1 ~= "ignore" - and ndef1 - and not ndef1.groups.level - and not ndef1.groups.unbreakable - and not ndef1.groups.liquid then - - minetest.set_node(s, {name = "air"}) - minetest.add_item(s, ItemStack(node1)) - - end - end - - s.y = s.y - sheight - self.object:set_pos({x = s.x, y = s.y + 2, z = s.z}) - - else -- dig 2 blocks to make door toward player direction - - local yaw1 = self.object:get_yaw() + pi / 2 - local p1 = { - x = s.x + cos(yaw1), - y = s.y, - z = s.z + sin(yaw1) - } - - if not minetest.is_protected(p1, "") then - - local node1 = node_ok(p1, "air").name - local ndef1 = minetest.registered_nodes[node1] - - if node1 ~= "air" - and node1 ~= "ignore" - and ndef1 - and not ndef1.groups.level - and not ndef1.groups.unbreakable - and not ndef1.groups.liquid then - - minetest.add_item(p1, ItemStack(node1)) - minetest.set_node(p1, {name = "air"}) - end - - p1.y = p1.y + 1 - node1 = node_ok(p1, "air").name - ndef1 = minetest.registered_nodes[node1] - - if node1 ~= "air" - and node1 ~= "ignore" - and ndef1 - and not ndef1.groups.level - and not ndef1.groups.unbreakable - and not ndef1.groups.liquid then - - minetest.add_item(p1, ItemStack(node1)) - minetest.set_node(p1, {name = "air"}) - end - - end - end - end - - -- will try again in 2 seconds - self.path.stuck_timer = stuck_timeout - 2 - elseif s.y < p1.y and (not self.fly) then - do_jump(self) --add jump to pathfinding - self.path.following = true - -- Yay, I found path! - -- TODO: Implement war_cry sound without being annoying - --mob_sound(self, "war_cry", true) - else - set_velocity(self, self.walk_velocity) - - -- follow path now that it has it - self.path.following = true - end - end -end - - --- specific attacks -local specific_attack = function(list, what) - - -- no list so attack default (player, animals etc.) - if list == nil then - return true - end - - -- found entity on list to attack? - for no = 1, #list do - - if list[no] == what then - return true - end - end - - return false -end - --- monster find someone to attack -local monster_attack = function(self) - - if self.type ~= "monster" - or not damage_enabled - or minetest.is_creative_enabled("") - or self.passive - or self.state == "attack" - or day_docile(self) then - return - end - - local s = self.object:get_pos() - local p, sp, dist - local player, obj, min_player - local type, name = "", "" - local min_dist = self.view_range + 1 - local objs = minetest.get_objects_inside_radius(s, self.view_range) - - for n = 1, #objs do - - if objs[n]:is_player() then - - if mobs.invis[ objs[n]:get_player_name() ] or (not object_in_range(self, objs[n])) then - type = "" - else - player = objs[n] - type = "player" - name = "player" - end - else - obj = objs[n]:get_luaentity() - - if obj then - player = obj.object - type = obj.type - name = obj.name or "" - end - end - - -- find specific mob to attack, failing that attack player/npc/animal - if specific_attack(self.specific_attack, name) - and (type == "player" or type == "npc" - or (type == "animal" and self.attack_animals == true)) then - - p = player:get_pos() - sp = s - - dist = vector.distance(p, s) - - -- aim higher to make looking up hills more realistic - p.y = p.y + 1 - sp.y = sp.y + 1 - - - -- choose closest player to attack - if dist < min_dist - and line_of_sight(self, sp, p, 2) == true then - min_dist = dist - min_player = player - end - end - end - - -- attack player - if min_player then - do_attack(self, min_player) - end -end - - --- npc, find closest monster to attack -local npc_attack = function(self) - - if self.type ~= "npc" - or not self.attacks_monsters - or self.state == "attack" then - return - end - - local p, sp, obj, min_player - local s = self.object:get_pos() - local min_dist = self.view_range + 1 - local objs = minetest.get_objects_inside_radius(s, self.view_range) - - for n = 1, #objs do - - obj = objs[n]:get_luaentity() - - if obj and obj.type == "monster" then - - p = obj.object:get_pos() - sp = s - - local dist = vector.distance(p, s) - - -- aim higher to make looking up hills more realistic - p.y = p.y + 1 - sp.y = sp.y + 1 - - if dist < min_dist - and line_of_sight(self, sp, p, 2) == true then - min_dist = dist - min_player = obj.object - end - end - end - - if min_player then - do_attack(self, min_player) - end -end - - --- specific runaway -local specific_runaway = function(list, what) - - -- no list so do not run - if list == nil then - return false - end - - -- found entity on list to attack? - for no = 1, #list do - - if list[no] == what then - return true - end - end - - return false -end - - -- find someone to runaway from local runaway_from = function(self) @@ -2060,7 +330,7 @@ local runaway_from = function(self) local player, obj, min_player local type, name = "", "" local min_dist = self.view_range + 1 - local objs = minetest.get_objects_inside_radius(s, self.view_range) + local objs = minetest_get_objects_inside_radius(s, self.view_range) for n = 1, #objs do @@ -2117,10 +387,10 @@ local runaway_from = function(self) z = lp.z - s.z } - local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate + local yaw = (atan(vec.z / vec.x) + 3 * math_pi / 2) - self.rotate if lp.x > s.x then - yaw = yaw + pi + yaw = yaw + math_pi end yaw = set_yaw(self, yaw, 4) @@ -2131,6 +401,26 @@ local runaway_from = function(self) end +-- specific runaway +local specific_runaway = function(list, what) + + -- no list so do not run + if list == nil then + return false + end + + -- found entity on list to attack? + for no = 1, #list do + + if list[no] == what then + return true + end + end + + return false +end + + -- follow player if owner or holding item, if fish outta water then flop local follow_flop = function(self) @@ -2209,9 +499,9 @@ local follow_flop = function(self) z = p.z - s.z } - local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate + local yaw = (atan(vec.z / vec.x) + math_pi / 2) - self.rotate - if p.x > s.x then yaw = yaw + pi end + if p.x > s.x then yaw = yaw + math_pi end set_yaw(self, yaw, 2.35) @@ -2242,14 +532,14 @@ local follow_flop = function(self) self.state = "flop" self.object:set_acceleration({x = 0, y = DEFAULT_FALL_SPEED, z = 0}) - local sdef = minetest.registered_nodes[self.standing_on] + local sdef = minetest_registered_nodes[self.standing_on] -- Flop on ground if sdef and sdef.walkable then mob_sound(self, "flop") self.object:set_velocity({ - x = math.random(-FLOP_HOR_SPEED, FLOP_HOR_SPEED), + x = math_random(-FLOP_HOR_SPEED, FLOP_HOR_SPEED), y = FLOP_HEIGHT, - z = math.random(-FLOP_HOR_SPEED, FLOP_HOR_SPEED), + z = math_random(-FLOP_HOR_SPEED, FLOP_HOR_SPEED), }) end @@ -2265,7 +555,141 @@ local follow_flop = function(self) end --- dogshoot attack switch and counter function +-- npc, find closest monster to attack +local npc_attack = function(self) + + if self.type ~= "npc" + or not self.attacks_monsters + or self.state == "attack" then + return + end + + local p, sp, obj, min_player + local s = self.object:get_pos() + local min_dist = self.view_range + 1 + local objs = minetest_get_objects_inside_radius(s, self.view_range) + + for n = 1, #objs do + + obj = objs[n]:get_luaentity() + + if obj and obj.type == "monster" then + + p = obj.object:get_pos() + sp = s + + local dist = vector.distance(p, s) + + -- aim higher to make looking up hills more realistic + p.y = p.y + 1 + sp.y = sp.y + 1 + + if dist < min_dist + and line_of_sight(self, sp, p, 2) == true then + min_dist = dist + min_player = obj.object + end + end + end + + if min_player then + do_attack(self, min_player) + end +end + + +-- monster find someone to attack +local monster_attack = function(self) + + if self.type ~= "monster" + or not damage_enabled + or minetest_is_creative_enabled("") + or self.passive + or self.state == "attack" + or day_docile(self) then + return + end + + local s = self.object:get_pos() + local p, sp, dist + local player, obj, min_player + local type, name = "", "" + local min_dist = self.view_range + 1 + local objs = minetest_get_objects_inside_radius(s, self.view_range) + + for n = 1, #objs do + + if objs[n]:is_player() then + + if mobs.invis[ objs[n]:get_player_name() ] or (not object_in_range(self, objs[n])) then + type = "" + else + player = objs[n] + type = "player" + name = "player" + end + else + obj = objs[n]:get_luaentity() + + if obj then + player = obj.object + type = obj.type + name = obj.name or "" + end + end + + -- find specific mob to attack, failing that attack player/npc/animal + if specific_attack(self.specific_attack, name) + and (type == "player" or type == "npc" + or (type == "animal" and self.attack_animals == true)) then + + p = player:get_pos() + sp = s + + dist = vector.distance(p, s) + + -- aim higher to make looking up hills more realistic + p.y = p.y + 1 + sp.y = sp.y + 1 + + + -- choose closest player to attack + if dist < min_dist + and line_of_sight(self, sp, p, 2) == true then + min_dist = dist + min_player = player + end + end + end + + -- attack player + if min_player then + do_attack(self, min_player) + end +end + + +-- specific attacks +local specific_attack = function(list, what) + + -- no list so attack default (player, animals etc.) + if list == nil then + return true + end + + -- found entity on list to attack? + for no = 1, #list do + + if list[no] == what then + return true + end + end + + return false +end + + +-- dogfight attack switch and counter function local dogswitch = function(self, dtime) -- switch mode not activated @@ -2293,19 +717,1577 @@ local dogswitch = function(self, dtime) return self.dogshoot_switch end --- execute current state (stand, walk, run, attacks) --- returns true if mob has died -local do_states = function(self, dtime) +-- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3 +local smart_mobs = function(self, s, p, dist, dtime) - local yaw = self.object:get_yaw() or 0 + local s1 = self.path.lastpos + + local target_pos = self.attack:get_pos() + + -- is it becoming stuck? + if math_abs(s1.x - s.x) + math_abs(s1.z - s.z) < .5 then + self.path.stuck_timer = self.path.stuck_timer + dtime + else + self.path.stuck_timer = 0 + end + + self.path.lastpos = {x = s.x, y = s.y, z = s.z} + + local use_pathfind = false + local has_lineofsight = minetest_line_of_sight( + {x = s.x, y = (s.y) + .5, z = s.z}, + {x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2) + + -- im stuck, search for path + if not has_lineofsight then + + if los_switcher == true then + use_pathfind = true + los_switcher = false + end -- cannot see target! + else + if los_switcher == false then + + los_switcher = true + use_pathfind = false + + minetest_after(1, function(self) + if not self.object:get_luaentity() then + return + end + if has_lineofsight then self.path.following = false end + end, self) + end -- can see target! + end + + if (self.path.stuck_timer > stuck_timeout and not self.path.following) then + + use_pathfind = true + self.path.stuck_timer = 0 + + minetest_after(1, function(self) + if not self.object:get_luaentity() then + return + end + if has_lineofsight then self.path.following = false end + end, self) + end + + if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then + + use_pathfind = true + self.path.stuck_timer = 0 + + minetest_after(1, function(self) + if not self.object:get_luaentity() then + return + end + if has_lineofsight then self.path.following = false end + end, self) + end + + if math_abs(vector.subtract(s,target_pos).y) > self.stepheight then + + if height_switcher then + use_pathfind = true + height_switcher = false + end + else + if not height_switcher then + use_pathfind = false + height_switcher = true + end + end + + if use_pathfind then + -- lets try find a path, first take care of positions + -- since pathfinder is very sensitive + local sheight = self.collisionbox[5] - self.collisionbox[2] + + -- round position to center of node to avoid stuck in walls + -- also adjust height for player models! + s.x = math_floor(s.x + 0.5) + s.z = math_floor(s.z + 0.5) + + local ssight, sground = minetest_line_of_sight(s, { + x = s.x, y = s.y - 4, z = s.z}, 1) + + -- determine node above ground + if not ssight then + s.y = sground.y + 1 + end + + local p1 = self.attack:get_pos() + + p1.x = math_floor(p1.x + 0.5) + p1.y = math_floor(p1.y + 0.5) + p1.z = math_floor(p1.z + 0.5) + + local dropheight = 12 + if self.fear_height ~= 0 then dropheight = self.fear_height end + local jumpheight = 0 + if self.jump and self.jump_height >= 4 then + jumpheight = math.min(math.ceil(self.jump_height / 4), 4) + elseif self.stepheight > 0.5 then + jumpheight = 1 + end + self.path.way = minetest_find_path(s, p1, 16, jumpheight, dropheight, "A*_noprefetch") + + self.state = "" + do_attack(self, self.attack) + + -- no path found, try something else + if not self.path.way then + + self.path.following = false + + -- lets make way by digging/building if not accessible + if self.pathfinding == 2 and mobs_griefing then + + -- is player higher than mob? + if s.y < p1.y then + + -- build upwards + if not minetest_is_protected(s, "") then + + local ndef1 = minetest_registered_nodes[self.standing_in] + + if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then + + minetest_set_node(s, {name = mobs.fallback_node}) + end + end + + local sheight = math.ceil(self.collisionbox[5]) + 1 + + -- assume mob is 2 blocks high so it digs above its head + s.y = s.y + sheight + + -- remove one block above to make room to jump + if not minetest_is_protected(s, "") then + + local node1 = node_ok(s, "air").name + local ndef1 = minetest_registered_nodes[node1] + + if node1 ~= "air" + and node1 ~= "ignore" + and ndef1 + and not ndef1.groups.level + and not ndef1.groups.unbreakable + and not ndef1.groups.liquid then + + minetest_set_node(s, {name = "air"}) + minetest_add_item(s, ItemStack(node1)) + + end + end + + s.y = s.y - sheight + self.object:set_pos({x = s.x, y = s.y + 2, z = s.z}) + + else -- dig 2 blocks to make door toward player direction + + local yaw1 = self.object:get_yaw() + math_pi / 2 + local p1 = { + x = s.x + math_cos(yaw1), + y = s.y, + z = s.z + math_sin(yaw1) + } + + if not minetest_is_protected(p1, "") then + + local node1 = node_ok(p1, "air").name + local ndef1 = minetest_registered_nodes[node1] + + if node1 ~= "air" + and node1 ~= "ignore" + and ndef1 + and not ndef1.groups.level + and not ndef1.groups.unbreakable + and not ndef1.groups.liquid then + + minetest_add_item(p1, ItemStack(node1)) + minetest_set_node(p1, {name = "air"}) + end + + p1.y = p1.y + 1 + node1 = node_ok(p1, "air").name + ndef1 = minetest_registered_nodes[node1] + + if node1 ~= "air" + and node1 ~= "ignore" + and ndef1 + and not ndef1.groups.level + and not ndef1.groups.unbreakable + and not ndef1.groups.liquid then + + minetest_add_item(p1, ItemStack(node1)) + minetest_set_node(p1, {name = "air"}) + end + + end + end + end + + -- will try again in 2 seconds + self.path.stuck_timer = stuck_timeout - 2 + elseif s.y < p1.y and (not self.fly) then + do_jump(self) --add jump to pathfinding + self.path.following = true + -- Yay, I found path! + -- TODO: Implement war_cry sound without being annoying + --mob_sound(self, "war_cry", true) + else + set_velocity(self, self.walk_velocity) + + -- follow path now that it has it + self.path.following = true + end + end +end + +local update_tag = function(self) + local tag + if mobs_debug then + tag = "nametag = '"..tostring(self.nametag).."'\n".. + "state = '"..tostring(self.state).."'\n".. + "order = '"..tostring(self.order).."'\n".. + "attack = "..tostring(self.attack).."\n".. + "health = "..tostring(self.health).."\n".. + "breath = "..tostring(self.breath).."\n".. + "gotten = "..tostring(self.gotten).."\n".. + "tamed = "..tostring(self.tamed).."\n".. + "horny = "..tostring(self.horny).."\n".. + "hornytimer = "..tostring(self.hornytimer).."\n".. + "runaway_timer = "..tostring(self.runaway_timer).."\n".. + "following = "..tostring(self.following) + else + tag = self.nametag + end + self.object:set_properties({ + nametag = tag, + }) + + update_roll(self) +end + +-- drop items +local item_drop = function(self, cooked, looting_level) + + -- no drops if disabled by setting + if not mobs_drop_items then return end + + looting_level = looting_level or 0 + + -- no drops for child mobs (except monster) + if (self.child and self.type ~= "monster") then + return + end + + local obj, item, num + local pos = self.object:get_pos() + + self.drops = self.drops or {} -- nil check + + for n = 1, #self.drops do + local dropdef = self.drops[n] + local chance = 1 / dropdef.chance + local looting_type = dropdef.looting + + if looting_level > 0 then + local chance_function = dropdef.looting_chance_function + if chance_function then + chance = chance_function(looting_level) + elseif looting_type == "rare" then + chance = chance + (dropdef.looting_factor or 0.01) * looting_level + end + end + + local num = 0 + local do_common_looting = (looting_level > 0 and looting_type == "common") + if math_random() < chance then + num = math_random(dropdef.min or 1, dropdef.max or 1) + elseif not dropdef.looting_ignore_chance then + do_common_looting = false + end + + if do_common_looting then + num = num + math_floor(math_random(0, looting_level) + 0.5) + end + + if num > 0 then + item = dropdef.name + + -- cook items when true + if cooked then + + local output = minetest_get_craft_result({ + method = "cooking", width = 1, items = {item}}) + + if output and output.item and not output.item:is_empty() then + item = output.item:get_name() + end + end + + -- add item if it exists + for x = 1, num do + obj = minetest_add_item(pos, ItemStack(item .. " " .. 1)) + end + + if obj and obj:get_luaentity() then + + obj:set_velocity({ + x = math_random(-10, 10) / 9, + y = 6, + z = math_random(-10, 10) / 9, + }) + elseif obj then + obj:remove() -- item does not exist + end + end + end + + self.drops = {} +end + + +-- check if mob is dead or only hurt +local check_for_death = function(self, cause, cmi_cause) + + if self.state == "die" then + return true + end + + -- has health actually changed? + if self.health == self.old_health and self.health > 0 then + return false + end + + local damaged = self.health < self.old_health + self.old_health = self.health + + -- still got some health? + if self.health > 0 then + + -- make sure health isn't higher than max + if self.health > self.hp_max then + self.health = self.hp_max + end + + -- play damage sound if health was reduced and make mob flash red. + if damaged then + add_texture_mod(self, "^[colorize:red:130") + minetest_after(.2, function(self) + if self and self.object then + remove_texture_mod(self, "^[colorize:red:130") + end + end, self) + mob_sound(self, "damage") + end + + -- backup nametag so we can show health stats + if not self.nametag2 then + self.nametag2 = self.nametag or "" + end + + if show_health + and (cmi_cause and cmi_cause.type == "punch") then + + self.htimer = 2 + self.nametag = "♥ " .. self.health .. " / " .. self.hp_max + + update_tag(self) + end + + return false + end + + mob_sound(self, "death") + + local function death_handle(self) + -- dropped cooked item if mob died in fire or lava + if cause == "lava" or cause == "fire" then + item_drop(self, true, 0) + else + local wielditem = ItemStack() + if cause == "hit" then + local puncher = cmi_cause.puncher + if puncher then + wielditem = puncher:get_wielded_item() + end + end + local cooked = mcl_burning.is_burning(self.object) or mcl_enchanting.has_enchantment(wielditem, "fire_aspect") + local looting = mcl_enchanting.get_enchantment(wielditem, "looting") + item_drop(self, cooked, looting) + + if mod_experience and ((not self.child) or self.type ~= "animal") and (minetest_get_us_time() - self.xp_timestamp <= 5000000) then + mcl_experience.throw_experience(self.object:get_pos(), math_random(self.xp_min, self.xp_max)) + end + end + end + + -- execute custom death function + if self.on_die then + + local pos = self.object:get_pos() + local on_die_exit = self.on_die(self, pos, cmi_cause) + if on_die_exit ~= true then + death_handle(self) + end + + if use_cmi then + cmi.notify_die(self.object, cmi_cause) + end + + if on_die_exit == true then + self.state = "die" + mcl_burning.extinguish(self.object) + self.object:remove() + return true + end + end + + local collisionbox + if self.collisionbox then + collisionbox = table.copy(self.collisionbox) + end + + self.state = "die" + self.attack = nil + self.v_start = false + self.fall_speed = DEFAULT_FALL_SPEED + self.timer = 0 + self.blinktimer = 0 + remove_texture_mod(self, "^[colorize:#FF000040") + remove_texture_mod(self, "^[brighten") + self.passive = true + + self.object:set_properties({ + pointable = false, + collide_with_objects = false, + }) + + set_velocity(self, 0) + local acc = self.object:get_acceleration() + acc.x, acc.y, acc.z = 0, DEFAULT_FALL_SPEED, 0 + self.object:set_acceleration(acc) + + local length + -- default death function and die animation (if defined) + if self.instant_death then + length = 0 + elseif self.animation + and self.animation.die_start + and self.animation.die_end then + + local frames = self.animation.die_end - self.animation.die_start + local speed = self.animation.die_speed or 15 + length = max(frames / speed, 0) + DEATH_DELAY + set_animation(self, "die") + else + local rot = self.object:get_rotation() + rot.z = math_pi/2 + self.object:set_rotation(rot) + length = 1 + DEATH_DELAY + set_animation(self, "stand", true) + end + + + -- Remove body after a few seconds and drop stuff + local kill = function(self) + if not self.object:get_luaentity() then + return + end + if use_cmi then + cmi.notify_die(self.object, cmi_cause) + end + + death_handle(self) + local dpos = self.object:get_pos() + local cbox = self.collisionbox + local yaw = self.object:get_rotation().y + mcl_burning.extinguish(self.object) + self.object:remove() + mobs.death_effect(dpos, yaw, cbox, not self.instant_death) + end + if length <= 0 then + kill(self) + else + minetest_after(length, kill, self) + end + + return true +end + +local damage_effect = function(self, damage) + -- damage particles + if (not disable_blood) and damage > 0 then + + local amount_large = math_floor(damage / 2) + local amount_small = damage % 2 + + local pos = self.object:get_pos() + + pos.y = pos.y + (self.collisionbox[5] - self.collisionbox[2]) * .5 + + local texture = "mobs_blood.png" + -- full heart damage (one particle for each 2 HP damage) + if amount_large > 0 then + effect(pos, amount_large, texture, 2, 2, 1.75, 0, nil, true) + end + -- half heart damage (one additional particle if damage is an odd number) + if amount_small > 0 then + -- TODO: Use "half heart" + effect(pos, amount_small, texture, 1, 1, 1.75, 0, nil, true) + end + end +end + + +-- custom particle effects +local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow, go_down) + + radius = radius or 2 + min_size = min_size or 0.5 + max_size = max_size or 1 + gravity = gravity or -10 + glow = glow or 0 + go_down = go_down or false + + local ym + if go_down then + ym = 0 + else + ym = -radius + end + + minetest_add_particlespawner({ + amount = amount, + time = 0.25, + minpos = pos, + maxpos = pos, + minvel = {x = -radius, y = ym, z = -radius}, + maxvel = {x = radius, y = radius, z = radius}, + minacc = {x = 0, y = gravity, z = 0}, + maxacc = {x = 0, y = gravity, z = 0}, + minexptime = 0.1, + maxexptime = 1, + minsize = min_size, + maxsize = max_size, + texture = texture, + glow = glow, + }) +end + + +-- are we flying in what we are suppose to? (taikedz) +local flight_check = function(self) + + local nod = self.standing_in + local def = minetest_registered_nodes[nod] + + if not def then return false end -- nil check + + local fly_in + if type(self.fly_in) == "string" then + fly_in = { self.fly_in } + elseif type(self.fly_in) == "table" then + fly_in = self.fly_in + else + return false + end + + for _,checknode in pairs(fly_in) do + if nod == checknode then + return true + elseif checknode == "__airlike" and def.walkable == false and + (def.liquidtype == "none" or minetest_get_item_group(nod, "fake_liquid") == 1) then + return true + end + end + + return false +end + + +-- check line of sight (BrunoMine) +local line_of_sight = function(self, pos1, pos2, stepsize) + + stepsize = stepsize or 1 + + local s, pos = minetest_line_of_sight(pos1, pos2, stepsize) + + -- normal walking and flying mobs can see you through air + if s == true then + return true + end + + -- New pos1 to be analyzed + local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z} + + local r, pos = minetest_line_of_sight(npos1, pos2, stepsize) + + -- Checks the return + if r == true then return true end + + -- Nodename found + local nn = minetest_get_node(pos).name + + -- Target Distance (td) to travel + local td = vector.distance(pos1, pos2) + + -- Actual Distance (ad) traveled + local ad = 0 + + -- It continues to advance in the line of sight in search of a real + -- obstruction which counts as 'normal' nodebox. + while minetest_registered_nodes[nn] + and minetest_registered_nodes[nn].walkable == false do + + -- Check if you can still move forward + if td < ad + stepsize then + return true -- Reached the target + end + + -- Moves the analyzed pos + local d = vector.distance(pos1, pos2) + + npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x + npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y + npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z + + -- NaN checks + if d == 0 + or npos1.x ~= npos1.x + or npos1.y ~= npos1.y + or npos1.z ~= npos1.z then + return false + end + + ad = ad + stepsize + + -- scan again + r, pos = minetest_line_of_sight(npos1, pos2, stepsize) + + if r == true then return true end + + -- New Nodename found + nn = minetest_get_node(pos).name + + end + + return false +end + +-- Returns true if node is a water hazard +local is_node_waterhazard = function(self, nodename) + local nn = nodename + if self.water_damage > 0 then + if minetest_get_item_group(nn, "water") ~= 0 then + return true + end + end + if minetest_registered_nodes[nn] and minetest_registered_nodes[nn].drowning and minetest_registered_nodes[nn].drowning > 0 then + if self.breath_max ~= -1 then + -- check if the mob is water-breathing _and_ the block is water; only return true if neither is the case + -- this will prevent water-breathing mobs to classify water or e.g. sand below them as dangerous + if not self.breathes_in_water and minetest_get_item_group(nn, "water") ~= 0 then + return true + end + end + end + return false +end + + +-- Returns true is node can deal damage to self +local is_node_dangerous = function(self, nodename) + local nn = nodename + if self.lava_damage > 0 then + if minetest_get_item_group(nn, "lava") ~= 0 then + return true + end + end + if self.fire_damage > 0 then + if minetest_get_item_group(nn, "fire") ~= 0 then + return true + end + end + if minetest_registered_nodes[nn] and minetest_registered_nodes[nn].damage_per_second and minetest_registered_nodes[nn].damage_per_second > 0 then + return true + end + return false +end + + +local add_texture_mod = function(self, mod) + local full_mod = "" + local already_added = false + for i=1, #self.texture_mods do + if mod == self.texture_mods[i] then + already_added = true + end + full_mod = full_mod .. self.texture_mods[i] + end + if not already_added then + full_mod = full_mod .. mod + table.insert(self.texture_mods, mod) + end + self.object:set_texture_mod(full_mod) +end + + +local remove_texture_mod = function(self, mod) + local full_mod = "" + local remove = {} + for i=1, #self.texture_mods do + if self.texture_mods[i] ~= mod then + full_mod = full_mod .. self.texture_mods[i] + else + table.insert(remove, i) + end + end + for i=#remove, 1 do + table.remove(self.texture_mods, remove[i]) + end + self.object:set_texture_mod(full_mod) +end + + +-- Return true if object is in view_range +local function object_in_range(self, object) + if not object then + return false + end + local factor + -- Apply view range reduction for special player armor + if object:is_player() and mod_armor then + factor = armor:get_mob_view_range_factor(object, self.name) + end + -- Distance check + local dist + if factor and factor == 0 then + return false + elseif factor then + dist = self.view_range * factor + else + dist = self.view_range + end + + local p1, p2 = self.object:get_pos(), object:get_pos() + return p1 and p2 and (vector.distance(p1, p2) <= dist) +end + +-- attack player/mob +local do_attack = function(self, player) + + if self.state == "attack" or self.state == "die" then + return + end + + self.attack = player + self.state = "attack" + + -- TODO: Implement war_cry sound without being annoying + --if math_random(0, 100) < 90 then + --mob_sound(self, "war_cry", true) + --end +end + + +-- play sound +local mob_sound = function(self, soundname, is_opinion, fixed_pitch) + local soundinfo + if self.sounds_child and self.child then + soundinfo = self.sounds_child + elseif self.sounds then + soundinfo = self.sounds + end + if not soundinfo then + return + end + local sound = soundinfo[soundname] + if sound then + if is_opinion and self.opinion_sound_cooloff > 0 then + return + end + local pitch + if not fixed_pitch then + local base_pitch = soundinfo.base_pitch + if not base_pitch then + base_pitch = 1 + end + if self.child and (not self.sounds_child) then + -- Children have higher pitch + pitch = base_pitch * 1.5 + else + pitch = base_pitch + end + -- randomize the pitch a bit + pitch = pitch + math_random(-10, 10) * 0.005 + end + minetest_sound_play(sound, { + object = self.object, + gain = 1.0, + max_hear_distance = self.sounds.distance, + pitch = pitch, + }, true) + self.opinion_sound_cooloff = 1 + end +end + + +local function update_roll(self) + local is_Fleckenstein = self.nametag == "Fleckenstein" + local was_Fleckenstein = false + + local rot = self.object:get_rotation() + rot.z = is_Fleckenstein and math_pi or 0 + self.object:set_rotation(rot) + + local cbox = table.copy(self.collisionbox) + local acbox = self.object:get_properties().collisionbox + + if math_abs(cbox[2] - acbox[2]) > 0.1 then + was_Fleckenstein = true + end + + if is_Fleckenstein ~= was_Fleckenstein then + local pos = self.object:get_pos() + pos.y = pos.y + (acbox[2] + acbox[5]) + self.object:set_pos(pos) + end + + if is_Fleckenstein then + cbox[2], cbox[5] = -cbox[5], -cbox[2] + end + + self.object:set_properties({collisionbox = cbox}) +end + + + +-- is mob facing a cliff or danger +local is_at_cliff_or_danger = function(self) + + if self.fear_height == 0 then -- 0 for no falling protection! + return false + end + + if not self.object:get_luaentity() then + return false + end + local yaw = self.object:get_yaw() + local dir_x = -math_sin(yaw) * (self.collisionbox[4] + 0.5) + local dir_z = math_cos(yaw) * (self.collisionbox[4] + 0.5) + local pos = self.object:get_pos() + local ypos = pos.y + self.collisionbox[2] -- just above floor + + local free_fall, blocker = minetest_line_of_sight( + {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z}, + {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}) + if free_fall then + return true + else + local bnode = minetest_get_node(blocker) + local danger = is_node_dangerous(self, bnode.name) + if danger then + return true + else + local def = minetest_registered_nodes[bnode.name] + if def and def.walkable then + return false + end + end + end + + return false +end + + +-- copy the 'mob facing cliff_or_danger check' from above, and rework to avoid water +local is_at_water_danger = function(self) + + + if not self.object:get_luaentity() then + return false + end + local yaw = self.object:get_yaw() + local dir_x = -math_sin(yaw) * (self.collisionbox[4] + 0.5) + local dir_z = math_cos(yaw) * (self.collisionbox[4] + 0.5) + local pos = self.object:get_pos() + local ypos = pos.y + self.collisionbox[2] -- just above floor + + local free_fall, blocker = minetest_line_of_sight( + {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z}, + {x = pos.x + dir_x, y = ypos - 3, z = pos.z + dir_z}) + if free_fall then + return true + else + local bnode = minetest_get_node(blocker) + local waterdanger = is_node_waterhazard(self, bnode.name) + if + waterdanger and (is_node_waterhazard(self, self.standing_in) or is_node_waterhazard(self, self.standing_on)) then + return false + elseif waterdanger and (is_node_waterhazard(self, self.standing_in) or is_node_waterhazard(self, self.standing_on)) == false then + return true + else + local def = minetest_registered_nodes[bnode.name] + if def and def.walkable then + return false + end + end + end + + return false +end + + + + + +-- environmental damage (water, lava, fire, light etc.) +local do_env_damage = function(self) + + -- feed/tame text timer (so mob 'full' messages dont spam chat) + if self.htimer > 0 then + self.htimer = self.htimer - 1 + end + + -- reset nametag after showing health stats + if self.htimer < 1 and self.nametag2 then + + self.nametag = self.nametag2 + self.nametag2 = nil + + update_tag(self) + end + + local pos = self.object:get_pos() + + self.time_of_day = minetest.get_timeofday() + + -- remove mob if beyond map limits + if not within_limits(pos, 0) then + mcl_burning.extinguish(self.object) + self.object:remove() + return true + end + + + -- Deal light damage to mob, returns true if mob died + local deal_light_damage = function(self, pos, damage) + if not (mod_weather and (mcl_weather.rain.raining or mcl_weather.state == "snow") and mcl_weather.is_outdoor(pos)) then + self.health = self.health - damage + + effect(pos, 5, "mcl_particles_smoke.png") + + if check_for_death(self, "light", {type = "light"}) then + return true + end + end + end + + -- Use get_node_light for Minetest version 5.3 where get_natural_light + -- does not exist yet. + local get_light = minetest_get_natural_light or minetest_get_node_light + local sunlight = get_light(pos, self.time_of_day) + + -- bright light harms mob + if self.light_damage ~= 0 and (sunlight or 0) > 12 then + if deal_light_damage(self, pos, self.light_damage) then + return true + end + end + local _, dim = nil, "overworld" + if mod_worlds then + _, dim = mcl_worlds.y_to_layer(pos.y) + end + if (self.sunlight_damage ~= 0 or self.ignited_by_sunlight) and (sunlight or 0) >= minetest.LIGHT_MAX and dim == "overworld" then + if self.ignited_by_sunlight then + mcl_burning.set_on_fire(self.object, 10) + else + deal_light_damage(self, pos, self.sunlight_damage) + return true + end + end + + local y_level = self.collisionbox[2] + + if self.child then + y_level = self.collisionbox[2] * 0.5 + end + + -- what is mob standing in? + pos.y = pos.y + y_level + 0.25 -- foot level + local pos2 = {x=pos.x, y=pos.y-1, z=pos.z} + self.standing_in = node_ok(pos, "air").name + self.standing_on = node_ok(pos2, "air").name + + -- don't fall when on ignore, just stand still + if self.standing_in == "ignore" then + self.object:set_velocity({x = 0, y = 0, z = 0}) + end + + local nodef = minetest_registered_nodes[self.standing_in] + + -- rain + if self.rain_damage > 0 and mod_weather then + if mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then + + self.health = self.health - self.rain_damage + + if check_for_death(self, "rain", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + end + + pos.y = pos.y + 1 -- for particle effect position + + -- water damage + if self.water_damage > 0 + and nodef.groups.water then + + if self.water_damage ~= 0 then + + self.health = self.health - self.water_damage + + effect(pos, 5, "mcl_particles_smoke.png", nil, nil, 1, nil) + + if check_for_death(self, "water", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + + -- lava damage + elseif self.lava_damage > 0 + and (nodef.groups.lava) then + + if self.lava_damage ~= 0 then + + self.health = self.health - self.lava_damage + + effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil) + + if check_for_death(self, "lava", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + + -- fire damage + elseif self.fire_damage > 0 + and (nodef.groups.fire) then + + if self.fire_damage ~= 0 then + + self.health = self.health - self.fire_damage + + effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil) + + if check_for_death(self, "fire", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + + -- damage_per_second node check + elseif nodef.damage_per_second ~= 0 and not nodef.groups.lava and not nodef.groups.fire then + + self.health = self.health - nodef.damage_per_second + + effect(pos, 5, "mcl_particles_smoke.png") + + if check_for_death(self, "dps", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + + -- Drowning damage + if self.breath_max ~= -1 then + local drowning = false + if self.breathes_in_water then + if minetest_get_item_group(self.standing_in, "water") == 0 then + drowning = true + end + elseif nodef.drowning > 0 then + drowning = true + end + if drowning then + + self.breath = math_max(0, self.breath - 1) + + effect(pos, 2, "bubble.png", nil, nil, 1, nil) + if self.breath <= 0 then + local dmg + if nodef.drowning > 0 then + dmg = nodef.drowning + else + dmg = 4 + end + damage_effect(self, dmg) + self.health = self.health - dmg + end + if check_for_death(self, "drowning", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + else + self.breath = math_min(self.breath_max, self.breath + 1) + end + end + + --- suffocation inside solid node + -- FIXME: Redundant with mcl_playerplus + if (self.suffocation == true) + and (nodef.walkable == nil or nodef.walkable == true) + and (nodef.collision_box == nil or nodef.collision_box.type == "regular") + and (nodef.node_box == nil or nodef.node_box.type == "regular") + and (nodef.groups.disable_suffocation ~= 1) + and (nodef.groups.opaque == 1) then + + -- Short grace period before starting to take suffocation damage. + -- This is different from players, who take damage instantly. + -- This has been done because mobs might briefly be inside solid nodes + -- when e.g. climbing up stairs. + -- This is a bit hacky because it assumes that do_env_damage + -- is called roughly every second only. + self.suffocation_timer = self.suffocation_timer + 1 + if self.suffocation_timer >= 3 then + -- 2 damage per second + -- TODO: Deal this damage once every 1/2 second + self.health = self.health - 2 + + if check_for_death(self, "suffocation", {type = "environment", + pos = pos, node = self.standing_in}) then + return true + end + end + else + self.suffocation_timer = 0 + end + + return check_for_death(self, "", {type = "unknown"}) +end + + +-- jump if facing a solid node (not fences or gates) +local do_jump = function(self) + + if not self.jump + or self.jump_height == 0 + or self.fly + or (self.child and self.type ~= "monster") + or self.order == "stand" then + return false + end + + self.facing_fence = false + + -- something stopping us while moving? + if self.state ~= "stand" + and get_velocity(self) > 0.5 + and self.object:get_velocity().y ~= 0 then + return false + end + + local pos = self.object:get_pos() + local yaw = self.object:get_yaw() + + -- what is mob standing on? + pos.y = pos.y + self.collisionbox[2] - 0.2 + + local nod = node_ok(pos) + + if minetest_registered_nodes[nod.name].walkable == false then + return false + end + + -- where is front + local dir_x = -math_sin(yaw) * (self.collisionbox[4] + 0.5) + local dir_z = math_cos(yaw) * (self.collisionbox[4] + 0.5) + + -- what is in front of mob? + nod = node_ok({ + x = pos.x + dir_x, + y = pos.y + 0.5, + z = pos.z + dir_z + }) + + -- this is used to detect if there's a block on top of the block in front of the mob. + -- If there is, there is no point in jumping as we won't manage. + local nodTop = node_ok({ + x = pos.x + dir_x, + y = pos.y + 1.5, + z = pos.z + dir_z + }, "air") + + -- we don't attempt to jump if there's a stack of blocks blocking + if minetest_registered_nodes[nodTop.name].walkable == true then + return false + end + + -- thin blocks that do not need to be jumped + if nod.name == node_snow then + return false + end + + if self.walk_chance == 0 + or minetest_registered_items[nod.name].walkable then + + if minetest_get_item_group(nod.name, "fence") == 0 + and minetest_get_item_group(nod.name, "fence_gate") == 0 + and minetest_get_item_group(nod.name, "wall") == 0 then + + local v = self.object:get_velocity() + + v.y = self.jump_height + + set_animation(self, "jump") -- only when defined + + self.object:set_velocity(v) + + -- when in air move forward + minetest_after(0.3, function(self, v) + if (not self.object) or (not self.object:get_luaentity()) or (self.state == "die") then + return + end + self.object:set_acceleration({ + x = v.x * 2, + y = -10, + z = v.z * 2, + }) + end, self, v) + + if self.jump_sound_cooloff <= 0 then + mob_sound(self, "jump") + self.jump_sound_cooloff = 0.5 + end + else + self.facing_fence = true + end + + -- if we jumped against a block/wall 4 times then turn + if self.object:get_velocity().x ~= 0 + and self.object:get_velocity().z ~= 0 then + + self.jump_count = (self.jump_count or 0) + 1 + + if self.jump_count == 4 then + + local yaw = self.object:get_yaw() or 0 + + yaw = set_yaw(self, yaw + 1.35, 8) + + self.jump_count = 0 + end + end + + return true + end + + return false +end + + +-- blast damage to entities nearby +local entity_physics = function(pos, radius) + + radius = radius * 2 + + local objs = minetest_get_objects_inside_radius(pos, radius) + local obj_pos, dist + + for n = 1, #objs do + + obj_pos = objs[n]:get_pos() + + dist = vector.distance(pos, obj_pos) + if dist < 1 then dist = 1 end + + local damage = math_floor((4 / dist) * radius) + local ent = objs[n]:get_luaentity() + + -- punches work on entities AND players + objs[n]:punch(objs[n], 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = damage}, + }, pos) + end +end + + +-- should mob follow what I'm holding ? +local follow_holding = function(self, clicker) + + if mobs.invis[clicker:get_player_name()] then + return false + end + + local item = clicker:get_wielded_item() + local t = type(self.follow) + + -- single item + if t == "string" + and item:get_name() == self.follow then + return true + + -- multiple items + elseif t == "table" then + + for no = 1, #self.follow do + + if self.follow[no] == item:get_name() then + return true + end + end + end + + return false +end + + +-- find two animals of same type and breed if nearby and horny +local breed = function(self) + + -- child takes a long time before growing into adult + if self.child == true then + + -- When a child, hornytimer is used to count age until adulthood + self.hornytimer = self.hornytimer + 1 + + if self.hornytimer >= CHILD_GROW_TIME 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, + selectionbox = self.base_selbox, + }) + + -- custom function when child grows up + if self.on_grown then + self.on_grown(self) + else + -- jump when fully grown so as not to fall into ground + self.object:set_velocity({ + x = 0, + y = self.jump_height, + z = 0 + }) + end + end + + return + end + + -- horny animal can mate for BREED_TIME seconds, + -- afterwards horny animal cannot mate again for BREED_TIME_AGAIN seconds + if self.horny == true + and self.hornytimer < BREED_TIME + BREED_TIME_AGAIN then + + self.hornytimer = self.hornytimer + 1 + + if self.hornytimer >= BREED_TIME + BREED_TIME_AGAIN then + self.hornytimer = 0 + self.horny = false + end + end + + -- find another same animal who is also horny and mate if nearby + if self.horny == true + and self.hornytimer <= BREED_TIME then + + local pos = self.object:get_pos() + + effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1) + + local objs = minetest_get_objects_inside_radius(pos, 3) + local num = 0 + local ent = nil + + for n = 1, #objs do + + ent = objs[n]:get_luaentity() + + -- check for same animal with different colour + local canmate = false + + if ent then + + if ent.name == self.name then + canmate = true + else + local entname = 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 <= BREED_TIME then + num = num + 1 + end + + -- found your mate? then have a baby + if num > 1 then + + self.hornytimer = BREED_TIME + 1 + ent.hornytimer = BREED_TIME + 1 + + -- spawn baby + minetest_after(5, function(parent1, parent2, pos) + if not parent1.object:get_luaentity() then + return + end + if not parent2.object:get_luaentity() then + return + end + + -- Give XP + if mod_experience then + mcl_experience.throw_experience(pos, math_random(1, 7)) + end + + -- custom breed function + if parent1.on_breed then + -- when false, skip going any further + if parent1.on_breed(parent1, parent2) == false then + return + end + end + + local child = mobs:spawn_child(pos, parent1.name) + + local ent_c = child:get_luaentity() + + + -- Use texture of one of the parents + local p = math_random(1, 2) + if p == 1 then + ent_c.base_texture = parent1.base_texture + else + ent_c.base_texture = parent2.base_texture + end + child:set_properties({ + textures = ent_c.base_texture + }) + + -- tamed and owned by parents' owner + ent_c.tamed = true + ent_c.owner = parent1.owner + end, self, ent, pos) + + num = 0 + + break + end + end + end +end + +-- find and replace what mob is looking for (grass, wheat etc.) +local replace = function(self, pos) + + if not self.replace_rate + or not self.replace_what + or self.child == true + or self.object:get_velocity().y ~= 0 + or math_random(1, self.replace_rate) > 1 then + return + end + + local what, with, y_offset + + if type(self.replace_what[1]) == "table" then + + local num = math_random(#self.replace_what) + + what = self.replace_what[num][1] or "" + with = self.replace_what[num][2] or "" + y_offset = self.replace_what[num][3] or 0 + else + what = self.replace_what + with = self.replace_with or "" + y_offset = self.replace_offset or 0 + end + + pos.y = pos.y + y_offset + + local node = minetest_get_node(pos) + if node.name == what then + + local oldnode = {name = what, param2 = node.param2} + local newnode = {name = with, param2 = node.param2} + local on_replace_return + + if self.on_replace then + on_replace_return = self.on_replace(self, pos, oldnode, newnode) + end + + if on_replace_return ~= false then + + if mobs_griefing then + minetest_set_node(pos, newnode) + end + + end + end +end + + +-- check if daytime and also if mob is docile during daylight hours +local day_docile = function(self) + + if self.docile_by_day == false then + + return false + + elseif self.docile_by_day == true + and self.time_of_day > 0.2 + and self.time_of_day < 0.8 then + + return true + end +end + + + +local mob_detach_child = function(self, child) + + if self.driver == child then + self.driver = nil + end + +end + +function do_states(self) if self.state == "stand" then - if random(1, 4) == 1 then + if math_random(1, 4) == 1 then local lp = nil local s = self.object:get_pos() - local objs = minetest.get_objects_inside_radius(s, 3) + local objs = minetest_get_objects_inside_radius(s, 3) for n = 1, #objs do @@ -2323,11 +2305,11 @@ local do_states = function(self, dtime) z = lp.z - s.z } - yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate + yaw = (atan(vec.z / vec.x) + math_pi / 2) - self.rotate - if lp.x > s.x then yaw = yaw + pi end + if lp.x > s.x then yaw = yaw + math_pi end else - yaw = yaw + random(-0.5, 0.5) + yaw = yaw + math_random(-0.5, 0.5) end yaw = set_yaw(self, yaw, 8) @@ -2342,7 +2324,7 @@ local do_states = function(self, dtime) if self.walk_chance ~= 0 and self.facing_fence ~= true - and random(1, 100) <= self.walk_chance + and math_random(1, 100) <= self.walk_chance and is_at_cliff_or_danger(self) == false then set_velocity(self, self.walk_velocity) @@ -2361,19 +2343,19 @@ local do_states = function(self, dtime) and self.lava_damage > 0) or self.breath_max ~= -1 then - lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"}) + lp = minetest_find_node_near(s, 1, {"group:water", "group:lava"}) elseif self.water_damage > 0 then - lp = minetest.find_node_near(s, 1, {"group:water"}) + lp = minetest_find_node_near(s, 1, {"group:water"}) elseif self.lava_damage > 0 then - lp = minetest.find_node_near(s, 1, {"group:lava"}) + lp = minetest_find_node_near(s, 1, {"group:lava"}) elseif self.fire_damage > 0 then - lp = minetest.find_node_near(s, 1, {"group:fire"}) + lp = minetest_find_node_near(s, 1, {"group:fire"}) end @@ -2387,12 +2369,12 @@ local do_states = function(self, dtime) -- If mob in or on dangerous block, look for land if is_in_danger then -- Better way to find shore - copied from upstream - lp = minetest.find_nodes_in_area_under_air( + lp = minetest_find_nodes_in_area_under_air( {x = s.x - 5, y = s.y - 0.5, z = s.z - 5}, {x = s.x + 5, y = s.y + 1, z = s.z + 5}, {"group:solid"}) - lp = #lp > 0 and lp[random(#lp)] + lp = #lp > 0 and lp[math_random(#lp)] -- did we find land? if lp then @@ -2402,10 +2384,10 @@ local do_states = function(self, dtime) z = lp.z - s.z } - yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate + yaw = (atan(vec.z / vec.x) + math_pi / 2) - self.rotate - if lp.x > s.x then yaw = yaw + pi end + if lp.x > s.x then yaw = yaw + math_pi end -- look towards land and move in that direction yaw = set_yaw(self, yaw, 6) @@ -2418,8 +2400,8 @@ local do_states = function(self, dtime) else -- Randomly turn - if random(1, 100) <= 30 then - yaw = yaw + random(-0.5, 0.5) + if math_random(1, 100) <= 30 then + yaw = yaw + math_random(-0.5, 0.5) yaw = set_yaw(self, yaw, 8) end end @@ -2427,9 +2409,9 @@ local do_states = function(self, dtime) yaw = set_yaw(self, yaw, 8) -- otherwise randomly turn - elseif random(1, 100) <= 30 then + elseif math_random(1, 100) <= 30 then - yaw = yaw + random(-0.5, 0.5) + yaw = yaw + math_random(-0.5, 0.5) yaw = set_yaw(self, yaw, 8) end @@ -2440,7 +2422,7 @@ local do_states = function(self, dtime) end if self.facing_fence == true or cliff_or_danger - or random(1, 100) <= 30 then + or math_random(1, 100) <= 30 then set_velocity(self, 0) self.state = "stand" @@ -2515,9 +2497,9 @@ local do_states = function(self, dtime) z = p.z - s.z } - yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate + yaw = (atan(vec.z / vec.x) + math_pi / 2) - self.rotate - if p.x > s.x then yaw = yaw + pi end + if p.x > s.x then yaw = yaw + math_pi end yaw = set_yaw(self, yaw, 0, dtime) @@ -2583,10 +2565,10 @@ local do_states = function(self, dtime) local pos = self.object:get_pos() if mod_explosions then - if mobs_griefing and not minetest.is_protected(pos, "") then + if mobs_griefing and not minetest_is_protected(pos, "") then mcl_explosions.explode(mcl_util.get_object_center(self.object), self.explosion_strength, { drop_chance = 1.0 }, self.object) else - minetest.sound_play(self.sounds.explode, { + minetest_sound_play(self.sounds.explode, { pos = pos, gain = 1.0, max_hear_distance = self.sounds.distance or 32 @@ -2611,9 +2593,9 @@ local do_states = function(self, dtime) and dist > self.reach then local p1 = s - local me_y = floor(p1.y) + local me_y = math_floor(p1.y) local p2 = p - local p_y = floor(p2.y + 1) + local p_y = math_floor(p2.y + 1) local v = self.object:get_velocity() if flight_check(self, s) then @@ -2674,7 +2656,7 @@ local do_states = function(self, dtime) return end - if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then + if math_abs(p1.x-s.x) + math_abs(p1.z - s.z) < 0.6 then -- reached waypoint, remove it from queue table.remove(self.path.way, 1) end @@ -2688,9 +2670,9 @@ local do_states = function(self, dtime) z = p.z - s.z } - yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate + yaw = (atan(vec.z / vec.x) + math_pi / 2) - self.rotate - if p.x > s.x then yaw = yaw + pi end + if p.x > s.x then yaw = yaw + math_pi end yaw = set_yaw(self, yaw, 0, dtime) @@ -2740,7 +2722,7 @@ local do_states = function(self, dtime) self.timer = 0 if self.double_melee_attack - and random(1, 2) == 1 then + and math_random(1, 2) == 1 then set_animation(self, "punch2") else set_animation(self, "punch") @@ -2793,9 +2775,9 @@ local do_states = function(self, dtime) z = p.z - s.z } - yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate + yaw = (atan(vec.z / vec.x) + math_pi / 2) - self.rotate - if p.x > s.x then yaw = yaw + pi end + if p.x > s.x then yaw = yaw + math_pi end yaw = set_yaw(self, yaw, 0, dtime) @@ -2806,8 +2788,8 @@ local do_states = function(self, dtime) if self.shoot_interval and self.timer > self.shoot_interval - and not minetest.raycast(p, self.attack:get_pos(), false, false):next() - and random(1, 100) <= 60 then + and not minetest_raycast(p, self.attack:get_pos(), false, false):next() + and math_random(1, 100) <= 60 then self.timer = 0 set_animation(self, "shoot") @@ -2816,16 +2798,16 @@ local do_states = function(self, dtime) mob_sound(self, "shoot_attack") -- Shoot arrow - if minetest.registered_entities[self.arrow] then + if minetest_registered_entities[self.arrow] then local arrow, ent local v = 1 if not self.shoot_arrow then self.firing = true - minetest.after(1, function() + minetest_after(1, function() self.firing = false end) - arrow = minetest.add_entity(p, self.arrow) + arrow = minetest_add_entity(p, self.arrow) ent = arrow:get_luaentity() if ent.velocity then v = ent.velocity @@ -2853,852 +2835,87 @@ local do_states = function(self, dtime) end --- falling and fall damage --- returns true if mob died -local falling = function(self, pos) - - if self.fly and self.state ~= "die" then - return - end - - if mcl_portals ~= nil then - if mcl_portals.nether_portal_cooloff(self.object) then - return false -- mob has teleported through Nether portal - it's 99% not falling - end - end - - -- floating in water (or falling) - local v = self.object:get_velocity() - - if v.y > 0 then - - -- apply gravity when moving up - self.object:set_acceleration({ - x = 0, - y = -10, - z = 0 - }) - - elseif v.y <= 0 and v.y > self.fall_speed then - - -- fall downwards at set speed - self.object:set_acceleration({ - x = 0, - y = self.fall_speed, - z = 0 - }) - else - -- stop accelerating once max fall speed hit - self.object:set_acceleration({x = 0, y = 0, z = 0}) - end - - if minetest.registered_nodes[node_ok(pos).name].groups.lava then - - if self.floats_on_lava == 1 then - - self.object:set_acceleration({ - x = 0, - y = -self.fall_speed / (max(1, v.y) ^ 2), - z = 0 - }) - end - end - - -- in water then float up - if minetest.registered_nodes[node_ok(pos).name].groups.water then - - if self.floats == 1 then - - self.object:set_acceleration({ - x = 0, - y = -self.fall_speed / (max(1, v.y) ^ 2), - z = 0 - }) - end - else - - -- fall damage onto solid ground - if self.fall_damage == 1 - and self.object:get_velocity().y == 0 then - - local d = (self.old_y or 0) - self.object:get_pos().y - - if d > 5 then - - local add = minetest.get_item_group(self.standing_on, "fall_damage_add_percent") - local damage = d - 5 - if add ~= 0 then - damage = damage + damage * (add/100) - end - damage = floor(damage) - if damage > 0 then - self.health = self.health - damage - - effect(pos, 5, "mcl_particles_smoke.png", 1, 2, 2, nil) - - if check_for_death(self, "fall", {type = "fall"}) then - return true - end - end - end - - self.old_y = self.object:get_pos().y - end - end -end - -local teleport = function(self, target) - if self.do_teleport then - if self.do_teleport(self, target) == false then - return - end - end -end - - --- deal damage and effects when mob punched -local mob_punch = function(self, hitter, tflp, tool_capabilities, dir) - - -- custom punch function - if self.do_punch then - - -- when false skip going any further - if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then - return - end - end - - -- error checking when mod profiling is enabled - if not tool_capabilities then - minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled") - return - end - - local is_player = hitter:is_player() - - if is_player then - -- is mob protected? - if self.protected and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then - return - end - - -- set/update 'drop xp' timestamp if hitted by player - self.xp_timestamp = minetest.get_us_time() - end - - - -- punch interval - local weapon = hitter:get_wielded_item() - local punch_interval = 1.4 - - -- exhaust attacker - if mod_hunger and is_player then - mcl_hunger.exhaust(hitter:get_player_name(), mcl_hunger.EXHAUST_ATTACK) - end - - -- calculate mob damage - local damage = 0 - local armor = self.object:get_armor_groups() or {} - local tmp - - -- quick error check incase it ends up 0 (serialize.h check test) - if tflp == 0 then - tflp = 0.2 - end - - if use_cmi then - damage = cmi.calculate_damage(self.object, hitter, tflp, tool_capabilities, dir) - else - - for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do - - tmp = tflp / (tool_capabilities.full_punch_interval or 1.4) - - if tmp < 0 then - tmp = 0.0 - elseif tmp > 1 then - tmp = 1.0 - end - - damage = damage + (tool_capabilities.damage_groups[group] or 0) - * tmp * ((armor[group] or 0) / 100.0) - end - end - - if weapon then - local fire_aspect_level = mcl_enchanting.get_enchantment(weapon, "fire_aspect") - if fire_aspect_level > 0 then - mcl_burning.set_on_fire(self.object, fire_aspect_level * 4) - end - end - - -- check for tool immunity or special damage - for n = 1, #self.immune_to do - - if self.immune_to[n][1] == weapon:get_name() then - - damage = self.immune_to[n][2] or 0 - break - end - end - - -- healing - if damage <= -1 then - self.health = self.health - floor(damage) - return - end - - if use_cmi then - - local cancel = cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) - - if cancel then return end - end - - if tool_capabilities then - punch_interval = tool_capabilities.full_punch_interval or 1.4 - end - - -- add weapon wear manually - -- Required because we have custom health handling ("health" property) - if minetest.is_creative_enabled("") ~= true - and tool_capabilities then - if tool_capabilities.punch_attack_uses then - -- Without this delay, the wear does not work. Quite hacky ... - minetest.after(0, function(name) - local player = minetest.get_player_by_name(name) - if not player then return end - local weapon = hitter:get_wielded_item(player) - local def = weapon:get_definition() - if def.tool_capabilities and def.tool_capabilities.punch_attack_uses then - local wear = floor(65535/tool_capabilities.punch_attack_uses) - weapon:add_wear(wear) - hitter:set_wielded_item(weapon) - end - end, hitter:get_player_name()) - end - end - - local die = false - - -- only play hit sound and show blood effects if damage is 1 or over; lower to 0.1 to ensure armor works appropriately. - if damage >= 0.1 then - - -- weapon sounds - if weapon:get_definition().sounds ~= nil then - - local s = random(0, #weapon:get_definition().sounds) - - minetest.sound_play(weapon:get_definition().sounds[s], { - object = self.object, --hitter, - max_hear_distance = 8 - }, true) + mobs.death_effect = function(pos, yaw, collisionbox, rotate) + local min, max + if collisionbox then + min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]} + max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]} else - minetest.sound_play("default_punch", { - object = self.object, - max_hear_distance = 5 - }, true) + min = { x = -0.5, y = 0, z = -0.5 } + max = { x = 0.5, y = 0.5, z = 0.5 } end - - damage_effect(self, damage) - - -- do damage - self.health = self.health - damage - - -- skip future functions if dead, except alerting others - if check_for_death(self, "hit", {type = "punch", puncher = hitter}) then - die = true + if rotate then + min = vector.rotate(min, {x=0, y=yaw, z=math_pi/2}) + max = vector.rotate(max, {x=0, y=yaw, z=math_pi/2}) + min, max = vector.sort(min, max) + min = vector.multiply(min, 0.5) + max = vector.multiply(max, 0.5) end - - -- knock back effect (only on full punch) - if not die - and self.knock_back - and tflp >= punch_interval then - - local v = self.object:get_velocity() - local r = 1.4 - min(punch_interval, 1.4) - local kb = r * 2.0 - local up = 2 - - -- if already in air then dont go up anymore when hit - if v.y ~= 0 - or self.fly then - up = 0 - end - - -- direction error check - dir = dir or {x = 0, y = 0, z = 0} - - -- check if tool already has specific knockback value - if tool_capabilities.damage_groups["knockback"] then - kb = tool_capabilities.damage_groups["knockback"] - else - kb = kb * 1.5 - end - - - local luaentity - if hitter then - luaentity = hitter:get_luaentity() - end - if hitter and is_player then - local wielditem = hitter:get_wielded_item() - kb = kb + 3 * mcl_enchanting.get_enchantment(wielditem, "knockback") - elseif luaentity and luaentity._knockback then - kb = kb + luaentity._knockback - end - - self.object:set_velocity({ - x = dir.x * kb, - y = dir.y * kb + up * 2, - z = dir.z * kb - }) - - self.pause_timer = 0.25 - end - end -- END if damage - - -- if skittish then run away - if not die and self.runaway == true and self.state ~= "flop" then - - local lp = hitter:get_pos() - local s = self.object:get_pos() - local vec = { - x = lp.x - s.x, - y = lp.y - s.y, - z = lp.z - s.z - } - - local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate - - if lp.x > s.x then - yaw = yaw + pi - end - - yaw = set_yaw(self, yaw, 6) - self.state = "runaway" - self.runaway_timer = 0 - self.following = nil + + minetest_add_particlespawner({ + amount = 50, + time = 0.001, + minpos = vector.add(pos, min), + maxpos = vector.add(pos, max), + minvel = vector.new(-5,-5,-5), + maxvel = vector.new(5,5,5), + minexptime = 1.1, + maxexptime = 1.5, + minsize = 1, + maxsize = 2, + collisiondetection = false, + vertical = false, + texture = "mcl_particles_mob_death.png^[colorize:#000000:255", + }) + + minetest_sound_play("mcl_mobs_mob_poof", { + pos = pos, + gain = 1.0, + max_hear_distance = 8, + }, true) end - - local name = hitter:get_player_name() or "" - - -- attack puncher and call other mobs for help - if self.passive == false - and self.state ~= "flop" - and (self.child == false or self.type == "monster") - and hitter:get_player_name() ~= self.owner - and not mobs.invis[ name ] then - - if not die then - -- attack whoever punched mob - self.state = "" - do_attack(self, hitter) - end - - -- alert others to the attack - local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range) - local obj = nil - - for n = 1, #objs do - - obj = objs[n]:get_luaentity() - - if obj then - - -- only alert members of same mob or friends - if obj.group_attack - and obj.state ~= "attack" - and obj.owner ~= name then - if obj.name == self.name then - do_attack(obj, hitter) - elseif type(obj.group_attack) == "table" then - for i=1, #obj.group_attack do - if obj.name == obj.group_attack[i] then - do_attack(obj, hitter) - break - end - end - end - end - - -- have owned mobs attack player threat - if obj.owner == name and obj.owner_loyal then - do_attack(obj, self.object) - end - end - end - end -end - -local mob_detach_child = function(self, child) - - if self.driver == child then - self.driver = nil - end - -end - --- get entity staticdata -local mob_staticdata = function(self) - ---[[ - -- remove mob when out of range unless tamed - if remove_far - and self.can_despawn - and self.remove_ok - and ((not self.nametag) or (self.nametag == "")) - and self.lifetimer <= 20 then - - minetest.log("action", "Mob "..name.." despawns in mob_staticdata at "..minetest.pos_to_string(self.object.get_pos(), 1)) - mcl_burning.extinguish(self.object) - self.object:remove() - - return ""-- nil - end ---]] - self.remove_ok = true - self.attack = nil - self.following = nil - self.state = "stand" - - if use_cmi then - self.serialized_cmi_components = cmi.serialize_components(self._cmi_components) - end - - local tmp = {} - - for _,stat in pairs(self) do - - local t = type(stat) - - if t ~= "function" - and t ~= "nil" - and t ~= "userdata" - and _ ~= "_cmi_components" then - tmp[_] = self[_] - end - end - - return minetest.serialize(tmp) + +-- above function exported for mount.lua +function mobs:set_animation(self, anim) + set_animation(self, anim) end --- activate mob and reload settings -local mob_activate = function(self, staticdata, def, dtime) - - -- remove monsters in peaceful mode - if self.type == "monster" - and minetest.settings:get_bool("only_peaceful_mobs", false) then - mcl_burning.extinguish(self.object) - self.object:remove() - +-- set defined animation +local set_animation = function(self, anim, fixed_frame) + if not self.animation or not anim then + return + end + if self.state == "die" and anim ~= "die" and anim ~= "stand" then return end - -- load entity variables - local tmp = minetest.deserialize(staticdata) + self.animation.current = self.animation.current or "" - if tmp then - for _,stat in pairs(tmp) do - self[_] = stat - end + if (anim == self.animation.current + or not self.animation[anim .. "_start"] + or not self.animation[anim .. "_end"]) and self.state ~= "die" then + return end - -- select random texture, set model and size - if not self.base_texture then + self.animation.current = anim - -- compatiblity with old simple mobs textures - if type(def.textures[1]) == "string" then - def.textures = {def.textures} - end - - self.base_texture = def.textures[random(1, #def.textures)] - self.base_mesh = def.mesh - self.base_size = self.visual_size - self.base_colbox = self.collisionbox - self.base_selbox = self.selectionbox - end - - -- for current mobs that dont have this set - if not self.base_selbox then - self.base_selbox = self.selectionbox or self.base_colbox - end - - -- set texture, model and size - local textures = self.base_texture - local mesh = self.base_mesh - local vis_size = self.base_size - local colbox = self.base_colbox - local selbox = self.base_selbox - - -- specific texture if gotten - if self.gotten == true - and def.gotten_texture then - textures = def.gotten_texture - end - - -- specific mesh if gotten - if self.gotten == true - and def.gotten_mesh then - mesh = def.gotten_mesh - end - - -- set child objects to half size - if self.child == true then - - vis_size = { - x = self.base_size.x * .5, - y = self.base_size.y * .5, - } - - if def.child_texture then - textures = def.child_texture[1] - end - - colbox = { - self.base_colbox[1] * .5, - self.base_colbox[2] * .5, - self.base_colbox[3] * .5, - self.base_colbox[4] * .5, - self.base_colbox[5] * .5, - self.base_colbox[6] * .5 - } - selbox = { - self.base_selbox[1] * .5, - self.base_selbox[2] * .5, - self.base_selbox[3] * .5, - self.base_selbox[4] * .5, - self.base_selbox[5] * .5, - self.base_selbox[6] * .5 - } - end - - if self.health == 0 then - self.health = random (self.hp_min, self.hp_max) - end - if self.breath == nil then - self.breath = self.breath_max - end - - -- pathfinding init - self.path = {} - self.path.way = {} -- path to follow, table of positions - self.path.lastpos = {x = 0, y = 0, z = 0} - self.path.stuck = false - self.path.following = false -- currently following path? - self.path.stuck_timer = 0 -- if stuck for too long search for path - - -- Armor groups - -- immortal=1 because we use custom health - -- handling (using "health" property) - local armor - if type(self.armor) == "table" then - armor = table.copy(self.armor) - armor.immortal = 1 + local a_start = self.animation[anim .. "_start"] + local a_end + if fixed_frame then + a_end = a_start else - armor = {immortal=1, fleshy = self.armor} - end - self.object:set_armor_groups(armor) - self.old_y = self.object:get_pos().y - self.old_health = self.health - self.sounds.distance = self.sounds.distance or 10 - self.textures = textures - self.mesh = mesh - self.collisionbox = colbox - self.selectionbox = selbox - self.visual_size = vis_size - self.standing_in = "ignore" - self.standing_on = "ignore" - self.jump_sound_cooloff = 0 -- used to prevent jump sound from being played too often in short time - self.opinion_sound_cooloff = 0 -- used to prevent sound spam of particular sound types - - self.texture_mods = {} - self.object:set_texture_mod("") - - self.v_start = false - self.timer = 0 - self.blinktimer = 0 - self.blinkstatus = false - - -- check existing nametag - if not self.nametag then - self.nametag = def.nametag + a_end = self.animation[anim .. "_end"] end - -- set anything changed above - self.object:set_properties(self) - set_yaw(self, (random(0, 360) - 180) / 180 * pi, 6) - update_tag(self) - set_animation(self, "stand") - - -- run on_spawn function if found - if self.on_spawn and not self.on_spawn_run then - if self.on_spawn(self) then - self.on_spawn_run = true -- if true, set flag to run once only - end - end - - -- run after_activate - if def.after_activate then - def.after_activate(self, staticdata, def, dtime) - end - - if use_cmi then - self._cmi_components = cmi.activate_components(self.serialized_cmi_components) - cmi.notify_activate(self.object, dtime) - end + self.object:set_animation({ + x = a_start, + y = a_end}, + self.animation[anim .. "_speed"] or self.animation.speed_normal or 15, + 0, self.animation[anim .. "_loop"] ~= false) end --- main mob function -local mob_step = function(self, dtime) - - if not self.fire_resistant and self.mcl_burning_burn_time and self.mcl_burning_burn_time > 0 then - mcl_burning.tick(self.object, dtime) - end - - if use_cmi then - cmi.notify_step(self.object, dtime) - end - - local pos = self.object:get_pos() - local yaw = 0 - - if mobs_debug then - update_tag(self) - end - - if self.state == "die" then - return - end - - if self.jump_sound_cooloff > 0 then - self.jump_sound_cooloff = self.jump_sound_cooloff - dtime - end - if self.opinion_sound_cooloff > 0 then - self.opinion_sound_cooloff = self.opinion_sound_cooloff - dtime - end - if falling(self, pos) then - -- Return if mob died after falling - return - end - - -- smooth rotation by ThomasMonroe314 - - if self.delay and self.delay > 0 then - - local yaw = self.object:get_yaw() or 0 - - if self.delay == 1 then - yaw = self.target_yaw - else - local dif = abs(yaw - self.target_yaw) - - if yaw > self.target_yaw then - - if dif > pi then - dif = 2 * pi - dif -- need to add - yaw = yaw + dif / self.delay - else - yaw = yaw - dif / self.delay -- need to subtract - end - - elseif yaw < self.target_yaw then - - if dif > pi then - dif = 2 * pi - dif - yaw = yaw - dif / self.delay -- need to subtract - else - yaw = yaw + dif / self.delay -- need to add - end - end - - if yaw > (pi * 2) then yaw = yaw - (pi * 2) end - if yaw < 0 then yaw = yaw + (pi * 2) end - end - - self.delay = self.delay - 1 - if self.shaking then - yaw = yaw + (math.random() * 2 - 1) * 5 * dtime - end - self.object:set_yaw(yaw) - update_roll(self) - end - - -- end rotation - - -- run custom function (defined in mob lua file) - if self.do_custom then - - -- when false skip going any further - if self.do_custom(self, dtime) == false then - return - end - end - - -- knockback timer - if self.pause_timer > 0 then - - self.pause_timer = self.pause_timer - dtime - - 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 - - -- never go over 100 - if self.timer > 100 then - self.timer = 1 - end - - -- mob plays random sound at times - if random(1, 70) == 1 then - mob_sound(self, "random", true) - 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) - or self.state ~= "attack" then - - self.env_damage_timer = 0 - - -- check for environmental damage (water, fire, lava etc.) - if do_env_damage(self) then - return - end - - -- node replace check (cow eats grass etc.) - replace(self, pos) - end - - monster_attack(self) - - npc_attack(self) - - breed(self) - - if do_states(self, dtime) then - return - end - - if not self.object:get_luaentity() then - return false - end - - do_jump(self) - - runaway_from(self) - - if is_at_water_danger(self) and self.state ~= "attack" then - if random(1, 10) <= 6 then - set_velocity(self, 0) - self.state = "stand" - set_animation(self, "stand") - yaw = yaw + random(-0.5, 0.5) - yaw = set_yaw(self, yaw, 8) - end - end - - -- Add water flowing for mobs from mcl_item_entity - local p, node, nn, def - p = self.object:get_pos() - node = minetest.get_node_or_nil(p) - if node then - nn = node.name - def = minetest.registered_nodes[nn] - end - - -- Move item around on flowing liquids - if def and def.liquidtype == "flowing" then - - --[[ Get flowing direction (function call from flowlib), if there's a liquid. - NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7. - Luckily, this is exactly what we need if we only care about water, which has this flowing distance. ]] - local vec = flowlib.quick_flow(p, node) - -- Just to make sure we don't manipulate the speed for no reason - if vec.x ~= 0 or vec.y ~= 0 or vec.z ~= 0 then - -- Minecraft Wiki: Flowing speed is "about 1.39 meters per second" - local f = 1.39 - -- Set new item moving speed into the direciton of the liquid - local newv = vector.multiply(vec, f) - self.object:set_acceleration({x = 0, y = 0, z = 0}) - self.object:set_velocity({x = newv.x, y = -0.22, z = newv.z}) - - self.physical_state = true - self._flowing = true - self.object:set_properties({ - physical = true - }) - return - end - elseif self._flowing == true then - -- Disable flowing physics if not on/in flowing liquid - self._flowing = false - enable_physics(self.object, self, true) - return - end - - --Mob following code. - follow_flop(self) - - if is_at_cliff_or_danger(self) then - set_velocity(self, 0) - self.state = "stand" - set_animation(self, "stand") - local yaw = self.object:get_yaw() or 0 - yaw = set_yaw(self, yaw + 0.78, 8) - end - - -- Despawning: when lifetimer expires, remove mob - if remove_far - and self.can_despawn == true - and ((not self.nametag) or (self.nametag == "")) - and self.state ~= "attack" - and self.following == nil then - - self.lifetimer = self.lifetimer - dtime - if self.despawn_immediately or self.lifetimer <= 0 then - minetest.log("action", "Mob "..self.name.." despawns in mob_step at "..minetest.pos_to_string(pos, 1)) - mcl_burning.extinguish(self.object) - self.object:remove() - elseif self.lifetimer <= 10 then - if math.random(10) < 4 then - self.despawn_immediately = true - else - self.lifetimer = 20 - end - end - end -end - - --- default function when mobs are blown up with TNT -local do_tnt = function(obj, damage) - - obj.object:punch(obj.object, 1.0, { - full_punch_interval = 1.0, - damage_groups = {fleshy = damage}, - }, nil) - - return false, true, {} -end - - -mobs.spawning_mobs = {} - -- Code to execute before custom on_rightclick handling local on_rightclick_prefix = function(self, clicker) local item = clicker:get_wielded_item() @@ -3735,642 +2952,201 @@ local create_mob_on_rightclick = function(on_rightclick) end end --- register mob entity -function mobs:register_mob(name, def) +-- set and return valid yaw +local set_yaw = function(self, yaw, delay, dtime) - mobs.spawning_mobs[name] = true - -local can_despawn -if def.can_despawn ~= nil then - can_despawn = def.can_despawn -elseif def.spawn_class == "passive" then - can_despawn = false -else - can_despawn = true -end - -local function scale_difficulty(value, default, min, special) - if (not value) or (value == default) or (value == special) then - return default - else - return max(min, value * difficulty) - end -end - -local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25} --- Workaround for : --- Increase upper Y limit to avoid mobs glitching through solid nodes. --- FIXME: Remove workaround if it's no longer needed. -if collisionbox[5] < 0.79 then - collisionbox[5] = 0.79 -end - -minetest.register_entity(name, { - - use_texture_alpha = def.use_texture_alpha, - stepheight = def.stepheight or 0.6, - name = name, - type = def.type, - attack_type = def.attack_type, - fly = def.fly, - fly_in = def.fly_in or {"air", "__airlike"}, - owner = def.owner or "", - order = def.order or "", - on_die = def.on_die, - spawn_small_alternative = def.spawn_small_alternative, - do_custom = def.do_custom, - jump_height = def.jump_height or 4, -- was 6 - rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2 - lifetimer = def.lifetimer or 57.73, - hp_min = scale_difficulty(def.hp_min, 5, 1), - hp_max = scale_difficulty(def.hp_max, 10, 1), - xp_min = def.xp_min or 0, - xp_max = def.xp_max or 0, - xp_timestamp = 0, - breath_max = def.breath_max or 15, - breathes_in_water = def.breathes_in_water or false, - physical = true, - collisionbox = collisionbox, - selectionbox = def.selectionbox or 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 16, - walk_velocity = def.walk_velocity or 1, - run_velocity = def.run_velocity or 2, - damage = scale_difficulty(def.damage, 0, 0), - light_damage = def.light_damage or 0, - sunlight_damage = def.sunlight_damage or 0, - water_damage = def.water_damage or 0, - lava_damage = def.lava_damage or 8, - fire_damage = def.fire_damage or 1, - suffocation = def.suffocation or true, - fall_damage = def.fall_damage or 1, - fall_speed = def.fall_speed or DEFAULT_FALL_SPEED, -- must be lower than -2 - drops = def.drops or {}, - armor = def.armor or 100, - on_rightclick = create_mob_on_rightclick(def.on_rightclick), - arrow = def.arrow, - shoot_interval = def.shoot_interval, - sounds = def.sounds or {}, - animation = def.animation, - follow = def.follow, - jump = def.jump ~= false, - walk_chance = def.walk_chance or 50, - attacks_monsters = def.attacks_monsters or false, - group_attack = def.group_attack or false, - passive = def.passive or false, - knock_back = def.knock_back ~= false, - shoot_offset = def.shoot_offset or 0, - floats = def.floats or 1, -- floats in water by default - floats_on_lava = def.floats_on_lava or 0, - replace_rate = def.replace_rate, - replace_what = def.replace_what, - replace_with = def.replace_with, - replace_offset = def.replace_offset or 0, - on_replace = def.on_replace, - timer = 0, - env_damage_timer = 0, - tamed = false, - pause_timer = 0, - horny = false, - hornytimer = 0, - gotten = false, - health = 0, - reach = def.reach or 3, - htimer = 0, - texture_list = def.textures, - child_texture = def.child_texture, - docile_by_day = def.docile_by_day or false, - time_of_day = 0.5, - fear_height = def.fear_height or 0, - runaway = def.runaway, - runaway_timer = 0, - pathfinding = def.pathfinding, - immune_to = def.immune_to or {}, - explosion_radius = def.explosion_radius, -- LEGACY - explosion_damage_radius = def.explosion_damage_radius, -- LEGACY - explosiontimer_reset_radius = def.explosiontimer_reset_radius, - explosion_timer = def.explosion_timer or 3, - allow_fuse_reset = def.allow_fuse_reset ~= false, - stop_to_explode = def.stop_to_explode ~= false, - custom_attack = def.custom_attack, - double_melee_attack = def.double_melee_attack, - dogshoot_switch = def.dogshoot_switch, - dogshoot_count = 0, - dogshoot_count_max = def.dogshoot_count_max or 5, - dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5), - attack_animals = def.attack_animals or false, - specific_attack = def.specific_attack, - runaway_from = def.runaway_from, - owner_loyal = def.owner_loyal, - facing_fence = false, - _cmi_is_mob = true, - pushable = def.pushable or true, - - - -- MCL2 extensions - teleport = teleport, - do_teleport = def.do_teleport, - spawn_class = def.spawn_class, - ignores_nametag = def.ignores_nametag or false, - rain_damage = def.rain_damage or 0, - glow = def.glow, - can_despawn = can_despawn, - child = def.child or false, - texture_mods = {}, - shoot_arrow = def.shoot_arrow, - sounds_child = def.sounds_child, - explosion_strength = def.explosion_strength, - suffocation_timer = 0, - follow_velocity = def.follow_velocity or 2.4, - instant_death = def.instant_death or false, - fire_resistant = def.fire_resistant or false, - fire_damage_resistant = def.fire_damage_resistant or false, - ignited_by_sunlight = def.ignited_by_sunlight or false, - -- End of MCL2 extensions - - on_spawn = def.on_spawn, - - on_blast = def.on_blast or do_tnt, - - on_step = mob_step, - - do_punch = def.do_punch, - - on_punch = mob_punch, - - on_breed = def.on_breed, - - on_grown = def.on_grown, - - on_detach_child = mob_detach_child, - - on_activate = function(self, staticdata, dtime) - --this is a temporary hack so mobs stop - --glitching and acting really weird with the - --default built in engine collision detection - self.object:set_properties({ - collide_with_objects = false, - }) - return mob_activate(self, staticdata, def, dtime) - end, - - get_staticdata = function(self) - return mob_staticdata(self) - end, - - harmed_by_heal = def.harmed_by_heal, - -}) - -if minetest.get_modpath("doc_identifier") ~= nil then - doc.sub.identifier.register_object(name, "basics", "mobs") -end - -end -- END mobs:register_mob function - - --- register arrow for shoot attack -function mobs:register_arrow(name, def) - - if not name or not def then return end -- errorcheck - - minetest.register_entity(name, { - - physical = false, - visual = def.visual, - visual_size = def.visual_size, - textures = def.textures, - velocity = def.velocity, - hit_player = def.hit_player, - hit_node = def.hit_node, - hit_mob = def.hit_mob, - hit_object = def.hit_object, - drop = def.drop or false, -- drops arrow as registered item when true - collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows - timer = 0, - switch = 0, - owner_id = def.owner_id, - rotate = def.rotate, - on_punch = function(self) - local vel = self.object:get_velocity() - self.object:set_velocity({x=vel.x * -1, y=vel.y * -1, z=vel.z * -1}) - end, - collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0}, - automatic_face_movement_dir = def.rotate - and (def.rotate - (pi / 180)) or false, - - on_activate = def.on_activate, - - on_step = def.on_step or function(self, dtime) - - self.timer = self.timer + 1 - - local pos = self.object:get_pos() - - if self.switch == 0 - or self.timer > 150 - or not within_limits(pos, 0) then - mcl_burning.extinguish(self.object) - self.object:remove(); - - return - end - - -- does arrow have a tail (fireball) - if def.tail - and def.tail == 1 - and def.tail_texture then - - minetest.add_particle({ - pos = pos, - velocity = {x = 0, y = 0, z = 0}, - acceleration = {x = 0, y = 0, z = 0}, - expirationtime = def.expire or 0.25, - collisiondetection = false, - texture = def.tail_texture, - size = def.tail_size or 5, - glow = def.glow or 0, - }) - end - - if self.hit_node then - - local node = node_ok(pos).name - - if minetest.registered_nodes[node].walkable then - - self.hit_node(self, pos, node) - - if self.drop == true then - - pos.y = pos.y + 1 - - self.lastpos = (self.lastpos or pos) - - minetest.add_item(self.lastpos, self.object:get_luaentity().name) - end - - self.object:remove(); - - return - end - end - - if self.hit_player or self.hit_mob or self.hit_object then - - for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do - - if self.hit_player - and player:is_player() then - - self.hit_player(self, player) - self.object:remove(); - return - end - - local entity = player:get_luaentity() - - if entity - and self.hit_mob - and entity._cmi_is_mob == true - and tostring(player) ~= self.owner_id - and entity.name ~= self.object:get_luaentity().name then - self.hit_mob(self, player) - self.object:remove(); - return - end - - if entity - and self.hit_object - and (not entity._cmi_is_mob) - and tostring(player) ~= self.owner_id - and entity.name ~= self.object:get_luaentity().name then - self.hit_object(self, player) - self.object:remove(); - return - end - end - end - - self.lastpos = pos - end - }) -end - - --- no damage to nodes explosion -function mobs:safe_boom(self, pos, strength) - minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", { - pos = pos, - gain = 1.0, - max_hear_distance = self.sounds and self.sounds.distance or 32 - }, true) - local radius = strength - entity_physics(pos, radius) - effect(pos, 32, "mcl_particles_smoke.png", radius * 3, radius * 5, radius, 1, 0) -end - - --- make explosion with protection and tnt mod check -function mobs:boom(self, pos, strength, fire) - self.object:remove() - if mod_explosions then - if mobs_griefing and not minetest.is_protected(pos, "") then - mcl_explosions.explode(pos, strength, { drop_chance = 1.0, fire = fire }, self.object) - else - mobs:safe_boom(self, pos, strength) - end - else - mobs:safe_boom(self, pos, strength) - end -end - - --- Register spawn eggs - --- Note: This also introduces the “spawn_egg” group: --- * spawn_egg=1: Spawn egg (generic mob, no metadata) --- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata) -function mobs:register_egg(mob, desc, background, addegg, no_creative) - - local grp = {spawn_egg = 1} - - -- do NOT add this egg to creative inventory (e.g. dungeon master) - if no_creative == true then - grp.not_in_creative_inventory = 1 + if not yaw or yaw ~= yaw then + yaw = 0 end - local invimg = background + delay = delay or 0 - if addegg == 1 then - invimg = "mobs_chicken_egg.png^(" .. invimg .. - "^[mask:mobs_chicken_egg_overlay.png)" + if delay == 0 then + if self.shaking and dtime then + yaw = yaw + (math_random() * 2 - 1) * 5 * dtime + end + self.yaw(yaw) + update_roll(self) + return yaw end - -- register old stackable mob egg - minetest.register_craftitem(mob, { - - description = desc, - inventory_image = invimg, - groups = grp, - - _doc_items_longdesc = S("This allows you to place a single mob."), - _doc_items_usagehelp = S("Just place it where you want the mob to appear. Animals will spawn tamed, unless you hold down the sneak key while placing. If you place this on a mob spawner, you change the mob it spawns."), - - on_place = function(itemstack, placer, pointed_thing) - - local pos = pointed_thing.above - - -- am I clicking on something with existing on_rightclick function? - local under = minetest.get_node(pointed_thing.under) - local def = minetest.registered_nodes[under.name] - if def and def.on_rightclick then - return def.on_rightclick(pointed_thing.under, under, placer, itemstack) - end - - if pos - and within_limits(pos, 0) - and not minetest.is_protected(pos, placer:get_player_name()) then - - local name = placer:get_player_name() - local privs = minetest.get_player_privs(name) - if mod_mobspawners and under.name == "mcl_mobspawners:spawner" then - if minetest.is_protected(pointed_thing.under, name) then - minetest.record_protection_violation(pointed_thing.under, name) - return itemstack - end - if not privs.maphack then - minetest.chat_send_player(name, S("You need the “maphack” privilege to change the mob spawner.")) - return itemstack - end - mcl_mobspawners.setup_spawner(pointed_thing.under, itemstack:get_name()) - if not mobs.is_creative(name) then - itemstack:take_item() - end - return itemstack - end - - if not minetest.registered_entities[mob] then - return itemstack - end - - if minetest.settings:get_bool("only_peaceful_mobs", false) - and minetest.registered_entities[mob].type == "monster" then - minetest.chat_send_player(name, S("Only peaceful mobs allowed!")) - return itemstack - end - - pos.y = pos.y - 0.5 - - local mob = minetest.add_entity(pos, mob) - minetest.log("action", "Mob spawned: "..name.." at "..minetest.pos_to_string(pos)) - local ent = mob:get_luaentity() - - -- don't set owner if monster or sneak pressed - if ent.type ~= "monster" - and not placer:get_player_control().sneak then - ent.owner = placer:get_player_name() - ent.tamed = true - end - - -- set nametag - local nametag = itemstack:get_meta():get_string("name") - if nametag ~= "" then - if string.len(nametag) > MAX_MOB_NAME_LENGTH then - nametag = string.sub(nametag, 1, MAX_MOB_NAME_LENGTH) - end - ent.nametag = nametag - update_tag(ent) - end - - -- if not in creative then take item - if not mobs.is_creative(placer:get_player_name()) then - itemstack:take_item() - end - end - - return itemstack - end, - }) + self.target_yaw = yaw + self.delay = delay + return self.target_yaw end --- No-op in MCL2 (capturing mobs is not possible). --- Provided for compability with Mobs Redo -function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith) - return false +-- global function to set mob yaw +function mobs:yaw(self, yaw, delay, dtime) + set_yaw(self, yaw, delay, dtime) end --- No-op in MCL2 (protecting mobs is not possible). -function mobs:protect(self, clicker) - return false -end +mob_step = function() + + --if self.state == "die" then + -- print("need custom die stop moving thing") + -- return + --end + + --if not self.fire_resistant then + -- mcl_burning.tick(self.object, dtime) + --end + + --if use_cmi then + --cmi.notify_step(self.object, dtime) + --end + + --local pos = self.object:get_pos() + --local yaw = 0 + + --if mobs_debug then + --update_tag(self) + --end --- feeding, taming and breeding (thanks blert2112) -function mobs:feed_tame(self, clicker, feed_count, breed, tame) - if not self.follow then - return false + + --if self.jump_sound_cooloff > 0 then + -- self.jump_sound_cooloff = self.jump_sound_cooloff - dtime + --end + + --if self.opinion_sound_cooloff > 0 then + -- self.opinion_sound_cooloff = self.opinion_sound_cooloff - dtime + --end + + --if falling(self, pos) then + -- Return if mob died after falling + -- return + --end + + + -- run custom function (defined in mob lua file) + --if self.do_custom then + + -- when false skip going any further + --if self.do_custom(self, dtime) == false then + -- return + --end + --end + + -- knockback timer + --if self.pause_timer > 0 then + + -- self.pause_timer = self.pause_timer - dtime + + -- return + --end + + -- attack timer + --self.timer = self.timer + dtime + + --[[ + if self.state ~= "attack" then + + if self.timer < 1 then + print("returning>>error code 1") + return + end + + self.timer = 0 end + ]]-- - -- can eat/tame with item in hand - if follow_holding(self, clicker) then + -- never go over 100 + --if self.timer > 100 then + -- self.timer = 1 + --end - -- if not in creative then take item - if not mobs.is_creative(clicker:get_player_name()) then + -- mob plays random sound at times + --if math_random(1, 70) == 1 then + -- mob_sound(self, "random", true) + --end - local item = clicker:get_wielded_item() + -- environmental damage timer (every 1 second) + --self.env_damage_timer = self.env_damage_timer + dtime - item:take_item() + --if (self.state == "attack" and self.env_damage_timer > 1) + --or self.state ~= "attack" then + -- + -- self.env_damage_timer = 0 + -- + -- -- check for environmental damage (water, fire, lava etc.) + -- if do_env_damage(self) then + -- return + -- end + -- + -- node replace check (cow eats grass etc.) + -- replace(self, pos) + --end - clicker:set_wielded_item(item) - end + --monster_attack(self) - mob_sound(self, "eat", nil, true) + --npc_attack(self) - -- increase health - self.health = self.health + 4 + --breed(self) - if self.health >= self.hp_max then + --do_jump(self) - self.health = self.hp_max + --runaway_from(self) - if self.htimer < 1 then - self.htimer = 5 - end - end - self.object:set_hp(self.health) + --if is_at_water_danger(self) and self.state ~= "attack" then + -- if math_random(1, 10) <= 6 then + -- set_velocity(self, 0) + -- self.state = "stand" + -- set_animation(self, "stand") + -- yaw = yaw + math_random(-0.5, 0.5) + -- yaw = set_yaw(self, yaw, 8) + -- end + --end - update_tag(self) - - -- make children grow quicker - if self.child == true then - - -- deduct 10% of the time to adulthood - self.hornytimer = self.hornytimer + ((CHILD_GROW_TIME - self.hornytimer) * 0.1) - - return true - end - - -- feed and tame - self.food = (self.food or 0) + 1 - if self.food >= feed_count then - - self.food = 0 - - if breed and self.hornytimer == 0 then - self.horny = true - end - - if tame then - - self.tamed = true - - if not self.owner or self.owner == "" then - self.owner = clicker:get_player_name() - end - end - - -- make sound when fed so many times - mob_sound(self, "random", true) - end - - return true - end - - return false -end - --- Spawn a child -function mobs:spawn_child(pos, mob_type) - local child = minetest.add_entity(pos, mob_type) - if not child then + + -- Add water flowing for mobs from mcl_item_entity + --[[ + local p, node, nn, def + p = self.object:get_pos() + node = minetest_get_node_or_nil(p) + if node then + nn = node.name + def = minetest_registered_nodes[nnenable_physicss if not on/in flowing liquid + self._flowing = false + enable_physics(self.object, self, true) return end - local ent = child:get_luaentity() - effect(pos, 15, "mcl_particles_smoke.png", 1, 2, 2, 15, 5) + --Mob following code. + follow_flop(self) - ent.child = true - local textures - -- using specific child texture (if found) - if ent.child_texture then - textures = ent.child_texture[1] + if is_at_cliff_or_danger(self) then + set_velocity(self, 0) + self.state = "stand" + set_animation(self, "stand") + local yaw = self.object:get_yaw() or 0 + yaw = set_yaw(self, yaw + 0.78, 8) end - -- and resize to half height - child:set_properties({ - textures = textures, - visual_size = { - x = ent.base_size.x * .5, - y = ent.base_size.y * .5, - }, - collisionbox = { - ent.base_colbox[1] * .5, - ent.base_colbox[2] * .5, - ent.base_colbox[3] * .5, - ent.base_colbox[4] * .5, - ent.base_colbox[5] * .5, - ent.base_colbox[6] * .5, - }, - selectionbox = { - ent.base_selbox[1] * .5, - ent.base_selbox[2] * .5, - ent.base_selbox[3] * .5, - ent.base_selbox[4] * .5, - ent.base_selbox[5] * .5, - ent.base_selbox[6] * .5, - }, - }) - - return child -end - - --- compatibility function for old entities to new modpack entities -function mobs:alias_mob(old_name, new_name) - - -- spawn egg - minetest.register_alias(old_name, new_name) - - -- entity - minetest.register_entity(":" .. old_name, { - - physical = false, - - on_step = function(self) - - if minetest.registered_entities[new_name] then - minetest.add_entity(self.object:get_pos(), new_name) - end + -- Despawning: when lifetimer expires, remove mob + if remove_far + and self.can_despawn == true + and ((not self.nametag) or (self.nametag == "")) + and self.state ~= "attack" + and self.following == nil then + self.lifetimer = self.lifetimer - dtime + if self.despawn_immediately or self.lifetimer <= 0 then + minetest.log("action", "Mob "..self.name.." despawns in mob_step at "..minetest.pos_to_string(pos, 1)) + mcl_burning.extinguish(self.object) self.object:remove() - end - }) - -end - - -local timer = 0 -minetest.register_globalstep(function(dtime) - timer = timer + dtime - if timer < 1 then return end - for _, player in pairs(minetest.get_connected_players()) do - local pos = player:get_pos() - for _, obj in pairs(minetest.get_objects_inside_radius(pos, 47)) do - local lua = obj:get_luaentity() - if lua and lua._cmi_is_mob then - lua.lifetimer = math.max(20, lua.lifetimer) - lua.despawn_immediately = false + elseif self.lifetimer <= 10 then + if math_random(10) < 4 then + self.despawn_immediately = true + else + self.lifetimer = 20 end end end - timer = 0 -end) + ]]-- + +end \ No newline at end of file diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/collision.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/collision.lua new file mode 100644 index 000000000..43b759269 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/collision.lua @@ -0,0 +1,138 @@ +local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius + +local math_random = math.random +local vector_multiply = vector.multiply + +local vector_direction = vector.direction + +local integer_test = {-1,1} + +mobs.collision = function(self) + + local pos = self.object:get_pos() + + + if not self or not self.object or not self.object:get_luaentity() then + return + end + + --do collision detection from the base of the mob + local collisionbox = self.object:get_properties().collisionbox + + pos.y = pos.y + collisionbox[2] + + local collision_boundary = collisionbox[4] + + local radius = collision_boundary + + if collisionbox[5] > collision_boundary then + radius = collisionbox[5] + end + + local collision_count = 0 + + + local check_for_attack = false + + if self.attack_type == "punch" and self.hostile and self.attacking then + check_for_attack = true + end + + for _,object in ipairs(minetest_get_objects_inside_radius(pos, radius*1.25)) do + if object and object ~= self.object and (object:is_player() or object:get_luaentity()._cmi_is_mob == true) then--and + --don't collide with rider, rider don't collide with thing + --(not object:get_attach() or (object:get_attach() and object:get_attach() ~= self.object)) and + --(not self.object:get_attach() or (self.object:get_attach() and self.object:get_attach() ~= object)) then + --stop infinite loop + collision_count = collision_count + 1 + if collision_count > 100 then + break + end + + local pos2 = object:get_pos() + + local object_collisionbox = object:get_properties().collisionbox + + pos2.y = pos2.y + object_collisionbox[2] + + local object_collision_boundary = object_collisionbox[4] + + + --this is checking the difference of the object collided with's possision + --if positive top of other object is inside (y axis) of current object + local y_base_diff = (pos2.y + object_collisionbox[5]) - pos.y + + local y_top_diff = (pos.y + collisionbox[5]) - pos2.y + + + local distance = vector.distance(vector.new(pos.x,0,pos.z),vector.new(pos2.x,0,pos2.z)) + + if distance <= collision_boundary + object_collision_boundary and y_base_diff >= 0 and y_top_diff >= 0 then + + local dir = vector.direction(pos,pos2) + + dir.y = 0 + + --eliminate mob being stuck in corners + if dir.x == 0 and dir.z == 0 then + --slightly adjust mob position to prevent equal length + --corner/wall sticking + dir.x = dir.x + ((math_random()/10)*integer_test[math.random(1,2)]) + dir.z = dir.z + ((math_random()/10)*integer_test[math.random(1,2)]) + end + + local velocity = dir + + --0.5 is the max force multiplier + local force = 0.5 - (0.5 * distance / (collision_boundary + object_collision_boundary)) + + local vel1 = vector.multiply(velocity, -1.5) + local vel2 = vector.multiply(velocity, 1.5) + + vel1 = vector.multiply(vel1, force * 10) + vel2 = vector.multiply(vel2, force) + + if object:is_player() then + vel2 = vector_multiply(vel2, 2.5) + + --integrate mob punching into collision detection + if check_for_attack and self.punch_timer <= 0 then + if object == self.attacking then + mobs.punch_attack(self) + end + end + end + + self.object:add_velocity(vel1) + object:add_velocity(vel2) + end + + end + end +end + + +--this is used for arrow collisions +mobs.arrow_hit = function(self, player) + + player:punch(self.object, 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = self._damage} + }, nil) + + + --knockback + local pos1 = self.object:get_pos() + pos1.y = 0 + local pos2 = player:get_pos() + pos2.y = 0 + local dir = vector_direction(pos1,pos2) + + dir = vector_multiply(dir,3) + + if player:get_velocity().y <= 1 then + dir.y = 5 + end + + player:add_velocity(dir) +end \ No newline at end of file diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/environment.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/environment.lua new file mode 100644 index 000000000..c6a392e57 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/environment.lua @@ -0,0 +1,200 @@ +local minetest_line_of_sight = minetest.line_of_sight +local minetest_dir_to_yaw = minetest.dir_to_yaw +local minetest_yaw_to_dir = minetest.yaw_to_dir +local minetest_get_node = minetest.get_node +local minetest_get_item_group = minetest.get_item_group +local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius +local minetest_get_node_or_nil = minetest.get_node_or_nil +local minetest_registered_nodes = minetest.registered_nodes + +local vector_new = vector.new +local vector_multiply = vector.multiply + +local table_copy = table.copy + +-- default function when mobs are blown up with TNT +local do_tnt = function(obj, damage) + + obj.object:punch(obj.object, 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = damage}, + }, nil) + + return false, true, {} +end + +--a fast function to be able to detect only players without using objects_in_radius +mobs.detect_closest_player_within_radius = function(self, line_of_sight, radius, object_height_adder) + + local pos1 = self.object:get_pos() + local players_in_area = {} + local winner_player = nil + local players_detected = 0 + + --get players in radius + for _,player in pairs(minetest.get_connected_players()) do + if player and player:get_hp() > 0 then + + local pos2 = player:get_pos() + + local distance = vector.distance(pos1,pos2) + + if distance <= radius then + if line_of_sight then + --must add eye height or stuff breaks randomly because of + --seethrough nodes being a blocker (like grass) + if minetest_line_of_sight( + vector_new(pos1.x, pos1.y + object_height_adder, pos1.z), + vector_new(pos2.x, pos2.y + player:get_properties().eye_height, pos2.z) + ) then + players_detected = players_detected + 1 + players_in_area[player] = distance + end + else + players_detected = players_detected + 1 + players_in_area[player] = distance + end + end + end + end + + + --return if there's no one near by + if players_detected <= 0 then --handle negative numbers for some crazy error that could possibly happen + return nil + end + + --do a default radius max + local shortest_disance = radius + 1 + + --sort through players and find the closest player + for player,distance in pairs(players_in_area) do + if distance < shortest_disance then + shortest_disance = distance + winner_player = player + end + end + + return(winner_player) +end + + +--check if a mob needs to jump +mobs.jump_check = function(self,dtime) + + local pos = self.object:get_pos() + pos.y = pos.y + 0.1 + local dir = minetest_yaw_to_dir(self.yaw) + + local collisionbox = self.object:get_properties().collisionbox + local radius = collisionbox[4] + 0.5 + + vector_multiply(dir, radius) + + --only jump if there's a node and a non-solid node above it + local test_dir = vector.add(pos,dir) + + local green_flag_1 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") ~= 0 + + test_dir.y = test_dir.y + 1 + + local green_flag_2 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") == 0 + + if green_flag_1 and green_flag_2 then + --can jump over node + return(1) + elseif green_flag_1 and not green_flag_2 then + --wall in front of mob + return(2) + end + + --nothing to jump over + return(0) +end + +-- a helper function to quickly turn neutral passive mobs hostile +local turn_hostile = function(self,detected_mob) + --drop in variables for attacking (stops crash) + detected_mob.punch_timer = 0 + --set to hostile + detected_mob.hostile = true + --hostile_cooldown timer is initialized here + detected_mob.hostile_cooldown_timer = detected_mob.hostile_cooldown + --set target to the same + detected_mob.attacking = self.attacking +end + +--allow hostile mobs to signal to other mobs +--to switch from neutal passive to neutral hostile +mobs.group_attack_initialization = function(self) + + --get basic data + local friends_list = table_copy(self.group_attack) + local objects_in_area = minetest_get_objects_inside_radius(self.object:get_pos(), self.view_range) + + --get the player's name + local name = self.attacking:get_player_name() + + --re-use local variable + local detected_mob + + --run through mobs in viewing distance + for _,object in pairs(objects_in_area) do + if object and object:get_luaentity() then + detected_mob = object:get_luaentity() + -- only alert members of same mob or friends + if detected_mob._cmi_is_mob and detected_mob.state ~= "attack" and detected_mob.owner ~= name then + if detected_mob.name == self.name then + turn_hostile(self,detected_mob) + elseif type(detected_mob.group_attack) == "table" then + for _,id in pairs(self.group_attack) do + if detected_mob.name == id then + turn_hostile(self,detected_mob) + break + end + end + end + end + + --THIS NEEDS TO BE RE-IMPLEMENTED AS A GLOBAL HIT IN MOB_PUNCH!! + -- have owned mobs attack player threat + --if obj.owner == name and obj.owner_loyal then + -- do_attack(obj, self.object) + --end + end + end +end + +-- check if within physical map limits (-30911 to 30927) +-- within_limits, wmin, wmax = nil, -30913, 30928 +mobs.within_limits = function(pos, radius) + if mcl_vars then + if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then + wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max + within_limits = function(pos, radius) + return pos + and (pos.x - radius) > wmin and (pos.x + radius) < wmax + and (pos.y - radius) > wmin and (pos.y + radius) < wmax + and (pos.z - radius) > wmin and (pos.z + radius) < wmax + end + end + end + return pos + and (pos.x - radius) > wmin and (pos.x + radius) < wmax + and (pos.y - radius) > wmin and (pos.y + radius) < wmax + and (pos.z - radius) > wmin and (pos.z + radius) < wmax +end + +-- get node but use fallback for nil or unknown +mobs.node_ok = function(pos, fallback) + + fallback = fallback or mobs.fallback_node + + local node = minetest_get_node_or_nil(pos) + + if node and minetest_registered_nodes[node.name] then + return node + end + + return minetest_registered_nodes[fallback] +end \ No newline at end of file diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/interaction.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/interaction.lua new file mode 100644 index 000000000..7cbcd9a70 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/interaction.lua @@ -0,0 +1,358 @@ +local math_floor = math.floor +local vector_direction = vector.direction + +mobs.feed_tame = function(self) + return nil +end + +-- Code to execute before custom on_rightclick handling +local on_rightclick_prefix = function(self, clicker) + + local item = clicker:get_wielded_item() + + -- Name mob with nametag + if not self.ignores_nametag and item:get_name() == "mcl_mobs:nametag" then + + local tag = item:get_meta():get_string("name") + if tag ~= "" then + if string.len(tag) > MAX_MOB_NAME_LENGTH then + tag = string.sub(tag, 1, MAX_MOB_NAME_LENGTH) + end + self.nametag = tag + + update_tag(self) + + if not mobs.is_creative(clicker:get_player_name()) then + item:take_item() + clicker:set_wielded_item(item) + end + return true + end + + end + return false +end + +-- I have no idea what this does +mobs.create_mob_on_rightclick = function(on_rightclick) + return function(self, clicker) + local stop = on_rightclick_prefix(self, clicker) + if (not stop) and (on_rightclick) then + on_rightclick(self, clicker) + end + end +end + + +-- deal damage and effects when mob punched +mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir) + + --neutral passive mobs switch to neutral hostile + if self.neutral then + + --drop in variables for attacking (stops crash) + self.attacking = hitter + self.punch_timer = 0 + + self.hostile = true + --hostile_cooldown timer is initialized here + self.hostile_cooldown_timer = self.hostile_cooldown + + --initialize the group attack (check for other mobs in area, make them neutral hostile) + if self.group_attack then + mobs.group_attack_initialization(self) + end + end + + + --[[ + -- custom punch function + if self.do_punch then + + -- when false skip going any further + if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then + return + end + end + + -- error checking when mod profiling is enabled + if not tool_capabilities then + minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled") + return + end + + local is_player = hitter:is_player() + + if is_player then + -- is mob protected? + if self.protected and minetest_is_protected(self.object:get_pos(), hitter:get_player_name()) then + return + end + + -- set/update 'drop xp' timestamp if hitted by player + self.xp_timestamp = minetest_get_us_time() + end + + + -- punch interval + local weapon = hitter:get_wielded_item() + local punch_interval = 1.4 + + -- exhaust attacker + if mod_hunger and is_player then + mcl_hunger.exhaust(hitter:get_player_name(), mcl_hunger.EXHAUST_ATTACK) + end + + -- calculate mob damage + local damage = 0 + local armor = self.object:get_armor_groups() or {} + local tmp + + -- quick error check incase it ends up 0 (serialize.h check test) + if tflp == 0 then + tflp = 0.2 + end + + if use_cmi then + damage = cmi.calculate_damage(self.object, hitter, tflp, tool_capabilities, dir) + else + + for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do + + tmp = tflp / (tool_capabilities.full_punch_interval or 1.4) + + if tmp < 0 then + tmp = 0.0 + elseif tmp > 1 then + tmp = 1.0 + end + + damage = damage + (tool_capabilities.damage_groups[group] or 0) + * tmp * ((armor[group] or 0) / 100.0) + end + end + + if weapon then + local fire_aspect_level = mcl_enchanting.get_enchantment(weapon, "fire_aspect") + if fire_aspect_level > 0 then + mcl_burning.set_on_fire(self.object, fire_aspect_level * 4) + end + end + + -- check for tool immunity or special damage + for n = 1, #self.immune_to do + + if self.immune_to[n][1] == weapon:get_name() then + + damage = self.immune_to[n][2] or 0 + break + end + end + + -- healing + if damage <= -1 then + self.health = self.health - math_floor(damage) + return + end + + if use_cmi then + + local cancel = cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) + + if cancel then return end + end + + if tool_capabilities then + punch_interval = tool_capabilities.full_punch_interval or 1.4 + end + + -- add weapon wear manually + -- Required because we have custom health handling ("health" property) + if minetest_is_creative_enabled("") ~= true + and tool_capabilities then + if tool_capabilities.punch_attack_uses then + -- Without this delay, the wear does not work. Quite hacky ... + minetest_after(0, function(name) + local player = minetest.get_player_by_name(name) + if not player then return end + local weapon = hitter:get_wielded_item(player) + local def = weapon:get_definition() + if def.tool_capabilities and def.tool_capabilities.punch_attack_uses then + local wear = math_floor(65535/tool_capabilities.punch_attack_uses) + weapon:add_wear(wear) + hitter:set_wielded_item(weapon) + end + end, hitter:get_player_name()) + end + end + + local die = false + + -- only play hit sound and show blood effects if damage is 1 or over; lower to 0.1 to ensure armor works appropriately. + if damage >= 0.1 then + + -- 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 = self.object, --hitter, + max_hear_distance = 8 + }, true) + else + minetest_sound_play("default_punch", { + object = self.object, + max_hear_distance = 5 + }, true) + end + + damage_effect(self, damage) + + -- do damage + self.health = self.health - damage + + -- skip future functions if dead, except alerting others + if check_for_death(self, "hit", {type = "punch", puncher = hitter}) then + die = true + end + + -- knock back effect (only on full punch) + if not die + and self.knock_back + and tflp >= punch_interval then + + local v = self.object:get_velocity() + local r = 1.4 - math_min(punch_interval, 1.4) + local kb = r * 2.0 + local up = 2 + + -- if already in air then dont go up anymore when hit + if v.y ~= 0 + or self.fly then + up = 0 + end + + -- direction error check + dir = dir or {x = 0, y = 0, z = 0} + + -- check if tool already has specific knockback value + if tool_capabilities.damage_groups["knockback"] then + kb = tool_capabilities.damage_groups["knockback"] + else + kb = kb * 1.5 + end + + + local luaentity + if hitter then + luaentity = hitter:get_luaentity() + end + if hitter and is_player then + local wielditem = hitter:get_wielded_item() + kb = kb + 3 * mcl_enchanting.get_enchantment(wielditem, "knockback") + elseif luaentity and luaentity._knockback then + kb = kb + luaentity._knockback + end + + self.object:set_velocity({ + x = dir.x * kb, + y = dir.y * kb + up * 2, + z = dir.z * kb + }) + + self.pause_timer = 0.25 + end + end -- END if damage + + -- if skittish then run away + if not die and self.runaway == true and self.state ~= "flop" then + + local lp = hitter:get_pos() + local s = self.object:get_pos() + local vec = { + x = lp.x - s.x, + y = lp.y - s.y, + z = lp.z - s.z + } + + local yaw = (atan(vec.z / vec.x) + 3 * math_pi / 2) - self.rotate + + if lp.x > s.x then + yaw = yaw + math_pi + end + + yaw = set_yaw(self, yaw, 6) + self.state = "runaway" + self.runaway_timer = 0 + self.following = nil + end + + local name = hitter:get_player_name() or "" + + -- attack puncher and call other mobs for help + if self.passive == false + and self.state ~= "flop" + and (self.child == false or self.type == "monster") + and hitter:get_player_name() ~= self.owner + and not mobs.invis[ name ] then + + if not die then + -- attack whoever punched mob + self.state = "" + do_attack(self, hitter) + end + + -- alert others to the attack + local objs = minetest_get_objects_inside_radius(hitter:get_pos(), self.view_range) + local obj = nil + + for n = 1, #objs do + + obj = objs[n]:get_luaentity() + + if obj then + + -- only alert members of same mob or friends + if obj.group_attack + and obj.state ~= "attack" + and obj.owner ~= name then + if obj.name == self.name then + do_attack(obj, hitter) + elseif type(obj.group_attack) == "table" then + for i=1, #obj.group_attack do + if obj.name == obj.group_attack[i] then + do_attack(obj, hitter) + break + end + end + end + end + + -- have owned mobs attack player threat + if obj.owner == name and obj.owner_loyal then + do_attack(obj, self.object) + end + end + end + end + ]]-- +end + +--do internal per mob projectile calculations +mobs.shoot_projectile = function(self) + + local pos1 = self.object:get_pos() + --add mob eye height + pos1.y = pos1.y + self.eye_height + + local pos2 = self.attacking:get_pos() + --add player eye height + pos2.y = pos2.y + self.attacking:get_properties().eye_height + + --get direction + local dir = vector_direction(pos1,pos2) + + --call internal shoot_arrow function + self.shoot_arrow(self,pos1,dir) +end \ No newline at end of file diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/movement.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/movement.lua new file mode 100644 index 000000000..811488396 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/movement.lua @@ -0,0 +1,309 @@ +local math_pi = math.pi +local math_sin = math.sin +local math_cos = math.cos +local math_random = math.random +local HALF_PI = math_pi / 2 +local DOUBLE_PI = math_pi * 2 + +-- localize vector functions +local vector_new = vector.new +local vector_length = vector.length +local vector_multiply = vector.multiply +local vector_distance = vector.distance + +local minetest_yaw_to_dir = minetest.yaw_to_dir +local minetest_dir_to_yaw = minetest.dir_to_yaw + +local DEFAULT_JUMP_HEIGHT = 5 +local DEFAULT_FLOAT_SPEED = 4 + + + +--this is a generic float function +mobs.float = function(self) + + local current_velocity = self.object:get_velocity() + + local goal_velocity = { + x = 0, + y = DEFAULT_FLOAT_SPEED, + z = 0, + } + + local new_velocity_addition = vector.subtract(goal_velocity,current_velocity) + + new_velocity_addition.x = 0 + new_velocity_addition.z = 0 + + --smooths out mobs a bit + if vector_length(new_velocity_addition) >= 0.0001 then + self.object:add_velocity(new_velocity_addition) + end +end + + + +--[[ + _ _ +| | | | +| | __ _ _ __ __| | +| | / _` | '_ \ / _` | +| |___| (_| | | | | (_| | +\_____/\__,_|_| |_|\__,_| +]] + + +-- move mob in facing direction +--this has been modified to be internal +--internal = lua (self.yaw) +--engine = c++ (self.object:get_yaw()) +mobs.set_velocity = function(self, v) + + local yaw = (self.yaw or 0) + + local current_velocity = self.object:get_velocity() + + local goal_velocity = { + x = (math_sin(yaw) * -v), + y = 0, + z = (math_cos(yaw) * v), + } + + + local new_velocity_addition = vector.subtract(goal_velocity,current_velocity) + + if vector_length(new_velocity_addition) > vector_length(goal_velocity) then + vector.multiply(new_velocity_addition, (vector_length(goal_velocity) / vector_length(new_velocity_addition))) + end + + new_velocity_addition.y = 0 + + --smooths out mobs a bit + if vector_length(new_velocity_addition) >= 0.0001 then + self.object:add_velocity(new_velocity_addition) + end +end + + + +-- calculate mob velocity +mobs.get_velocity = function(self) + + local v = self.object:get_velocity() + + v.y = 0 + + if v then + return vector_length(v) + end + + return 0 +end + +--make mobs jump +mobs.jump = function(self, velocity) + + if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then + return + end + + --fallback velocity to allow modularity + velocity = velocity or DEFAULT_JUMP_HEIGHT + + self.object:add_velocity(vector_new(0,velocity,0)) +end + + + + + +--[[ + _____ _ +/ ___| (_) +\ `--.__ ___ _ __ ___ + `--. \ \ /\ / / | '_ ` _ \ +/\__/ /\ V V /| | | | | | | +\____/ \_/\_/ |_|_| |_| |_| +]]-- + + + + +--make mobs flop +mobs.flop = function(self, velocity) + + if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then + return false + end + + mobs.set_velocity(self, 0) + + --fallback velocity to allow modularity + velocity = velocity or DEFAULT_JUMP_HEIGHT + + --create a random direction (2d yaw) + local dir = DOUBLE_PI * math_random() + + --create a random force value + local force = math_random(0,3) + math_random() + + --convert the yaw to a direction vector then multiply it times the force + local final_additional_force = vector_multiply(minetest_yaw_to_dir(dir), force) + + --place in the "flop" velocity to make the mob flop + final_additional_force.y = velocity + + self.object:add_velocity(final_additional_force) + + return true +end + + + +-- move mob in facing direction +--this has been modified to be internal +--internal = lua (self.yaw) +--engine = c++ (self.object:get_yaw()) +mobs.set_swim_velocity = function(self, v) + + local yaw = (self.yaw or 0) + local pitch = (self.pitch or 0) + + if v == 0 then + pitch = 0 + end + + local current_velocity = self.object:get_velocity() + + local goal_velocity = { + x = (math_sin(yaw) * -v), + y = pitch, + z = (math_cos(yaw) * v), + } + + + local new_velocity_addition = vector.subtract(goal_velocity,current_velocity) + + if vector_length(new_velocity_addition) > vector_length(goal_velocity) then + vector.multiply(new_velocity_addition, (vector_length(goal_velocity) / vector_length(new_velocity_addition))) + end + + --smooths out mobs a bit + if vector_length(new_velocity_addition) >= 0.0001 then + self.object:add_velocity(new_velocity_addition) + end +end + +--[[ +______ _ +| ___| | +| |_ | |_ _ +| _| | | | | | +| | | | |_| | +\_| |_|\__, | + __/ | + |___/ +]]-- + +-- move mob in facing direction +--this has been modified to be internal +--internal = lua (self.yaw) +--engine = c++ (self.object:get_yaw()) +mobs.set_fly_velocity = function(self, v) + + local yaw = (self.yaw or 0) + local pitch = (self.pitch or 0) + + if v == 0 then + pitch = 0 + end + + local current_velocity = self.object:get_velocity() + + local goal_velocity = { + x = (math_sin(yaw) * -v), + y = pitch, + z = (math_cos(yaw) * v), + } + + + local new_velocity_addition = vector.subtract(goal_velocity,current_velocity) + + if vector_length(new_velocity_addition) > vector_length(goal_velocity) then + vector.multiply(new_velocity_addition, (vector_length(goal_velocity) / vector_length(new_velocity_addition))) + end + + --smooths out mobs a bit + if vector_length(new_velocity_addition) >= 0.0001 then + self.object:add_velocity(new_velocity_addition) + end +end + +--a quick and simple pitch calculation between two vector positions +mobs.calculate_pitch = function(pos1, pos2) + + if pos1 == nil or pos2 == nil then + return false + end + + return(minetest_dir_to_yaw(vector_new(vector_distance(vector_new(pos1.x,0,pos1.z),vector_new(pos2.x,0,pos2.z)),0,pos1.y - pos2.y)) + HALF_PI) +end + +--make mobs fly up or down based on their y difference +mobs.set_pitch_while_attacking = function(self) + local pos1 = self.object:get_pos() + local pos2 = self.attacking:get_pos() + + local pitch = mobs.calculate_pitch(pos2,pos1) + + self.pitch = pitch +end + + + +--[[ + ___ + |_ | + | |_ _ _ __ ___ _ __ + | | | | | '_ ` _ \| '_ \ +/\__/ / |_| | | | | | | |_) | +\____/ \__,_|_| |_| |_| .__/ + | | + |_| +]]-- + +--special mob jump movement +mobs.jump_move = function(self, velocity) + + if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then + return + end + + --make the mob stick for a split second + mobs.set_velocity(self,0) + + --fallback velocity to allow modularity + jump_height = DEFAULT_JUMP_HEIGHT + + local yaw = (self.yaw or 0) + + local current_velocity = self.object:get_velocity() + + local goal_velocity = { + x = (math_sin(yaw) * -velocity), + y = jump_height, + z = (math_cos(yaw) * velocity), + } + + + local new_velocity_addition = vector.subtract(goal_velocity,current_velocity) + + if vector_length(new_velocity_addition) > vector_length(goal_velocity) then + vector.multiply(new_velocity_addition, (vector_length(goal_velocity) / vector_length(new_velocity_addition))) + end + + --smooths out mobs a bit + if vector_length(new_velocity_addition) >= 0.0001 then + self.object:add_velocity(new_velocity_addition) + end +end \ No newline at end of file diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/set_up.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/set_up.lua new file mode 100644 index 000000000..1fee55217 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/set_up.lua @@ -0,0 +1,220 @@ +local math_random = math.random + +local minetest_settings = minetest.settings + +-- get entity staticdata +mobs.mob_staticdata = function(self) + +--[[ + -- remove mob when out of range unless tamed + if remove_far + and self.can_despawn + and self.remove_ok + and ((not self.nametag) or (self.nametag == "")) + and self.lifetimer <= 20 then + + minetest.log("action", "Mob "..name.." despawns in mob_staticdata at "..minetest.pos_to_string(self.object.get_pos(), 1)) + mcl_burning.extinguish(self.object) + self.object:remove() + + return ""-- nil + end +--]] + + self.remove_ok = true + self.attack = nil + self.following = nil + + if use_cmi then + self.serialized_cmi_components = cmi.serialize_components(self._cmi_components) + end + + local tmp = {} + + for _,stat in pairs(self) do + + local t = type(stat) + + if t ~= "function" + and t ~= "nil" + and t ~= "userdata" + and _ ~= "_cmi_components" then + tmp[_] = self[_] + end + end + + return minetest.serialize(tmp) +end + + +-- activate mob and reload settings +mobs.mob_activate = function(self, staticdata, def, dtime) + + -- remove monsters in peaceful mode + if self.type == "monster" and minetest_settings:get_bool("only_peaceful_mobs", false) then + mcl_burning.extinguish(self.object) + self.object:remove() + return + end + + -- load entity variables + local tmp = minetest.deserialize(staticdata) + + if tmp then + for _,stat in pairs(tmp) do + self[_] = stat + end + end + + --set up wandering + if not self.wandering then + self.wandering = true + end + + --clear animation + self.current_animation = nil + + -- select random texture, set model and size + if not self.base_texture then + + -- compatiblity with old simple mobs textures + if type(def.textures[1]) == "string" then + def.textures = {def.textures} + end + + self.base_texture = def.textures[math_random(1, #def.textures)] + self.base_mesh = def.mesh + self.base_size = self.visual_size + self.base_colbox = self.collisionbox + self.base_selbox = self.selectionbox + end + + -- for current mobs that dont have this set + if not self.base_selbox then + self.base_selbox = self.selectionbox or self.base_colbox + end + + -- set texture, model and size + local textures = self.base_texture + local mesh = self.base_mesh + local vis_size = self.base_size + local colbox = self.base_colbox + local selbox = self.base_selbox + + -- specific texture if gotten + if self.gotten == true + and def.gotten_texture then + textures = def.gotten_texture + end + + -- specific mesh if gotten + if self.gotten == true + and def.gotten_mesh then + mesh = def.gotten_mesh + end + + -- set child objects to half size + if self.child == true then + + vis_size = { + x = self.base_size.x * .5, + y = self.base_size.y * .5, + } + + if def.child_texture then + textures = def.child_texture[1] + end + + colbox = { + self.base_colbox[1] * .5, + self.base_colbox[2] * .5, + self.base_colbox[3] * .5, + self.base_colbox[4] * .5, + self.base_colbox[5] * .5, + self.base_colbox[6] * .5 + } + selbox = { + self.base_selbox[1] * .5, + self.base_selbox[2] * .5, + self.base_selbox[3] * .5, + self.base_selbox[4] * .5, + self.base_selbox[5] * .5, + self.base_selbox[6] * .5 + } + end + + if self.health == 0 then + self.health = math_random (self.hp_min, self.hp_max) + end + if self.breath == nil then + self.breath = self.breath_max + end + + -- pathfinding init + self.path = {} + self.path.way = {} -- path to follow, table of positions + self.path.lastpos = {x = 0, y = 0, z = 0} + self.path.stuck = false + self.path.following = false -- currently following path? + self.path.stuck_timer = 0 -- if stuck for too long search for path + + -- Armor groups + -- immortal=1 because we use custom health + -- handling (using "health" property) + local armor + if type(self.armor) == "table" then + armor = table.copy(self.armor) + armor.immortal = 1 + else + armor = {immortal=1, fleshy = self.armor} + end + self.object:set_armor_groups(armor) + self.old_y = self.object:get_pos().y + self.old_health = self.health + self.sounds.distance = self.sounds.distance or 10 + self.textures = textures + self.mesh = mesh + self.collisionbox = colbox + self.selectionbox = selbox + self.visual_size = vis_size + self.standing_in = "ignore" + self.standing_on = "ignore" + self.jump_sound_cooloff = 0 -- used to prevent jump sound from being played too often in short time + self.opinion_sound_cooloff = 0 -- used to prevent sound spam of particular sound types + + self.texture_mods = {} + self.object:set_texture_mod("") + + self.v_start = false + self.timer = 0 + self.blinktimer = 0 + self.blinkstatus = false + + -- check existing nametag + if not self.nametag then + self.nametag = def.nametag + end + + -- set anything changed above + self.object:set_properties(self) + + --update_tag(self) + --mobs.set_animation(self, "stand") + + -- run on_spawn function if found + if self.on_spawn and not self.on_spawn_run then + if self.on_spawn(self) then + self.on_spawn_run = true -- if true, set flag to run once only + end + end + + -- run after_activate + if def.after_activate then + def.after_activate(self, staticdata, def, dtime) + end + + if use_cmi then + self._cmi_components = cmi.activate_components(self.serialized_cmi_components) + cmi.notify_activate(self.object, dtime) + end +end \ No newline at end of file diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/sound_handling.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/sound_handling.lua new file mode 100644 index 000000000..e699a1f29 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/sound_handling.lua @@ -0,0 +1,32 @@ +local math_random = math.random + + +--generic call for sound handler for mobs (data access) +mobs.play_sound = function(self,sound) + local soundinfo = self.sounds + + if not soundinfo then + return + end + + local play_sound = soundinfo[sound] + + if not play_sound then + return + end + + mobs.play_sound_handler(self, play_sound) +end + + +--generic sound handler for mobs +mobs.play_sound_handler = function(self, sound) + local pitch = (100 + math_random(-15,15) + math_random()) / 100 + + minetest.sound_play(sound, { + object = self.object, + gain = 1.0, + max_hear_distance = self.sounds.distance, + pitch = pitch, + }, true) +end \ No newline at end of file diff --git a/mods/ENTITIES/mcl_mobs/mount.lua b/mods/ENTITIES/mcl_mobs/api/mount.lua similarity index 100% rename from mods/ENTITIES/mcl_mobs/mount.lua rename to mods/ENTITIES/mcl_mobs/api/mount.lua diff --git a/mods/ENTITIES/mcl_mobs/spawning.lua b/mods/ENTITIES/mcl_mobs/api/spawning.lua similarity index 99% rename from mods/ENTITIES/mcl_mobs/spawning.lua rename to mods/ENTITIES/mcl_mobs/api/spawning.lua index 210c6b9c6..2875b27e2 100644 --- a/mods/ENTITIES/mcl_mobs/spawning.lua +++ b/mods/ENTITIES/mcl_mobs/api/spawning.lua @@ -155,6 +155,7 @@ Overworld regular: local mobs_spawn = minetest.settings:get_bool("mobs_spawn", true) ~= false +mobs_spawn = false -- count how many mobs of one type are inside an area local count_mobs = function(pos,mobtype) diff --git a/mods/ENTITIES/mcl_mobs/init.lua b/mods/ENTITIES/mcl_mobs/init.lua index 69246b470..b0daba2c4 100644 --- a/mods/ENTITIES/mcl_mobs/init.lua +++ b/mods/ENTITIES/mcl_mobs/init.lua @@ -1,14 +1,16 @@ local path = minetest.get_modpath(minetest.get_current_modname()) +local api_path = path.."/api" + -- Mob API -dofile(path .. "/api.lua") +dofile(api_path .. "/api.lua") -- Spawning Algorithm -dofile(path .. "/spawning.lua") +dofile(api_path .. "/spawning.lua") -- Rideable Mobs -dofile(path .. "/mount.lua") +dofile(api_path .. "/mount.lua") -- Mob Items dofile(path .. "/crafts.lua") \ No newline at end of file diff --git a/mods/ENTITIES/mcl_mobs/lucky_block.lua b/mods/ENTITIES/mcl_mobs/lucky_block.lua deleted file mode 100644 index ea90de74a..000000000 --- a/mods/ENTITIES/mcl_mobs/lucky_block.lua +++ /dev/null @@ -1,8 +0,0 @@ - -if minetest.get_modpath("lucky_block") then - - lucky_block:add_blocks({ - {"dro", {"mcl_mobs:nametag"}, 1}, - {"lig"}, - }) -end diff --git a/mods/ENTITIES/mcl_mobs/todo.txt b/mods/ENTITIES/mcl_mobs/todo.txt new file mode 100644 index 000000000..7598b14ed --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/todo.txt @@ -0,0 +1 @@ +--use vector.distance to count down mob despawn timer \ No newline at end of file diff --git a/mods/ENTITIES/mobs_mc/0_gameconfig.lua b/mods/ENTITIES/mobs_mc/0_gameconfig.lua index c92ccbba5..3476bee4c 100644 --- a/mods/ENTITIES/mobs_mc/0_gameconfig.lua +++ b/mods/ENTITIES/mobs_mc/0_gameconfig.lua @@ -81,7 +81,9 @@ mobs_mc.items = { gunpowder = "tnt:gunpowder", flint_and_steel = "fire:flint_and_steel", water_source = "default:water_source", + water_flowing = "default:water_flowing", river_water_source = "default:river_water_source", + water_flowing = "default:river_water_flowing", black_dye = "dye:black", poppy = "flowers:rose", dandelion = "flowers:dandelion_yellow", diff --git a/mods/ENTITIES/mobs_mc/blaze.lua b/mods/ENTITIES/mobs_mc/blaze.lua index 847e2f4a5..d88a84515 100644 --- a/mods/ENTITIES/mobs_mc/blaze.lua +++ b/mods/ENTITIES/mobs_mc/blaze.lua @@ -17,6 +17,9 @@ mobs:register_mob("mobs_mc:blaze", { hp_max = 20, xp_min = 10, xp_max = 10, + tilt_fly = false, + hostile = true, + rotate = 270, collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.79, 0.3}, rotate = -180, visual = "mesh", @@ -35,7 +38,7 @@ mobs:register_mob("mobs_mc:blaze", { walk_velocity = .8, run_velocity = 1.6, damage = 6, - reach = 2, + reach = 4, -- don't want blaze getting too close pathfinding = 1, drops = { {name = mobs_mc.items.blaze_rod, @@ -63,7 +66,7 @@ mobs:register_mob("mobs_mc:blaze", { fall_speed = -2.25, light_damage = 0, view_range = 16, - attack_type = "dogshoot", + attack_type = "projectile", arrow = "mobs_mc:blaze_fireball", shoot_interval = 3.5, shoot_offset = 1.0, @@ -75,6 +78,13 @@ mobs:register_mob("mobs_mc:blaze", { fear_height = 0, glow = 14, fire_resistant = true, + eye_height = 0.75, + shoot_arrow = function(self, pos, dir) + -- 2-4 damage per arrow + local dmg = math.random(2,4) + mcl_bows.shoot_arrow("mobs_mc:blaze_fireball", pos, dir, self.object:get_yaw(), self.object, nil, dmg) + end, + do_custom = function(self) if self.state == "attack" and vector.distance(self.object:get_pos(), self.attack:get_pos()) < 1.2 then mcl_burning.set_on_fire(self.attack, 5) @@ -147,6 +157,7 @@ mobs:register_arrow("mobs_mc:blaze_fireball", { visual_size = {x = 0.3, y = 0.3}, textures = {"mcl_fire_fire_charge.png"}, velocity = 15, + speed = 5, -- Direct hit, no fire... just plenty of pain hit_player = function(self, player) @@ -179,7 +190,9 @@ mobs:register_arrow("mobs_mc:blaze_fireball", { -- Node hit, make fire hit_node = function(self, pos, node) - if node.name == "air" then + if node.name ~= "air" then + local pos_above = table.copy(pos) + pos_above.y = pos_above.y + 1 minetest.set_node(pos_above, {name=mobs_mc.items.fire}) else local v = self.object:get_velocity() diff --git a/mods/ENTITIES/mobs_mc/chicken.lua b/mods/ENTITIES/mobs_mc/chicken.lua index 246bf216a..2fe82293e 100644 --- a/mods/ENTITIES/mobs_mc/chicken.lua +++ b/mods/ENTITIES/mobs_mc/chicken.lua @@ -25,7 +25,7 @@ mobs:register_mob("mobs_mc:chicken", { {"mobs_mc_chicken.png"}, }, visual_size = {x=2.2, y=2.2}, - + rotate = 270, makes_footstep_sound = true, walk_velocity = 1, drops = { @@ -69,8 +69,6 @@ mobs:register_mob("mobs_mc:chicken", { on_rightclick = function(self, clicker) if mobs:feed_tame(self, clicker, 1, true, true) then return end - if mobs:protect(self, clicker) then return end - if mobs:capture_mob(self, clicker, 0, 60, 5, false, nil) then return end end, do_custom = function(self, dtime) diff --git a/mods/ENTITIES/mobs_mc/cow+mooshroom.lua b/mods/ENTITIES/mobs_mc/cow+mooshroom.lua index 48fcc8197..59b80ecb9 100644 --- a/mods/ENTITIES/mobs_mc/cow+mooshroom.lua +++ b/mods/ENTITIES/mobs_mc/cow+mooshroom.lua @@ -9,6 +9,7 @@ local cow_def = { hp_max = 10, xp_min = 1, xp_max = 3, + rotate = 270, collisionbox = {-0.45, -0.01, -0.45, 0.45, 1.39, 0.45}, visual = "mesh", mesh = "mobs_mc_cow.b3d", @@ -49,7 +50,6 @@ local cow_def = { follow = mobs_mc.follow.cow, on_rightclick = function(self, clicker) if mobs:feed_tame(self, clicker, 1, true, true) then return end - if mobs:protect(self, clicker) then return end if self.child then return @@ -70,7 +70,6 @@ local cow_def = { end return end - mobs:capture_mob(self, clicker, 0, 5, 60, false, nil) end, follow = mobs_mc.items.wheat, view_range = 10, @@ -86,7 +85,6 @@ mooshroom_def.mesh = "mobs_mc_cow.b3d" mooshroom_def.textures = { {"mobs_mc_mooshroom.png", "mobs_mc_mushroom_red.png"}, {"mobs_mc_mooshroom_brown.png", "mobs_mc_mushroom_brown.png" } } mooshroom_def.on_rightclick = function(self, clicker) if mobs:feed_tame(self, clicker, 1, true, true) then return end - if mobs:protect(self, clicker) then return end if self.child then return @@ -138,8 +136,7 @@ mooshroom_def.on_rightclick = function(self, clicker) pos.y = pos.y + 0.5 minetest.add_item(pos, {name = mobs_mc.items.mushroom_stew}) end - end - mobs:capture_mob(self, clicker, 0, 5, 60, false, nil) + end end mobs:register_mob("mobs_mc:mooshroom", mooshroom_def) diff --git a/mods/ENTITIES/mobs_mc/creeper.lua b/mods/ENTITIES/mobs_mc/creeper.lua index 0c884d569..b94a61dea 100644 --- a/mods/ENTITIES/mobs_mc/creeper.lua +++ b/mods/ENTITIES/mobs_mc/creeper.lua @@ -12,6 +12,8 @@ local S = minetest.get_translator("mobs_mc") mobs:register_mob("mobs_mc:creeper", { type = "monster", spawn_class = "hostile", + hostile = true, + rotate = 270, hp_min = 20, hp_max = 20, xp_min = 5, @@ -33,20 +35,21 @@ mobs:register_mob("mobs_mc:creeper", { explode = "tnt_explode", distance = 16, }, - makes_footstep_sound = true, + makes_footstep_sound = false, walk_velocity = 1.05, - run_velocity = 2.1, + run_velocity = 3.25, runaway_from = { "mobs_mc:ocelot", "mobs_mc:cat" }, attack_type = "explode", - + eye_height = 1.25, --hssssssssssss - explosion_strength = 3, - explosion_radius = 3.5, - explosion_damage_radius = 3.5, + explosion_strength = 10, + explosion_radius = 4, + explosion_damage_radius = 6, explosiontimer_reset_radius = 6, - reach = 3, - explosion_timer = 1.5, + reach = 1.5, + defuse_reach = 4, + explosion_timer = 0.3, allow_fuse_reset = true, stop_to_explode = true, @@ -148,6 +151,7 @@ mobs:register_mob("mobs_mc:creeper_charged", { "mobs_mc_creeper_charge.png"}, }, visual_size = {x=3, y=3}, + rotate = 270, sounds = { attack = "tnt_ignite", death = "mobs_mc_creeper_death", @@ -156,18 +160,19 @@ mobs:register_mob("mobs_mc:creeper_charged", { explode = "tnt_explode", distance = 16, }, - makes_footstep_sound = true, + makes_footstep_sound = false, walk_velocity = 1.05, run_velocity = 2.1, runaway_from = { "mobs_mc:ocelot", "mobs_mc:cat" }, attack_type = "explode", - explosion_strength = 6, - explosion_radius = 8, - explosion_damage_radius = 8, - explosiontimer_reset_radius = 6, - reach = 3, - explosion_timer = 1.5, + explosion_strength = 24, + explosion_radius = 12, + explosion_damage_radius = 18, + explosiontimer_reset_radius = 10, + reach = 1.5, + defuse_reach = 4, + explosion_timer = 0.3, allow_fuse_reset = true, stop_to_explode = true, diff --git a/mods/ENTITIES/mobs_mc/ender_dragon.lua b/mods/ENTITIES/mobs_mc/ender_dragon.lua index a6f404275..ec31be35c 100644 --- a/mods/ENTITIES/mobs_mc/ender_dragon.lua +++ b/mods/ENTITIES/mobs_mc/ender_dragon.lua @@ -46,7 +46,7 @@ mobs:register_mob("mobs_mc:enderdragon", { lava_damage = 0, fire_damage = 0, on_rightclick = nil, - attack_type = "dogshoot", + attack_type = "projectile", arrow = "mobs_mc:dragon_fireball", shoot_interval = 0.5, shoot_offset = -1.0, diff --git a/mods/ENTITIES/mobs_mc/endermite.lua b/mods/ENTITIES/mobs_mc/endermite.lua index da3922a10..e413f65b3 100644 --- a/mods/ENTITIES/mobs_mc/endermite.lua +++ b/mods/ENTITIES/mobs_mc/endermite.lua @@ -14,6 +14,7 @@ mobs:register_mob("mobs_mc:endermite", { xp_max = 3, armor = {fleshy = 100, arthropod = 100}, group_attack = true, + attack_type = "punch", collisionbox = {-0.2, -0.01, -0.2, 0.2, 0.29, 0.2}, visual = "mesh", mesh = "mobs_mc_endermite.b3d", diff --git a/mods/ENTITIES/mobs_mc/ghast.lua b/mods/ENTITIES/mobs_mc/ghast.lua index 83a10bfc4..8a800b788 100644 --- a/mods/ENTITIES/mobs_mc/ghast.lua +++ b/mods/ENTITIES/mobs_mc/ghast.lua @@ -49,7 +49,7 @@ mobs:register_mob("mobs_mc:ghast", { }, fall_damage = 0, view_range = 100, - attack_type = "dogshoot", + attack_type = "projectile", arrow = "mobs_mc:fireball", shoot_interval = 3.5, shoot_offset = -5, diff --git a/mods/ENTITIES/mobs_mc/guardian.lua b/mods/ENTITIES/mobs_mc/guardian.lua index 13c857ea3..086338da8 100644 --- a/mods/ENTITIES/mobs_mc/guardian.lua +++ b/mods/ENTITIES/mobs_mc/guardian.lua @@ -13,7 +13,7 @@ mobs:register_mob("mobs_mc:guardian", { xp_max = 10, breath_max = -1, passive = false, - attack_type = "dogfight", + attack_type = "punch", pathfinding = 1, view_range = 16, walk_velocity = 2, diff --git a/mods/ENTITIES/mobs_mc/guardian_elder.lua b/mods/ENTITIES/mobs_mc/guardian_elder.lua index 089f6e38f..087e85353 100644 --- a/mods/ENTITIES/mobs_mc/guardian_elder.lua +++ b/mods/ENTITIES/mobs_mc/guardian_elder.lua @@ -15,7 +15,7 @@ mobs:register_mob("mobs_mc:guardian_elder", { xp_max = 10, breath_max = -1, passive = false, - attack_type = "dogfight", + attack_type = "punch", pathfinding = 1, view_range = 16, walk_velocity = 2, diff --git a/mods/ENTITIES/mobs_mc/horse.lua b/mods/ENTITIES/mobs_mc/horse.lua index 938a6b6ac..0e0246d4e 100644 --- a/mods/ENTITIES/mobs_mc/horse.lua +++ b/mods/ENTITIES/mobs_mc/horse.lua @@ -281,10 +281,6 @@ local horse = { return end - if mobs:protect(self, clicker) then - return - end - -- Make sure tamed horse is mature and being clicked by owner only if self.tamed and not self.child and self.owner == clicker:get_player_name() then @@ -356,9 +352,6 @@ local horse = { self.object:set_properties({stepheight = 1.1}) mobs.attach(self, clicker) - -- Used to capture horse - elseif not self.driver and iname ~= "" then - mobs:capture_mob(self, clicker, 0, 5, 60, false, nil) end end end, diff --git a/mods/ENTITIES/mobs_mc/llama.lua b/mods/ENTITIES/mobs_mc/llama.lua index 8ff82b502..e0c353ad8 100644 --- a/mods/ENTITIES/mobs_mc/llama.lua +++ b/mods/ENTITIES/mobs_mc/llama.lua @@ -133,7 +133,6 @@ mobs:register_mob("mobs_mc:llama", { -- Feed with anything else if mobs:feed_tame(self, clicker, 1, false, true) then return end end - if mobs:protect(self, clicker) then return end -- Make sure tamed llama is mature and being clicked by owner only if self.tamed and not self.child and self.owner == clicker:get_player_name() then @@ -183,9 +182,6 @@ mobs:register_mob("mobs_mc:llama", { mobs.attach(self, clicker) end - -- Used to capture llama - elseif not self.driver and clicker:get_wielded_item():get_name() ~= "" then - mobs:capture_mob(self, clicker, 0, 5, 60, false, nil) end end, diff --git a/mods/ENTITIES/mobs_mc/ocelot.lua b/mods/ENTITIES/mobs_mc/ocelot.lua index f3c8c87ae..bb62a54fa 100644 --- a/mods/ENTITIES/mobs_mc/ocelot.lua +++ b/mods/ENTITIES/mobs_mc/ocelot.lua @@ -121,8 +121,6 @@ cat.sounds = { } cat.on_rightclick = function(self, clicker) if mobs:feed_tame(self, clicker, 1, true, false) then return end - if mobs:capture_mob(self, clicker, 0, 60, 5, false, nil) then return end - if mobs:protect(self, clicker) then return end if self.child then return end diff --git a/mods/ENTITIES/mobs_mc/parrot.lua b/mods/ENTITIES/mobs_mc/parrot.lua index 5efcb191b..fb1bc6f9b 100644 --- a/mods/ENTITIES/mobs_mc/parrot.lua +++ b/mods/ENTITIES/mobs_mc/parrot.lua @@ -19,11 +19,13 @@ mobs:register_mob("mobs_mc:parrot", { hp_max = 6, xp_min = 1, xp_max = 3, + tilt_fly = true, collisionbox = {-0.25, -0.01, -0.25, 0.25, 0.89, 0.25}, visual = "mesh", mesh = "mobs_mc_parrot.b3d", textures = {{"mobs_mc_parrot_blue.png"},{"mobs_mc_parrot_green.png"},{"mobs_mc_parrot_grey.png"},{"mobs_mc_parrot_red_blue.png"},{"mobs_mc_parrot_yellow_blue.png"}}, visual_size = {x=3, y=3}, + rotate = 270, walk_velocity = 3, run_velocity = 5, sounds = { @@ -84,8 +86,6 @@ mobs:register_mob("mobs_mc:parrot", { -- Feed to tame, but not breed if mobs:feed_tame(self, clicker, 1, false, true) then return end - if mobs:protect(self, clicker) then return end - if mobs:capture_mob(self, clicker, 0, 50, 80, false, nil) then return end end, }) diff --git a/mods/ENTITIES/mobs_mc/pig.lua b/mods/ENTITIES/mobs_mc/pig.lua index b7cdf1afe..2751a8beb 100644 --- a/mods/ENTITIES/mobs_mc/pig.lua +++ b/mods/ENTITIES/mobs_mc/pig.lua @@ -95,7 +95,6 @@ mobs:register_mob("mobs_mc:pig", { if wielditem:get_name() ~= mobs_mc.items.carrot_on_a_stick then if mobs:feed_tame(self, clicker, 1, true, true) then return end end - if mobs:protect(self, clicker) then return end if self.child then return @@ -163,10 +162,6 @@ mobs:register_mob("mobs_mc:pig", { inv:set_stack("main",self.driver:get_wield_index(), wielditem) end return - - -- Capture pig - elseif not self.driver and clicker:get_wielded_item():get_name() ~= "" then - mobs:capture_mob(self, clicker, 0, 5, 60, false, nil) end end, diff --git a/mods/ENTITIES/mobs_mc/polar_bear.lua b/mods/ENTITIES/mobs_mc/polar_bear.lua index 5d2853f6d..f1772be99 100644 --- a/mods/ENTITIES/mobs_mc/polar_bear.lua +++ b/mods/ENTITIES/mobs_mc/polar_bear.lua @@ -30,7 +30,7 @@ mobs:register_mob("mobs_mc:polar_bear", { walk_velocity = 1.2, run_velocity = 2.4, group_attack = true, - attack_type = "dogfight", + attack_type = "punch", drops = { -- 3/4 chance to drop raw fish (poor approximation) {name = mobs_mc.items.fish_raw, diff --git a/mods/ENTITIES/mobs_mc/rabbit.lua b/mods/ENTITIES/mobs_mc/rabbit.lua index 74bdffcd8..a7d5d02d9 100644 --- a/mods/ENTITIES/mobs_mc/rabbit.lua +++ b/mods/ENTITIES/mobs_mc/rabbit.lua @@ -61,8 +61,6 @@ local rabbit = { on_rightclick = function(self, clicker) -- Feed, tame protect or capture if mobs:feed_tame(self, clicker, 1, true, true) then return end - if mobs:protect(self, clicker) then return end - if mobs:capture_mob(self, clicker, 0, 50, 80, false, nil) then return end end, do_custom = function(self) -- Easter egg: Change texture if rabbit is named “Toast” diff --git a/mods/ENTITIES/mobs_mc/sheep.lua b/mods/ENTITIES/mobs_mc/sheep.lua index d82df8cf9..e6368a3f8 100644 --- a/mods/ENTITIES/mobs_mc/sheep.lua +++ b/mods/ENTITIES/mobs_mc/sheep.lua @@ -195,7 +195,6 @@ mobs:register_mob("mobs_mc:sheep", { local item = clicker:get_wielded_item() if mobs:feed_tame(self, clicker, 1, true, true) then return end - if mobs:protect(self, clicker) then return end if item:get_name() == mobs_mc.items.shears and not self.gotten and not self.child then self.gotten = true @@ -251,7 +250,6 @@ mobs:register_mob("mobs_mc:sheep", { end return end - if mobs:capture_mob(self, clicker, 0, 5, 70, false, nil) then return end end, on_breed = function(parent1, parent2) -- Breed sheep and choose a fur color for the child. diff --git a/mods/ENTITIES/mobs_mc/shulker.lua b/mods/ENTITIES/mobs_mc/shulker.lua index 8000d0937..b6a4a6199 100644 --- a/mods/ENTITIES/mobs_mc/shulker.lua +++ b/mods/ENTITIES/mobs_mc/shulker.lua @@ -14,7 +14,7 @@ local S = minetest.get_translator("mobs_mc") mobs:register_mob("mobs_mc:shulker", { type = "monster", spawn_class = "hostile", - attack_type = "shoot", + attack_type = "projectile", shoot_interval = 0.5, arrow = "mobs_mc:shulkerbullet", shoot_offset = 0.5, diff --git a/mods/ENTITIES/mobs_mc/silverfish.lua b/mods/ENTITIES/mobs_mc/silverfish.lua index 433211503..6ed9e6dca 100644 --- a/mods/ENTITIES/mobs_mc/silverfish.lua +++ b/mods/ENTITIES/mobs_mc/silverfish.lua @@ -43,7 +43,7 @@ mobs:register_mob("mobs_mc:silverfish", { run_start = 0, run_end = 20, }, view_range = 16, - attack_type = "dogfight", + attack_type = "punch", damage = 1, reach = 1, }) diff --git a/mods/ENTITIES/mobs_mc/skeleton+stray.lua b/mods/ENTITIES/mobs_mc/skeleton+stray.lua index 05b829bcd..dfcab465a 100644 --- a/mods/ENTITIES/mobs_mc/skeleton+stray.lua +++ b/mods/ENTITIES/mobs_mc/skeleton+stray.lua @@ -15,11 +15,15 @@ local mod_bows = minetest.get_modpath("mcl_bows") ~= nil local skeleton = { type = "monster", spawn_class = "hostile", + hostile = true, + rotate = 270, hp_min = 20, hp_max = 20, xp_min = 6, xp_max = 6, breath_max = -1, + eye_height = 1.5, + projectile_cooldown = 1.5, armor = {undead = 100, fleshy = 100}, collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.98, 0.3}, pathfinding = 1, @@ -42,7 +46,7 @@ local skeleton = { walk_velocity = 1.2, run_velocity = 2.4, damage = 2, - reach = 2, + reach = 3, drops = { {name = mobs_mc.items.arrow, chance = 1, @@ -74,6 +78,8 @@ local skeleton = { walk_speed = 15, walk_start = 40, walk_end = 60, + run_start = 40, + run_end = 60, run_speed = 30, shoot_start = 70, shoot_end = 90, @@ -85,12 +91,12 @@ local skeleton = { ignited_by_sunlight = true, view_range = 16, fear_height = 4, - attack_type = "dogshoot", + attack_type = "projectile", arrow = "mcl_bows:arrow_entity", shoot_arrow = function(self, pos, dir) if mod_bows then -- 2-4 damage per arrow - local dmg = math.max(4, math.random(2, 8)) + local dmg = math.random(2,4) mcl_bows.shoot_arrow("mcl_bows:arrow", pos, dir, self.object:get_yaw(), self.object, nil, dmg) end end, diff --git a/mods/ENTITIES/mobs_mc/skeleton_wither.lua b/mods/ENTITIES/mobs_mc/skeleton_wither.lua index c089850f4..a6a140a6e 100644 --- a/mods/ENTITIES/mobs_mc/skeleton_wither.lua +++ b/mods/ENTITIES/mobs_mc/skeleton_wither.lua @@ -86,7 +86,7 @@ mobs:register_mob("mobs_mc:witherskeleton", { fire_damage = 0, light_damage = 0, view_range = 16, - attack_type = "dogfight", + attack_type = "punch", dogshoot_switch = 1, dogshoot_count_max =0.5, fear_height = 4, diff --git a/mods/ENTITIES/mobs_mc/slime+magma_cube.lua b/mods/ENTITIES/mobs_mc/slime+magma_cube.lua index 6c8000a50..b29ccd454 100644 --- a/mods/ENTITIES/mobs_mc/slime+magma_cube.lua +++ b/mods/ENTITIES/mobs_mc/slime+magma_cube.lua @@ -64,6 +64,7 @@ local slime_big = { hp_max = 16, xp_min = 4, xp_max = 4, + rotate = 270, collisionbox = {-1.02, -0.01, -1.02, 1.02, 2.03, 1.02}, visual_size = {x=12.5, y=12.5}, textures = {{"mobs_mc_slime.png"}}, @@ -98,8 +99,9 @@ local slime_big = { }, fall_damage = 0, view_range = 16, - attack_type = "dogfight", + attack_type = "jump_punch", passive = false, + jump_only = true, jump = true, walk_velocity = 2.5, run_velocity = 2.5, @@ -311,6 +313,7 @@ local magma_cube_big = { }, walk_velocity = 4, run_velocity = 4, + rotate = 270, damage = 6, reach = 3, armor = 53, @@ -337,12 +340,13 @@ local magma_cube_big = { }, water_damage = 0, lava_damage = 0, - fire_damage = 0, + fire_damage = 0, light_damage = 0, fall_damage = 0, view_range = 16, - attack_type = "dogfight", + attack_type = "jump_punch", passive = false, + jump_only = true, jump = true, jump_height = 8, walk_chance = 0, diff --git a/mods/ENTITIES/mobs_mc/spider.lua b/mods/ENTITIES/mobs_mc/spider.lua index bb5e29eb1..95179310b 100644 --- a/mods/ENTITIES/mobs_mc/spider.lua +++ b/mods/ENTITIES/mobs_mc/spider.lua @@ -17,7 +17,7 @@ local spider = { spawn_class = "hostile", passive = false, docile_by_day = true, - attack_type = "dogfight", + attack_type = "punch", pathfinding = 1, damage = 2, reach = 2, diff --git a/mods/ENTITIES/mobs_mc/squid.lua b/mods/ENTITIES/mobs_mc/squid.lua index cf794ea5b..53e99f7f9 100644 --- a/mods/ENTITIES/mobs_mc/squid.lua +++ b/mods/ENTITIES/mobs_mc/squid.lua @@ -16,6 +16,8 @@ mobs:register_mob("mobs_mc:squid", { xp_min = 1, xp_max = 3, armor = 100, + rotate = 270, + tilt_swim = true, -- FIXME: If the squid is near the floor, it turns black collisionbox = {-0.4, 0.0, -0.4, 0.4, 0.9, 0.4}, visual = "mesh", @@ -47,8 +49,7 @@ mobs:register_mob("mobs_mc:squid", { }, visual_size = {x=3, y=3}, makes_footstep_sound = false, - fly = true, - fly_in = { mobs_mc.items.water_source, mobs_mc.items.river_water_source }, + swim = true, breathes_in_water = true, jump = false, view_range = 16, diff --git a/mods/ENTITIES/mobs_mc/vex.lua b/mods/ENTITIES/mobs_mc/vex.lua index cccdebe7a..c2ce2a87a 100644 --- a/mods/ENTITIES/mobs_mc/vex.lua +++ b/mods/ENTITIES/mobs_mc/vex.lua @@ -14,7 +14,7 @@ mobs:register_mob("mobs_mc:vex", { spawn_class = "hostile", pathfinding = 1, passive = false, - attack_type = "dogfight", + attack_type = "punch", physical = false, hp_min = 14, hp_max = 14, diff --git a/mods/ENTITIES/mobs_mc/villager_evoker.lua b/mods/ENTITIES/mobs_mc/villager_evoker.lua index abe0e9ca2..86338eb14 100644 --- a/mods/ENTITIES/mobs_mc/villager_evoker.lua +++ b/mods/ENTITIES/mobs_mc/villager_evoker.lua @@ -34,7 +34,7 @@ mobs:register_mob("mobs_mc:evoker", { walk_velocity = 0.2, run_velocity = 1.4, group_attack = true, - attack_type = "dogfight", + attack_type = "punch", -- Summon vexes custom_attack = function(self, to_attack) local r = pr:next(2,4) diff --git a/mods/ENTITIES/mobs_mc/villager_illusioner.lua b/mods/ENTITIES/mobs_mc/villager_illusioner.lua index 0bbe2a5f6..e4642c847 100644 --- a/mods/ENTITIES/mobs_mc/villager_illusioner.lua +++ b/mods/ENTITIES/mobs_mc/villager_illusioner.lua @@ -9,7 +9,7 @@ local mod_bows = minetest.get_modpath("mcl_bows") ~= nil mobs:register_mob("mobs_mc:illusioner", { type = "monster", spawn_class = "hostile", - attack_type = "shoot", + attack_type = "projectile", shoot_interval = 2.5, shoot_offset = 1.5, arrow = "mcl_bows:arrow_entity", diff --git a/mods/ENTITIES/mobs_mc/villager_vindicator.lua b/mods/ENTITIES/mobs_mc/villager_vindicator.lua index 56b295066..19b2c7a7f 100644 --- a/mods/ENTITIES/mobs_mc/villager_vindicator.lua +++ b/mods/ENTITIES/mobs_mc/villager_vindicator.lua @@ -36,7 +36,7 @@ mobs:register_mob("mobs_mc:vindicator", { reach = 2, walk_velocity = 1.2, run_velocity = 2.4, - attack_type = "dogfight", + attack_type = "punch", drops = { {name = mobs_mc.items.emerald, chance = 1, diff --git a/mods/ENTITIES/mobs_mc/villager_zombie.lua b/mods/ENTITIES/mobs_mc/villager_zombie.lua index b90823629..6c3823d1b 100644 --- a/mods/ENTITIES/mobs_mc/villager_zombie.lua +++ b/mods/ENTITIES/mobs_mc/villager_zombie.lua @@ -28,6 +28,9 @@ local professions = { mobs:register_mob("mobs_mc:villager_zombie", { type = "monster", spawn_class = "hostile", + hostile = true, + rotate = 270, + eye_height = 1.65, hp_min = 20, hp_max = 20, xp_min = 5, @@ -50,8 +53,8 @@ mobs:register_mob("mobs_mc:villager_zombie", { damage = 3, reach = 2, walk_velocity = 1.2, - run_velocity = 2.4, - attack_type = "dogfight", + run_velocity = 3.5, + attack_type = "punch", group_attack = true, drops = { {name = mobs_mc.items.rotten_flesh, diff --git a/mods/ENTITIES/mobs_mc/witch.lua b/mods/ENTITIES/mobs_mc/witch.lua index f9f9b8d1f..83174313f 100644 --- a/mods/ENTITIES/mobs_mc/witch.lua +++ b/mods/ENTITIES/mobs_mc/witch.lua @@ -33,7 +33,7 @@ mobs:register_mob("mobs_mc:witch", { run_velocity = 2.4, pathfinding = 1, group_attack = true, - attack_type = "dogshoot", + attack_type = "projectile", arrow = "mobs_mc:potion_arrow", shoot_interval = 2.5, shoot_offset = 1, diff --git a/mods/ENTITIES/mobs_mc/wither.lua b/mods/ENTITIES/mobs_mc/wither.lua index 2d53cc547..6a63b2f82 100644 --- a/mods/ENTITIES/mobs_mc/wither.lua +++ b/mods/ENTITIES/mobs_mc/wither.lua @@ -52,7 +52,7 @@ mobs:register_mob("mobs_mc:wither", { }, lava_damage = 0, fire_damage = 0, - attack_type = "dogshoot", + attack_type = "projectile", explosion_strength = 8, dogshoot_stop = true, arrow = "mobs_mc:wither_skull", diff --git a/mods/ENTITIES/mobs_mc/wolf.lua b/mods/ENTITIES/mobs_mc/wolf.lua index b1c077d46..00a41b685 100644 --- a/mods/ENTITIES/mobs_mc/wolf.lua +++ b/mods/ENTITIES/mobs_mc/wolf.lua @@ -147,11 +147,7 @@ dog.specific_attack = nil dog.on_rightclick = function(self, clicker) local item = clicker:get_wielded_item() - if mobs:protect(self, clicker) then - return - elseif item:get_name() ~= "" and mobs:capture_mob(self, clicker, 0, 2, 80, false, nil) then - return - elseif is_food(item:get_name()) then + if is_food(item:get_name()) then -- Feed to increase health local hp = self.health local hp_add = 0 diff --git a/mods/ENTITIES/mobs_mc/zombie.lua b/mods/ENTITIES/mobs_mc/zombie.lua index 1be47848b..ecb79621d 100644 --- a/mods/ENTITIES/mobs_mc/zombie.lua +++ b/mods/ENTITIES/mobs_mc/zombie.lua @@ -48,6 +48,8 @@ table.insert(drops_zombie, { local zombie = { type = "monster", spawn_class = "hostile", + hostile = true, + rotate = 270, hp_min = 20, hp_max = 20, xp_min = 5, @@ -73,8 +75,9 @@ local zombie = { damage = "mobs_mc_zombie_hurt", distance = 16, }, - walk_velocity = .8, - run_velocity = 1.6, + eye_height = 1.65, + walk_velocity = 1, + run_velocity = 3.5, damage = 3, reach = 2, fear_height = 4, @@ -92,7 +95,8 @@ local zombie = { ignited_by_sunlight = true, sunlight_damage = 2, view_range = 16, - attack_type = "dogfight", + attack_type = "punch", + punch_timer_cooloff = 0.5, harmed_by_heal = true, } diff --git a/mods/ENTITIES/mobs_mc/zombiepig.lua b/mods/ENTITIES/mobs_mc/zombiepig.lua index ebd8ce485..af1da2962 100644 --- a/mods/ENTITIES/mobs_mc/zombiepig.lua +++ b/mods/ENTITIES/mobs_mc/zombiepig.lua @@ -14,13 +14,16 @@ local pigman = { -- type="animal", passive=false: This combination is needed for a neutral mob which becomes hostile, if attacked type = "animal", passive = false, + neutral = true, + rotate = 270, spawn_class = "passive", + hostile_cooldown = 15, --seconds hp_min = 20, hp_max = 20, xp_min = 6, xp_max = 6, armor = {undead = 90, fleshy = 90}, - attack_type = "dogfight", + attack_type = "punch", group_attack = { "mobs_mc:pigman", "mobs_mc:baby_pigman" }, damage = 9, reach = 2,