From e773fab1211c8c17f4bff52238544c1018efb71a Mon Sep 17 00:00:00 2001 From: ElCeejo <40281901+ElCeejo@users.noreply.github.com> Date: Thu, 10 Feb 2022 18:00:06 -0800 Subject: [PATCH] New Mobs, Switch to Creatura --- api/api.lua | 2460 ++++++++++++++++++---------------------- api/behaviors.lua | 1582 ++++++++++++++++++++++++++ api/lasso.lua | 360 ++++++ api/spawning.lua | 316 ++++++ api/storage.lua | 20 + craftitems.lua | 810 +++++++++---- init.lua | 201 ++-- mobs/bat.lua | 274 +++++ mobs/bird.lua | 182 +++ mobs/cat.lua | 451 ++++---- mobs/chicken.lua | 356 ++---- mobs/cow.lua | 282 ++--- mobs/frog.lua | 195 ++++ mobs/horse.lua | 383 ++++--- mobs/pig.lua | 233 ++-- mobs/reindeer.lua | 251 ++-- mobs/sheep.lua | 310 +++-- mobs/tropical_fish.lua | 89 ++ mobs/turkey.lua | 216 ++-- mobs/wolf.lua | 400 ++++--- mod.conf | 6 +- settingtypes.txt | 14 +- 22 files changed, 6144 insertions(+), 3247 deletions(-) create mode 100644 api/behaviors.lua create mode 100644 api/lasso.lua create mode 100644 api/spawning.lua create mode 100644 api/storage.lua create mode 100644 mobs/bat.lua create mode 100644 mobs/bird.lua create mode 100644 mobs/frog.lua create mode 100644 mobs/tropical_fish.lua diff --git a/api/api.lua b/api/api.lua index 59d7eba..aad8d6a 100644 --- a/api/api.lua +++ b/api/api.lua @@ -1,125 +1,60 @@ -------------- ----- API ---- -------------- --- Ver 0.2 -- +--------- +-- API -- +--------- -local hitbox = mob_core.get_hitbox +animalia.walkable_nodes = {} -local find_string = mob_core.find_val +minetest.register_on_mods_loaded(function() + for name in pairs(minetest.registered_nodes) do + if name ~= "air" and name ~= "ignore" then + if minetest.registered_nodes[name].walkable then + table.insert(animalia.walkable_nodes, name) + end + end + end +end) + +-- Math -- + +local pi = math.pi +local random = math.random +local abs = math.abs +local deg = math.deg + +-- Vector Math -- + +local vec_dir = vector.direction +local vec_add = vector.add +local vec_sub = vector.subtract +local vec_multi = vector.multiply +local vec_divide = vector.divide +local vec_len = vector.length + +local dir2yaw = minetest.dir_to_yaw +local yaw2dir = minetest.yaw_to_dir + +-------------- +-- Settings -- +-------------- local creative = minetest.settings:get_bool("creative_mode") -local fancy_step = minetest.settings:get_bool("animalia_fancy_step") +--------------------- +-- Local Utilities -- +--------------------- ----------- --- Math -- ----------- - -local random = math.random -local min = math.min -local pi = math.pi -local abs = math.abs -local ceil = math.ceil -local floor = math.floor -local function diff(a, b) -- Get difference between 2 angles - return math.atan2(math.sin(b - a), math.cos(b - a)) -end -local function round(x) -- Round to nearest multiple of 0.5 - return x + 0.5 - (x + 0.5) % 1 -end - -local function clamp(num, min, max) - if num < min then - num = min - elseif num > max then - num = max - end - return num -end - -local yaw2dir = minetest.yaw_to_dir -local dir2yaw = minetest.dir_to_yaw - -local vec_dist = vector.distance -local vec_dir = vector.direction - -local function dist_2d(pos1, pos2) - local a = vector.new(pos1.x, 0, pos1.z) - local b = vector.new(pos2.x, 0, pos2.z) - return vec_dist(a, b) -end - -local function get_average_pos(vectors) - local sum = {x = 0, y = 0, z = 0} - for _, vec in pairs(vectors) do sum = vector.add(sum, vec) end - local avg = vector.divide(sum, #vectors) - avg.x = math.floor(avg.x) + 0.5 - avg.y = math.floor(avg.y) + 0.5 - avg.z = math.floor(avg.z) + 0.5 - return avg -end - -local function pos_to_neighbor(self, pos2) - local pos = self.object:get_pos() - local dir = vector.direction(pos, pos2) - local neighbor = self._neighbors[mobkit.dir2neighbor(dir)] - local vec = { - x = neighbor.x, - y = 0, - z = neighbor.z - } - return vector.add(pos, vec) -end - -local function get_random_offset(pos, range) - if not pos then return nil end - range = (range * 0.5) or 3 - local pos_x = pos.x + random(-range, range) - local pos_z = pos.z + random(-range, range) - local node = nil - for i = -1, 1 do - i_pos = vector.new(pos_x, pos.y + i, pos_z) - if minetest.registered_nodes[minetest.get_node(vector.new(i_pos.x, i_pos.y - 1, i_pos.z)).name].walkable then - node = i_pos - break - end +function animalia.correct_name(str) + if str then + if str:match(":") then str = str:split(":")[2] end + return (string.gsub(" " .. str, "%W%l", string.upper):sub(2):gsub("_", " ")) end - if not node then return nil end - return node end -local is_movable = mob_core.is_moveable +local correct_name = animalia.correct_name -local function walkable(pos) - return minetest.registered_nodes[minetest.get_node(pos).name].walkable -end - -------------------- --- API Functions -- -------------------- - -function animalia.is_prey(name1, name2) - if name1 == name2 then return 0 end - local def1 = minetest.registered_entities[name1] - local def2 = minetest.registered_entities[name2] - if find_string(paleotest.mobkit_mobs, name2) - and def1.follow - and def2.drops then - for _, drop in ipairs(def2.drops) do - if drop.name - and find_string(def1.follow, drop.name) - and def1.prey_params["height"] >= get_height(name2) - and def1.prey_params["health"] >= def2.max_hp then - -- Mob is prey - return 2 - end - end - -- Mob can be attacked - return 1 - end - -- Not a mobkit mob - return 0 -end +---------------------- +-- Global Utilities -- +---------------------- function animalia.particle_spawner(pos, texture, type, min_pos, max_pos) type = type or "float" @@ -154,10 +89,10 @@ function animalia.particle_spawner(pos, texture, type, min_pos, max_pos) time = 0.25, minpos = {x = pos.x - 7/16, y = pos.y + 0.6, z = pos.z - 7/16}, maxpos = {x = pos.x + 7/16, y = pos.y + 0.6, z = pos.z + 7/16}, - minvel = vector.new(-1, 2, -1), - maxvel = vector.new(1, 5, 1), - minacc = vector.new(0, -9.81, 0), - maxacc = vector.new(0, -9.81, 0), + minvel = {x = -1, y = 2, z = -1}, + maxvel = {x = 1, y = 5, z = 1}, + minacc = {x = 0, y = -9.81, z = 0}, + maxacc = {x = 0, y = -9.81, z = 0}, minsize = 2, maxsize = 4, collisiondetection = true, @@ -166,1258 +101,137 @@ function animalia.particle_spawner(pos, texture, type, min_pos, max_pos) end end -function animalia.can_reach(self, pos2) - if pos2 then - local pos = mobkit.get_stand_pos(self) - local box = hitbox(self) - local path_data = mob_core.find_path(pos, pos2, box[4] - 0.1, self.height, 100) - if path_data and #path_data > 2 then - return true, path_data - end - end - return false +function animalia.get_average_pos(vectors) + local sum = {x = 0, y = 0, z = 0} + for _, vec in pairs(vectors) do sum = vec_add(sum, vec) end + return vec_divide(sum, #vectors) end -function animalia.find_collision(self, dir) - local pos = mobkit.get_stand_pos(self) - local pos2 = vector.add(pos, vector.multiply(dir, 16)) - local ray = minetest.raycast(pos, pos2, false, false) - for pointed_thing in ray do - if pointed_thing.type == "node" then - return pointed_thing.under +---------------------- +-- Entity Utilities -- +---------------------- + +function animalia.get_group_positions(name, pos, radius) + local objects = minetest.get_objects_in_area(vec_sub(pos, radius), vec_add(pos, radius)) + local group = {} + for i = 1, #objects do + local object = objects[i] + if object + and object:get_luaentity() + and object:get_luaentity().name == name then + table.insert(group, object:get_pos()) end end - return nil + return group end +function animalia.get_group(self) + local pos = self.object:get_pos() + local radius = self.tracking_range + local objects = minetest.get_objects_in_area(vec_sub(pos, radius), vec_add(pos, radius)) + local group = {} + for i = 1, #objects do + local object = objects[i] + if object + and object ~= self.object + and object:get_luaentity() + and object:get_luaentity().name == self.name then + table.insert(group, object) + end + end + return group +end + + function animalia.get_nearby_mate(self, name) - for _, obj in ipairs(self.nearby_objects) do - if mobkit.is_alive(obj) - and not obj:is_player() - and obj:get_luaentity().name == name - and obj:get_luaentity().gender ~= self.gender - and obj:get_luaentity().breeding then - return obj + local objects = minetest.get_objects_inside_radius(self:get_center_pos(), self.tracking_range) + for _, object in ipairs(objects) do + if creatura.is_alive(object) + and not object:is_player() + and object:get_luaentity().name == name + and object:get_luaentity().gender ~= self.gender + and object:get_luaentity().breeding then + return object end end end -function animalia.feed_tame(self, clicker, feed_count, tame, breed) - local item = clicker:get_wielded_item() - local pos = self.object:get_pos() - local mob_name = mob_core.get_name_proper(self.name) - if mob_core.follow_holding(self, clicker) then - if not creative then - item:take_item() - clicker:set_wielded_item(item) - end - mobkit.heal(self, self.max_hp/feed_count) - if self.hp >= self.max_hp then - self.hp = self.max_hp - end - self.food = mobkit.remember(self, "food", self.food + 1) +------------------- +-- Mob Functions -- +------------------- - local minppos = vector.add(pos, hitbox(self)[4]) - local maxppos = vector.subtract(pos, hitbox(self)[4]) - local def = minetest.registered_items[item:get_name()] - local texture = def.inventory_image - if not texture or texture == "" then - texture = def.wield_image - if def.tiles then - texture = def.tiles[1] - end +function animalia.initialize_api(self) + self.gender = self:recall("gender") or nil + if not self.gender then + local genders = {"male", "female"} + self.gender = self:memorize("gender", genders[random(2)]) + end + self.food = self:recall("food") or 0 + self.gotten = self:recall("gotten") or false + self.breeding = false + self.breeding_cooldown = self:recall("breeding_cooldown") or 0 + if self.growth_scale then + self:memorize("growth_scale", self.growth_scale) -- This is for spawning children + end + self.growth_scale = self:recall("growth_scale") or 1 + self:set_scale(self.growth_scale) + if self.growth_scale < 0.8 + and self.child_textures then + if not self.texture_no then + self.texture_no = random(#self.child_textures) end - texture = texture .. "^[resize:8x8" -- Crops image - minetest.add_particlespawner({ - amount = 12*self.height, - time = 0.1, - minpos = minppos, - maxpos = maxppos, - minvel = {x=-1, y=1, z=-1}, - maxvel = {x=1, y=2, z=1}, - minacc = {x=0, y=-5, z=0}, - maxacc = {x=0, y=-9, z=0}, - minexptime = 1, - maxexptime = 1, - minsize = 2*self.height, - maxsize = 3*self.height, - collisiondetection = true, - vertical = false, - texture = texture, - }) - if self.food >= feed_count then - self.food = mobkit.remember(self, "food", 0) - if tame - and not self.tamed - and self.follow[1] == item:get_name() then - mob_core.set_owner(self, clicker:get_player_name()) - minetest.chat_send_player(clicker:get_player_name(), mob_name.." has been tamed!") - mobkit.clear_queue_high(self) - animalia.particle_spawner(pos, "mob_core_green_particle.png", "float", minppos, maxppos) - end - if breed then - if self.child then return false end - if self.breeding then return false end - if self.breeding_cooldown <= 0 then - self.breeding = true - animalia.particle_spawner(pos, "heart.png", "float", minppos, maxppos) + self:set_texture(self.texture_no, self.child_textures) + return + elseif self.growth_scale > 0.7 + and self.male_textures + and self.female_textures then + if not self.texture_no then + self.texture_no = random(#self[self.gender .. "_textures"]) + end + self:set_texture(self.texture_no, self[self.gender .. "_textures"]) + return + end +end + +function animalia.step_timers(self) + self.breeding_cooldown = self.breeding_cooldown - self.dtime + if self.breeding + and self.breeding_cooldown <= 30 then + self.breeding = false + end + self:memorize("breeding_cooldown", self.breeding_cooldown) +end + +function animalia.do_growth(self, interval) + if self.growth_scale + and self.growth_scale < 0.9 then + if self:timer(interval) then + self.growth_scale = self.growth_scale + 0.1 + self:set_scale(self.growth_scale) + if self.growth_scale < 0.8 + and self.child_textures then + local tex_no = self.texture_no + if not self.child_textures[tex_no] then + tex_no = 1 end - end - end - end - return false -end - -local function is_within_reach(self, target) - local dist = vec_dist(mobkit.get_stand_pos(self), mobkit.get_stand_pos(target)) - (hitbox(self)[4] + hitbox(target)[4]) - if dist <= self.reach then - return true - end - return false -end - -local function is_on_ground(object) - if object then - local pos = object:get_pos() - local sub = 1 - if not object:is_player() then - sub = math.abs(hitbox(object)[2]) + 1 - end - pos.y = pos.y - sub - if minetest.registered_nodes[minetest.get_node(pos).name].walkable then - return true - end - pos.y = pos.y - 1 - if minetest.registered_nodes[minetest.get_node(pos).name].walkable then - return true - end - end - return false -end - -local function get_height(name) - local def = minetest.registered_entities[name] - return abs(def.collisionbox[2]) + abs(def.collisionbox[5]) -end - ------------------- --- LQ Functions -- ------------------- - -function animalia.lq_follow_path(self, path_data, speed_factor, anim) - speed_factor = speed_factor or 1 - anim = anim or "walk" - local dest = nil - local timer = #path_data -- failsafe - local width = hitbox(self)[4] - local init = false - local func = function(self) - local pos = mobkit.get_stand_pos(self) - local yaw = self.object:get_yaw() - local s_fctr = speed_factor - if path_data and #path_data > 1 then - if #path_data >= math.ceil(width) then - dest = path_data[1] - else - return true - end - else - return true - end - - if not self.isonground then - table.remove(path_data, 1) - timer = timer - 1 - s_fctr = 0.25 - end - - timer = timer - self.dtime - if timer < 0 then return true end - - local y = self.object:get_velocity().y - - local tyaw = minetest.dir_to_yaw(vector.direction(pos, dest)) - - mobkit.turn2yaw(self, tyaw, self.turn_rate or 4) - - if vec_dist(pos, path_data[#path_data]) < math.ceil(width) then - if not self.isonground and not self.isinliquid and - mob_core.fall_check(self, pos, self.max_fall or self.jump_height) then - self.object:set_velocity({x = 0, y = y, z = 0}) - end - return true - end - - if vec_dist(pos, path_data[1]) < 2.5 - and diff(yaw, tyaw) < 1.5 then - table.remove(path_data, 1) - timer = timer - 1 - end - - if mob_core.fall_check(self, pos, self.max_fall or self.jump_height) then - self.object:set_velocity({x = 0, y = y, z = 0}) - return true - end - - if self.isonground or self.isinliquid then - local forward_dir = vector.normalize(minetest.yaw_to_dir(yaw)) - forward_dir = vector.multiply(forward_dir, - self.max_speed * s_fctr) - forward_dir.y = y - self.object:set_velocity(forward_dir) - if not init then - mobkit.animate(self, anim) - init = true - end - end - end - mobkit.queue_low(self, func) -end - -function animalia.lq_dumb_follow_path(self, path_data, speed_factor, anim) - speed_factor = speed_factor or 1 - anim = anim or "walk" - local dest = nil - local timer = 3 -- failsafe - local width = hitbox(self)[4] - local stop_thresh = 1 - local init = false - local func = function(self) - local pos = mobkit.get_stand_pos(self) - local yaw = self.object:get_yaw() - if path_data and #path_data > 1 then - dest = path_data[1] - else - return true - end - - if not self.isonground then table.remove(path_data, 1) end - - timer = timer - self.dtime - if timer < 0 then return true end - - local y = self.object:get_velocity().y - - local tyaw = minetest.dir_to_yaw(vector.direction(pos, dest)) - - if #path_data > 2 - and ((dist_2d(pos, path_data[1]) < 1 - or abs(tyaw - yaw) < 3) - or dist_2d(pos, path_data[1]) >= dist_2d(pos, path_data[2])) then - table.remove(path_data, 1) - end - - if abs(yaw - tyaw) > 0.5 then - mobkit.turn2yaw(self, tyaw, 8) - end - - if dist_2d(pos, path_data[#path_data]) < 0.6 then - if not self.isonground and not self.isinliquid and - mob_core.fall_check(self, pos, self.max_fall or self.jump_height) then - self.object:set_velocity({x = 0, y = y, z = 0}) - end - mobkit.animate(self, "stand") - return true - end - - if dist_2d(pos, path_data[#path_data]) < 0.6 then - mobkit.animate(self, "stand") - return true - end - - if mob_core.fall_check(self, pos, self.max_fall or self.jump_height) then - self.object:set_velocity({x = 0, y = y, z = 0}) - return true - end - - if self.isonground or self.isinliquid then - local forward_dir = vector.normalize(minetest.yaw_to_dir(yaw)) - forward_dir = vector.multiply(forward_dir, - self.max_speed * speed_factor) - forward_dir.y = y - self.object:set_velocity(forward_dir) - if not init then - mobkit.animate(self, anim) - init = true - end - end - end - mobkit.queue_low(self, func) -end - -function animalia.lq_idle(self, duration, anim) - anim = anim or 'stand' - local random_yaw = nil - local init = true - local func = function(self) - if init then - mobkit.animate(self, anim) - init = false - end - duration = duration - self.dtime - if random(6) < 2 - and not random_yaw then - random_yaw = self.object:get_yaw() + random(-2, 2) - elseif random_yaw - and abs(self.object:get_yaw() - random_yaw) > 0.1 then - mobkit.turn2yaw(self, random_yaw, 3) - self._tyaw = random_yaw - end - if duration <= 0 then return true end - end - mobkit.queue_low(self, func) -end - ---------------------------- --- Mob Control Functions -- ---------------------------- - -function animalia.go_to_pos(self, tpos, speed_factor, anim) - speed_factor = speed_factor or 1 - local pos = self.object:get_pos() - local dist = vec_dist(pos, tpos) - if dist < 5 - and minetest.line_of_sight(pos, tpos) then - local _, pos2 = mob_core.get_next_waypoint(self, tpos) - if pos2 then - mob_core.lq_dumbwalk(self, pos2, speed_factor, anim) - return - end - else - local box = hitbox(self) - local path_data = mob_core.find_path(mobkit.get_stand_pos(self), tpos, box[4] - 0.1, self.height, 100) - if path_data then - mob_core.lq_follow_path(self, path_data, speed_factor, anim) - return - end - end - mob_core.lq_dumbwalk(self, tpos, speed_factor, anim) -end - -function animalia.go_to_pos_lite(self, tpos, speed_factor) - speed_factor = speed_factor or 1 - if mobkit.is_queue_empty_low(self) then - local _, pos2 = mob_core.get_next_waypoint(self, tpos) - if pos2 then - mob_core.lq_dumbwalk(self, pos2, speed_factor) - return true - else - local box = hitbox(self) - local path_data = mob_core.find_path(mobkit.get_stand_pos(self), tpos, box[4] - 0.1, self.height, 100) - if path_data and #path_data > 2 then - mob_core.lq_follow_path(self, path_data, speed_factor, anim) - return true - end - end - end - return false -end - ---------------------------------- --- Entity Definition Functions -- ---------------------------------- - -local function sensors() - local timer = 2 - local pulse = 1 - return function(self) - timer = timer - self.dtime - if timer < 0 then - pulse = pulse + 1 - local range = self.view_range - if pulse > 2 then - pulse = 1 - else - range = self.view_range * 0.5 - end - - local pos = self.object:get_pos() - self.group = {} - self.nearby_objects = minetest.get_objects_inside_radius(pos, range) - for i, obj in ipairs(self.nearby_objects) do - if obj ~= self.object - and obj:get_luaentity() - and obj:get_luaentity().name == self.name then - table.insert(self.group, obj) - elseif obj == self.object then - table.remove(self.nearby_objects, i) - break - end - end - timer = 2 - end - end -end - -function animalia.on_activate(self, staticdata, dtime_s) - mob_core.on_activate(self, staticdata, dtime_s) - self.sensefunc = sensors() - self.order = mobkit.recall(self, "order") or "wander" - self.gotten = mobkit.recall(self, "gotten") or false - self.attention_span = mobkit.recall(self, "attention_span") or 0 - self.breeding = mobkit.recall(self, "breeding") or false - self.breeding_time = mobkit.recall(self, "breeding_time") or 0 - self.breeding_cooldown = mobkit.recall(self, "breeding_cooldown") or 0 - self.lasso_pos = mobkit.recall(self, "lasso_pos") or nil - self.liquid_recovery_cooldown = 0 - self.target_blacklist = {} - if self.lasso_pos then - self.caught_with_lasso = true - if minetest.get_item_group(minetest.get_node(self.lasso_pos).name, "fence") > 0 then - local object = minetest.add_entity(self.lasso_pos, "animalia:lasso_fence_ent") - object:get_luaentity().parent = self.object - end - end - for name in pairs(minetest.registered_entities) do - if self.targets - and self.prey_params then - if animalia.is_prey(self.name, name) == 2 then - table.insert(self.targets, name) - end - end - end - self._tyaw = self.object:get_yaw() or 0 -end - -local function lasso_effect(self, pos2) - local pos = mobkit.get_stand_pos(self) - pos.y = pos.y + (self.height * 0.5) - local object = minetest.add_entity(pos2, "animalia:lasso_visual") - local ent = object:get_luaentity() - ent.parent = self.object - ent.anchor_pos = pos2 - return object -end - -local function is_under_solid(pos) - local pos2 = vector.new(pos.x, pos.y + 1, pos.z) - return (walkable(pos2) or ((mobkit.get_node_height(pos2) or 0) < 1.5)) -end - -local function vec_center(vec) - return {x = floor(vec.x + 0.5), y = floor(vec.y + 0.5), z = floor(vec.z + 0.5)} -end - -local function do_step(self, moveresult) - if not fancy_step then return end - local pos = mobkit.get_stand_pos(self) - local width = hitbox(self)[4] - 0.1 - if not self._step then - for _, data in ipairs(moveresult.collisions) do - if data.type == "node" then - local step_pos = data.node_pos - local halfway = vector.add(pos, vector.multiply(vector.direction(pos, step_pos), 0.5)) - if step_pos.y + 0.5 > pos.y - and (walkable({x = pos.x, y = pos.y - 1, z = pos.z}) - or self.isinliquid) - and not vector.equals(vec_center(pos), step_pos) - and not is_under_solid(step_pos) - and is_movable({x = halfway.x, y = step_pos.y + 1, z = halfway.z}, width, self.height) then - local vel_yaw = self.object:get_yaw() - local dir_yaw = minetest.dir_to_yaw(vector.direction(pos, data.node_pos)) - if diff(vel_yaw, dir_yaw) < width * 2 then - self._step = data.node_pos - break + self:set_texture(tex_no, self.child_textures) + elseif self.growth_scale == 0.8 then + if self.male_textures + and self.female_textures then + if #self.child_textures == 1 then + self.texture_no = random(#self[self.gender .. "_textures"]) end + self:set_texture(self.texture_no, self[self.gender .. "_textures"]) else - self._step = nil - end - end - end - else - local vel = self.object:get_velocity() - self.object:set_velocity(vector.new(vel.x, 7, vel.z)) - if self._step.y < pos.y - 0.5 then - self.object:set_velocity(vector.new(vel.x, 0.5, vel.z)) - self._step = nil - end - end -end - -function animalia.on_step(self, dtime, moveresult) - mob_core.on_step(self, dtime, moveresult) - mob_core.vitals(self) - if mobkit.timer(self, 1) then - if self.breeding_cooldown > 0 then - self.breeding_cooldown = self.breeding_cooldown - 1 - end - mobkit.remember(self, "breeding_cooldown", self.breeding_cooldown) - end - if mobkit.timer(self, 4) - and #self.target_blacklist > 0 then - table.remove(self.target_blacklist, 1) - end - if self.caught_with_lasso then - if self.point_to - and mobkit.is_alive(self) then - local player = self.point_to - local pos = mobkit.get_stand_pos(self) - pos.y = pos.y + (self.height * 0.5) - local ppos = player:get_pos() - ppos.y = ppos.y + 1 - if not self.lasso_visual - or not self.lasso_visual:get_luaentity() then - self.lasso_visual = lasso_effect(self, ppos) - else - self.lasso_visual:get_luaentity().anchor_pos = ppos - end - local dist = vector.distance(pos, ppos) - local dist = vector.distance(pos, ppos) - if dist_2d(pos, ppos) > 6 - or abs(ppos.y - pos.y) > 8 then - local p_target = vector.add(pos, vector.multiply(vector.direction(pos, ppos), dist * 0.8)) - local g = -0.18 - local v = vector.new(0, 0, 0) - v.x = (1.0 + (0.005 * dist)) * (p_target.x - pos.x) / dist - v.y = -((1.0 + (0.03 * dist)) * ((ppos.y - 4) - pos.y) / (dist * (g * dist))) - v.z = (1.0 + (0.005 * dist)) * (p_target.z - pos.z) / dist - self.object:add_velocity(v) - end - if player:get_wielded_item():get_name() ~= "animalia:lasso" - or vector.distance(pos, ppos) > 20 then - self.caught_with_lasso = nil - self.point_to = nil - if self.lasso_visual then - self.lasso_visual:remove() - self.lasso_visual = nil - end - end - elseif self.lasso_pos - and mobkit.is_alive(self) then - mobkit.remember(self, "lasso_pos", self.lasso_pos) - local pos = mobkit.get_stand_pos(self) - pos.y = pos.y + (self.height * 0.5) - local ppos = self.lasso_pos - if not self.lasso_visual - or not self.lasso_visual:get_luaentity() then - self.lasso_visual = lasso_effect(self, ppos) - else - self.lasso_visual:get_luaentity().anchor_pos = ppos - end - local dist = vector.distance(pos, ppos) - if dist_2d(pos, ppos) > 6 - or abs(ppos.y - pos.y) > 8 then - local p_target = vector.add(pos, vector.multiply(vector.direction(pos, ppos), dist * 0.8)) - local g = -0.18 - local v = vector.new(0, 0, 0) - v.x = (1.0 + (0.005 * dist)) * (p_target.x - pos.x) / dist - v.y = -((1.0 + (0.03 * dist)) * ((ppos.y - 4) - pos.y) / (dist * (g * dist))) - v.z = (1.0 + (0.005 * dist)) * (p_target.z - pos.z) / dist - self.object:add_velocity(v) - end - local objects = minetest.get_objects_inside_radius(ppos, 1) - local is_lasso_attached = false - for _, object in ipairs(objects) do - if object - and object:get_luaentity() - and object:get_luaentity().name == "animalia:lasso_fence_ent" then - is_lasso_attached = true - end - end - if not is_lasso_attached then - - self.caught_with_lasso = nil - self.lasso_pos = nil - if self.lasso_visual then - self.lasso_visual:remove() - self.lasso_visual = nil - end - end - else - if self.lasso_pos then - local objects = minetest.get_objects_inside_radius(self.lasso_pos, 0.4) - for _, object in ipairs(objects) do - if object - and object:get_luaentity() - and object:get_luaentity().name == "animalia:lasso_fence_ent" then - minetest.add_item(object:get_pos(), "animalia:lasso") - object:remove() + if #self.child_textures == 1 then + self.texture_no = random(#self.textures) end + self:set_texture(self.texture_no, self.textures) end end - self.caught_with_lasso = nil - self.lasso_pos = nil - if self.lasso_visual then - self.lasso_visual:remove() - self.lasso_visual = nil - end + self:memorize("growth_scale", self.growth_scale) end end - if mobkit.is_alive(self) then - do_step(self, moveresult) - end -end - -------------- --- Physics -- -------------- - -function animalia.lightweight_physics(self) - local vel = self.object:get_velocity() - if self.isonground and not self.isinliquid then - self.object:set_velocity({x= vel.x> 0.2 and vel.x*mobkit.friction or 0, - y=vel.y, - z=vel.z > 0.2 and vel.z*mobkit.friction or 0}) - end - if self.springiness and self.springiness > 0 then - local vnew = vector.new(vel) - - if not self.collided then - for _,k in ipairs({'y','z','x'}) do - if vel[k]==0 and abs(self.lastvelocity[k])> 0.1 then - vnew[k]=-self.lastvelocity[k]*self.springiness - end - end - end - if not vector.equals(vel,vnew) then - self.collided = true - else - if self.collided then - vnew = vector.new(self.lastvelocity) - end - self.collided = false - end - - self.object:set_velocity(vnew) - end - local surface = nil - local surfnodename = nil - local spos = mobkit.get_stand_pos(self) - spos.y = spos.y+0.01 - local snodepos = mobkit.get_node_pos(spos) - local surfnode = mobkit.nodeatpos(spos) - while surfnode and surfnode.drawtype == 'liquid' do - surfnodename = surfnode.name - surface = snodepos.y+0.5 - if surface > spos.y+self.height then break end - snodepos.y = snodepos.y+1 - surfnode = mobkit.nodeatpos(snodepos) - end - self.isinliquid = surfnodename - if surface then - local submergence = min(surface-spos.y,self.height)/self.height - local buoyacc = mobkit.gravity*(self.buoyancy-submergence) - mobkit.set_acceleration(self.object, - {x=-vel.x*self.water_drag,y=buoyacc-vel.y*abs(vel.y)*0.4,z=-vel.z*self.water_drag}) - else - self.object:set_acceleration({x=0,y=-2.8,z=0}) - end -end - ------------------- --- HQ Functions -- ------------------- - -function animalia.hq_eat(self, prty) - local func = function(self) - local pos = mobkit.get_stand_pos(self) - local under = vector.new(pos.x, pos.y - 1, pos.z) - for _, node in ipairs(self.consumable_nodes) do - if node.name == minetest.get_node(under).name then - minetest.set_node(under, {name = node.replacement}) - local def = minetest.registered_nodes[node.name] - local texture = def.tiles[1] - texture = texture .. "^[resize:8x8" - minetest.add_particlespawner({ - amount = 6, - time = 0.1, - minpos = vector.new( - pos.x - 0.5, - pos.y + 0.1, - pos.z - 0.5 - ), - maxpos = vector.new( - pos.x + 0.5, - pos.y + 0.1, - pos.z + 0.5 - ), - minvel = {x=-1, y=1, z=-1}, - maxvel = {x=1, y=2, z=1}, - minacc = {x=0, y=-5, z=0}, - maxacc = {x=0, y=-9, z=0}, - minexptime = 1, - maxexptime = 1, - minsize = 1, - maxsize = 2, - collisiondetection = true, - vertical = false, - texture = texture, - }) - self.gotten = false - mobkit.remember(self, "gotten", self.gotten) - return true - else - return true - end - end - end - mobkit.queue_high(self, func, prty) -end - --- Wandering -- - -function animalia.hq_go_to_land(self, prty) - local init = false - local tpos = nil - local func = function(self) - if self.liquid_recovery_cooldown > 0 then - self.liquid_recovery_cooldown = self.liquid_recovery_cooldown - 1 - return true - end - if not init then - for i = 1, 359, 15 do - local yaw = math.rad(i) - local dir = minetest.yaw_to_dir(yaw) - tpos = animalia.find_collision(self, dir) - if tpos then - local node = minetest.get_node({x = tpos.x, y = tpos.y + 1, z = tpos.z}) - if node.name == "air" then - break - else - tpos = nil - end - end - end - init = true - end - if tpos then - local pos = mobkit.get_stand_pos(self) - local yaw = self.object:get_yaw() - local tyaw = minetest.dir_to_yaw(vec_dir(pos, tpos)) - if abs(tyaw - yaw) > 0.1 then - mobkit.turn2yaw(self, tyaw) - else - mobkit.go_forward_horizontal(self, self.max_speed * 0.66) - mobkit.animate(self, "walk") - end - if dist_2d(pos, tpos) < 1 - or (not self.isinliquid - and self.isonground) then - return true - end - else - self.liquid_recovery_cooldown = 5 - return true - end - end - mobkit.queue_high(self, func, prty) -end - -function animalia.hq_wander_ranged(self, prty) - local idle_time = 3 - local move_probability = 3 - local func = function(self) - if mobkit.is_queue_empty_low(self) then - local pos = self.object:get_pos() - local random_goal = vector.new( - pos.x + random(-1, 1), - pos.y, - pos.z + random(-1, 1) - ) - local node = minetest.get_node(random_goal) - local def = minetest.registered_nodes[node.name] - if node.name - and def - and ((def.drawtype - and def.drawtype == "liquid") - or minetest.registered_nodes[node.name].walkable) then - random_goal = nil - end - if self.lasso_pos - and vec_dist(pos, self.lasso_pos) > 10 then - random_goal = self.lasso_pos - end - if random(move_probability) < 2 - and random_goal then - local _, pos2 = mobkit.get_next_waypoint(self, random_goal) - if pos2 then - random_goal = pos2 - end - mob_core.lq_dumbwalk(self, random_goal, 0.5) - else - animalia.lq_idle(self, idle_time) - end - end - end - mobkit.queue_high(self, func, prty) -end - -function animalia.hq_wander_group(self, prty, group_range) - local idle_time = 3 - local move_probability = 3 - local group_tick = 0 - local func = function(self) - if mobkit.is_queue_empty_low(self) then - group_tick = group_tick - 1 - local pos = self.object:get_pos() - local group_positions = {} - local random_goal = vector.new( - pos.x + random(-1, 1), - pos.y, - pos.z + random(-1, 1) - ) - if group_tick <= 0 - and self.group - and #self.group > 0 then - for _, obj in ipairs(self.group) do - if obj - and mobkit.is_alive(obj) - and #group_positions < 4 then - table.insert(group_positions, obj:get_pos()) - end - end - if #group_positions > 2 then - group_range = group_range + #group_positions - local center = get_average_pos(group_positions) - if center - and ((vec_dist(random_goal, center) > group_range) - or vec_dist(pos, center) > group_range) then - random_goal = pos_to_neighbor(self, center) - end - end - group_tick = 3 - end - local node = minetest.get_node(random_goal) - local def = minetest.registered_nodes[node.name] - if node.name - and def - and ((def.drawtype - and def.drawtype == "liquid") - or minetest.registered_nodes[node.name].walkable) then - random_goal = nil - end - if self.lasso_pos - and vec_dist(pos, self.lasso_pos) > 10 then - random_goal = self.lasso_pos - end - if random(move_probability) < 2 - and random_goal then - local _, pos2 = mobkit.get_next_waypoint(self, random_goal) - if pos2 then - random_goal = pos2 - end - mob_core.lq_dumbwalk(self, random_goal, 0.5) - else - animalia.lq_idle(self, idle_time) - end - end - end - mobkit.queue_high(self, func, prty) -end - --- Breeding -- - -function animalia.hq_breed(self, prty) - local mate = animalia.get_nearby_mate(self, self.name) - if not mate then return end - local func = function(self) - if not mobkit.is_alive(mate) then - return true - end - local pos = mobkit.get_stand_pos(self) - local tpos = mate:get_pos() - local dist = vec_dist(pos, tpos) - math.abs(hitbox(self)[4]) - local speed_factor = clamp(dist, 0.1, 0.65) - if dist < 1.75 then - self.breeding_time = self.breeding_time + 1 - end - if self.breeding_time >= 2 - or mate:get_luaentity().breeding_time >= 2 then - if self.gender == "female" then - mob_core.spawn_child(pos, self.name) - end - self.breeding = false - self.breeding_time = 0 - self.breeding_cooldown = 300 - mobkit.remember(self, "breeding", self.breeding) - mobkit.remember(self, "breeding_time", self.breeding_time) - mobkit.remember(self, "breeding_cooldown", self.breeding_cooldown) - return true - end - if mobkit.is_queue_empty_low(self) then - animalia.go_to_pos(self, tpos, speed_factor) - end - end - mobkit.queue_high(self, func, prty) -end - -function animalia.hq_fowl_breed(self, prty) - local mate = animalia.get_nearby_mate(self, self.name) - if not mate then return end - local speed_factor = 0.5 - local func = function(self) - if mobkit.is_queue_empty_low(self) then - local pos = mobkit.get_stand_pos(self) - local tpos = mate:get_pos() - local dist = vec_dist(pos, tpos) - math.abs(hitbox(self)[4]) - if dist > 1.5 then - speed_factor = 0.5 - else - speed_factor = 0.1 - end - mob_core.goto_next_waypoint(self, tpos, speed_factor) - if dist < 1.75 then - self.breeding_time = self.breeding_time + 1 - end - if self.breeding_time >= 2 - or mate:get_luaentity().breeding_time >= 2 then - if self.gender == "female" then - minetest.add_particlespawner({ - amount = 6, - time = 0.25, - minpos = {x = pos.x - 7/16, y = pos.y - 5/16, z = pos.z - 7/16}, - maxpos = {x = pos.x + 7/16, y = pos.y - 5/16, z = pos.z + 7/16}, - minvel = vector.new(-1, 2, -1), - maxvel = vector.new(1, 5, 1), - minacc = vector.new(0, -9.81, 0), - maxacc = vector.new(0, -9.81, 0), - collisiondetection = true, - texture = "animalia_egg_fragment.png", - }) - mob_core.spawn_child(pos, self.name) - end - self.breeding = false - self.breeding_time = 0 - self.breeding_cooldown = 300 - mobkit.remember(self, "breeding", self.breeding) - mobkit.remember(self, "breeding_time", self.breeding_time) - mobkit.remember(self, "breeding_cooldown", self.breeding_cooldown) - return true - end - end - end - mobkit.queue_high(self, func, prty) -end - --- Player Interaction -- - - -function animalia.hq_sporadic_flee(self, prty) - local timer = 12 - local func = function(self) - if mobkit.is_queue_empty_low(self) then - local pos = self.object:get_pos() - local random_goal = vector.new( - pos.x + random(-6, 6), - pos.y, - pos.z + random(-6, 6) - ) - local node = minetest.get_node({x = random_goal.x, y = random_goal.y + 1, z = random_goal.z}) - if minetest.registered_nodes[node.name].drawtype == "liquid" then - random_goal = nil - end - if random_goal then - local anim = "walk" - if self.animation["run"] then - anim = "run" - end - animalia.go_to_pos_lite(self, random_goal, 1) - else - animalia.lq_idle(self, 0.1) - end - end - timer = timer - self.dtime - if timer <= 0 then - animalia.lq_idle(self, 0.1, "stand") - return true - end - end - mobkit.queue_high(self, func, prty) -end - -function animalia.hq_attack(self, prty, target) - local func = function(self) - if not mobkit.is_alive(target) then - return true - end - mob_core.punch_timer(self) - local pos = mobkit.get_stand_pos(self) - local tpos = target:get_pos() - if not is_on_ground(target) then - table.insert(self.target_blacklist, target) - return true - end - local can_punch = is_within_reach(self, target) - if mobkit.is_queue_empty_low(self) then - animalia.go_to_pos(self, tpos, 1, "run") - end - if self.punch_timer <= 0 - and can_punch then - target:punch(self.object, 1.0, { - full_punch_interval = 0.1, - damage_groups = {fleshy = self.damage} - }, nil) - mob_core.knockback(self, target) - mob_core.punch_timer(self, self.punch_cooldown or 1) - return true - end - end - mobkit.queue_high(self, func, prty) -end - -function animalia.hq_follow_player(self, prty, player, force) -- Follow Player - if not player then return end - if not force - and not mob_core.follow_holding(self, player) then return end - local func = function(self) - if not mobkit.is_alive(player) then - return true - end - local pos = mobkit.get_stand_pos(self) - local tpos = player:get_pos() - if mob_core.follow_holding(self, player) - or force then - self.status = mobkit.remember(self, "status", "following") - local dist = vec_dist(pos, tpos) - local yaw = self.object:get_yaw() - local tyaw = minetest.dir_to_yaw(vector.direction(pos, tpos)) - if dist > self.view_range then - self.status = mobkit.remember(self, "status", "") - return true - end - if mobkit.is_queue_empty_low(self) then - if vec_dist(pos, tpos) > hitbox(self)[4] + 2 then - animalia.go_to_pos(self, tpos, 0.6) - else - mobkit.lq_idle(self, 0.1, "stand") - end - end - elseif mobkit.is_queue_empty_low(self) then - self.status = mobkit.remember(self, "status", "") - mobkit.lq_idle(self, 0.1, "stand") - return true - end - end - mobkit.queue_high(self, func, prty) -end - -------------------------------- --- Mob Specific HQ Functions -- -------------------------------- - --- Cat -- - -function animalia.hq_find_and_break_glass(self, prty) - local timer = 6 - local moving = false - local pos2 = nil - mobkit.clear_queue_low(self) - local func = function(self) - local pos = mobkit.get_stand_pos(self) - if not pos2 then - local nodes = minetest.find_nodes_in_area( - vector.subtract(pos, 8), - vector.add(pos, 8), - {"vessels:glass_bottle", "vessels:drinking_glass"} - ) - if #nodes > 0 then - pos2 = nodes[1] - end - end - if not pos2 then return true end - timer = timer - self.dtime - if mobkit.is_queue_empty_low(self) then - if dist_2d(pos, pos2) > 0.5 then - animalia.go_to_pos(self, pos2, 0.35) - end - end - if dist_2d(pos, pos2) <= 0.5 then - mobkit.lq_idle(self, 0.7, "smack") - minetest.remove_node(pos2) - minetest.add_item(pos2, "vessels:glass_fragments") - if minetest.get_node(pos2).name == "air" then - return true - end - end - if timer < 0 then return true end - end - mobkit.queue_high(self, func, prty) -end - -function animalia.hq_walk_in_front_of_player(self, prty, player) - if not player then return end - local can_reach = false - local path_data = nil - local timer = 8 - local func = function(self) - if not mobkit.is_alive(player) then - return true - end - local pos = mobkit.get_stand_pos(self) - local tpos = player:get_pos() - local dir = player:get_look_dir() - tpos.x = tpos.x + dir.x - tpos.z = tpos.z + dir.z - self.status = mobkit.remember(self, "status", "following") - local dist = vec_dist(pos, tpos) - local yaw = self.object:get_yaw() - local tyaw = minetest.dir_to_yaw(vector.direction(pos, tpos)) - if dist > self.view_range then - self.status = mobkit.remember(self, "status", "") - return true - end - if mobkit.is_queue_empty_low(self) then - if vec_dist(pos, tpos) > hitbox(self)[4] + 0.5 then - if not can_reach then - can_reach, path_data = animalia.can_reach(self, tpos) - else - animalia.lq_dumb_follow_path(self, path_data, 1, "run") - end - else - can_reach = false - path_data = nil - mobkit.lq_idle(self, 0.1, "stand") - end - else - can_reach = false - path_data = nil - end - timer = timer - self.dtime - if timer < 0 then return true end - end - mobkit.queue_high(self, func, prty) -end - --- Horse -- - -function animalia.hq_mount_logic(self, prty) - local tvel = 0 - local rearing = false - local jumping = false - local anim = "stand" - local func = function(self) - if not self.driver then return true end - -- if horse is rearing, stop moving - if rearing then - if mobkit.timer(self, 1.5) then - rearing = false - end - return - end - -- Controls - local vel = self.object:get_velocity() - local ctrl = self.driver:get_player_control() - if ctrl.up then - tvel = self.speed - if ctrl.aux1 then - tvel = self.speed * 2 - end - elseif tvel < 0.25 or tvel == 0 then - tvel = 0 - self.object:set_velocity({ - x = 0, - y = vel.y, - z = 0 - }) - anim = "stand" - end - if self.isonground then - if ctrl.jump then - jumping = true - vel.y = self.jump_power + 4.405 - else - jumping = false - end - end - -- Physics and Animation - if not ctrl.up - and self.isonground then - tvel = tvel * 0.75 - elseif not self.isonground then - if self.isinliquid then - tvel = tvel * 0.4 - vel.y = vel.y * 0.4 - else - if jumping then - tvel = tvel * 0.4 - else - tvel = tvel * 0.6 - end - end - end - if tvel > 0 then - if jumping then - anim = "rear_constant" - else - if ctrl.aux1 then - anim = "run" - else - anim = "walk" - end - end - end - if random(1024) < 2 then - tvel = 0 - anim = "rear" - rearing = true - end - mobkit.animate(self, anim) - local tyaw = self.driver:get_look_horizontal() or 0 - self._tyaw = tyaw - self.object:set_yaw(tyaw) - local nvel = vector.multiply(minetest.yaw_to_dir(self.object:get_yaw()), tvel) - self.object:set_velocity({ - x = nvel.x, - y = vel.y, - z = nvel.z - }) - if ctrl.sneak then - mob_core.detach(self.driver, {x = 1, y = 0, z = 1}) - return true - end - end - mobkit.queue_high(self, func, prty) -end - -function animalia.hq_horse_breed(self, prty) - local mate = animalia.get_nearby_mate(self, self.name) - if not mate then return end - local speed_factor = 0.5 - local func = function(self) - if mobkit.is_queue_empty_low(self) then - local pos = mobkit.get_stand_pos(self) - local tpos = mate:get_pos() - local dist = vec_dist(pos, tpos) - math.abs(hitbox(self)[4]) - if dist > 1.5 then - speed_factor = 0.5 - else - speed_factor = 0.1 - end - mob_core.goto_next_waypoint(self, tpos, speed_factor) - if dist < 1.75 then - self.breeding_time = self.breeding_time + 1 - end - if self.breeding_time >= 2 - or mate:get_luaentity().breeding_time >= 2 then - if self.gender == "female" then - local obj = mob_core.spawn_child(pos, self.name) - local ent = obj:get_luaentity() - local tex_no = self.texture_no - if random(2) < 2 then - tex_no = mate:get_luaentity().texture_no - end - mobkit.remember(ent, "texture_no", self.texture_no) - mobkit.remember(ent, "speed", random(mate:get_luaentity().speed, self.speed)) - mobkit.remember(ent, "jump_power", random(mate:get_luaentity().jump_power, self.jump_power)) - mobkit.remember(ent, "max_hp", random(mate:get_luaentity().max_hp, self.max_hp)) - ent.speed = mobkit.recall(ent, "speed") - ent.jump_power = mobkit.recall(ent, "jump_power") - ent.max_hp = mobkit.recall(ent, "max_hp") - ent.object:set_properties({ - texture = ent.textures[ent.texture_no] .. "^" .. mobkit.recall(ent, "pattern") - }) - end - self.breeding = false - self.breeding_time = 0 - self.breeding_cooldown = 300 - mobkit.remember(self, "breeding", self.breeding) - mobkit.remember(self, "breeding_time", self.breeding_time) - mobkit.remember(self, "breeding_cooldown", self.breeding_cooldown) - return true - end - end - end - mobkit.queue_high(self, func, prty) end ----------------------- @@ -1438,40 +252,39 @@ local function clamp_bone_rot(n) -- Fixes issues with bones jittering when yaw c return n end -local function interp(a, b, w) -- Smoothens bone movement - local pi = math.pi - if math.abs(a - b) > math.deg(pi) then +local function interp_bone_rot(a, b, w) -- Smoothens bone movement + if abs(a - b) > deg(pi) then if a < b then - return ((a + (b - a) * w) + (math.deg(pi) * 2)) + return ((a + (b - a) * w) + (deg(pi) * 2)) elseif a > b then - return ((a + (b - a) * w) - (math.deg(pi) * 2)) + return ((a + (b - a) * w) - (deg(pi) * 2)) end end return a + (b - a) * w end -local function move_head(self, tyaw, pitch) +function animalia.move_head(self, tyaw, pitch) local data = self.head_data local _, rot = self.object:get_bone_position(data.bone or "Head.CTRL") local yaw = self.object:get_yaw() - local look_yaw = clamp_bone_rot(math.deg(yaw - tyaw)) + local look_yaw = clamp_bone_rot(deg(yaw - tyaw)) local look_pitch = 0 if pitch then - look_pitch = clamp_bone_rot(math.deg(pitch)) + look_pitch = clamp_bone_rot(deg(pitch)) end if tyaw ~= yaw then look_yaw = look_yaw * 0.66 end - local yaw = interp(rot.z, look_yaw, 0.1) - local ptch = interp(rot.x, look_pitch + data.pitch_correction, 0.1) + yaw = interp_bone_rot(rot.z, look_yaw, 0.1) + local ptch = interp_bone_rot(rot.x, look_pitch + data.pitch_correction, 0.1) self.object:set_bone_position(data.bone or "Head.CTRL", data.offset, {x = ptch, y = yaw, z = yaw}) end function animalia.head_tracking(self) if not self.head_data then return end local yaw = self.object:get_yaw() - local pos = mobkit.get_stand_pos(self) - local v = vector.add(pos, vector.multiply(yaw2dir(yaw), self.head_data.pivot_h)) + local pos = self.object:get_pos() + local v = vec_add(pos, vec_multi(yaw2dir(yaw), self.head_data.pivot_h)) pos.x = v.x pos.y = pos.y + self.head_data.pivot_v pos.z = v.z @@ -1486,39 +299,932 @@ function animalia.head_tracking(self) texture = "mob_core_green_particle.png", playername = "singleplayer" })]] - if not self.head_tracking then + local vel = self.object:get_velocity() + if abs(yaw - self.last_yaw) < 0.1 then + animalia.move_head(self, yaw) + else + animalia.move_head(self, self._tyaw) + end + if not self.head_tracking + and self:timer(3) + and random(4) < 2 then local objects = minetest.get_objects_inside_radius(pos, 6) for _, object in ipairs(objects) do if object:is_player() then - local dir_2_plyr = vector.direction(pos, object:get_pos()) - local yaw_2_plyr = dir2yaw(dir_2_plyr) - if abs(yaw - yaw_2_plyr) < 1 - or abs(yaw - yaw_2_plyr) > 5.3 then - self.head_tracking = object - end + self.head_tracking = object break end end - if self._anim == "stand" then - move_head(self, yaw) - else - move_head(self, self._tyaw) - end else - if not mobkit.exists(self.head_tracking) then + if not creatura.is_valid(self.head_tracking) then self.head_tracking = nil + self.head_tracking_turn = nil return end local ppos = self.head_tracking:get_pos() ppos.y = ppos.y + 1.4 - local dir = vector.direction(pos, ppos) - local tyaw = minetest.dir_to_yaw(dir) - if abs(yaw - tyaw) > 1 - and abs(yaw - tyaw) < 5.3 then + local dir = vec_dir(pos, ppos) + local tyaw = dir2yaw(dir) + if self:timer(1) + and abs(yaw - tyaw) > 1 + and abs(yaw - tyaw) < 5.3 + and self.head_tracking_turn then self.head_tracking = nil + self.head_tracking_turn = nil dir.y = 0 return + elseif not self.head_tracking_turn then + self.head_tracking_turn = tyaw end - move_head(self, tyaw, dir.y) + if self.head_tracking_turn + and self._anim == "stand" then + self:turn_to(self.head_tracking_turn, 2) + end + animalia.move_head(self, tyaw, dir.y) end end + +----------------------- +-- World Interaction -- +----------------------- + +function animalia.random_drop_item(item, chance) + if random(chance) < 2 then + local object = minetest.add_item(ItemStack(item)) + object:add_velocity({ + x = random(-2, 2), + y = 1.5, + z = random(-2, 2) + }) + end +end + +function animalia.protect_from_despawn(self) + self._despawn = self:memorize("_despawn", false) + self.despawn_after = self:memorize("despawn_after", false) +end + +------------------------ +-- Player Interaction -- +------------------------ + +function animalia.feed(self, player, tame, breed) + local item, item_name = self:follow_wielded_item(player) + if item_name then + if not creative then + item:take_item() + player:set_wielded_item(item) + end + if self.hp < self.max_health then + self:heal(self.max_health / 5) + end + self.food = self.food + 1 + if self.food >= 5 then + local pos = self:get_center_pos() + local minp = vec_sub(pos, 1) + local maxp = vec_add(pos, 1) + self.food = 0 + local follow = self.follow + if type(follow) == "table" then + follow = follow[1] + end + if tame + and not self.owner + and (follow == item_name) then + self.owner = self:memorize("owner", player:get_player_name()) + local name = correct_name(self.name) + minetest.chat_send_player(player:get_player_name(), name .. " has been tamed!") + if self.logic then + self:clear_task() + end + animalia.particle_spawner(pos, "creatura_particle_green.png", "float", minp, maxp) + if not animalia.pets[self.owner][self.object] then + table.insert(animalia.pets[self.owner], self.object) + end + end + if breed then + if self.breeding then return false end + if self.breeding_cooldown <= 0 then + self.breeding = true + self.breeding_cooldown = 60 + animalia.particle_spawner(pos, "heart.png", "float", minp, maxp) + end + end + end + animalia.protect_from_despawn(self) + return true + end + return false +end + +local animate_player = {} + +if minetest.get_modpath("default") +and minetest.get_modpath("player_api") then + animate_player = player_api.set_animation +elseif minetest.get_modpath("mcl_player") then + animate_player = mcl_player.set_animation +end + +function animalia.mount(self, player, params) + if not creatura.is_alive(player) then + return + end + if (player:get_attach() + and player:get_attach() == self.object) + or not params then + player:set_detach() + player:set_properties({ + visual_size = { + x = 1, + y = 1 + } + }) + player:set_eye_offset() + if player_api then + animate_player(player, "stand", 30) + if player_api.player_attached then + player_api.player_attached[player:get_player_name()] = false + end + end + self.rider = nil + return + end + if player_api then + player_api.player_attached[player:get_player_name()] = true + end + minetest.after(0.2, function() + if player + and player:is_player() + and player_api then + animate_player(player, "sit", 30) + end + end) self.rider = player + local mob_size = self.object:get_properties().visual_size + local player_size = player:get_properties().visual_size + player:set_attach(self.object, "Torso", params.pos, params.rot) + player:set_properties({ + visual_size = { + x = player_size.x / mob_size.x, + y = player_size.y / mob_size.y + } + }) + player:set_eye_offset({x = 0, y = 15, z = 0}, {x = 0, y = 15, z = 15}) +end + +------------- +-- Sensors -- +------------- + +function animalia.find_collision(self, dir) + local pos = self.object:get_pos() + local pos2 = vec_add(pos, vec_multi(dir, 16)) + local ray = minetest.raycast(pos, pos2, false, false) + for pointed_thing in ray do + if pointed_thing.type == "node" then + return pointed_thing.under + end + end + return nil +end + +---------- +-- Misc -- +---------- + +function animalia.alias_mob(old_mob, new_mob) + minetest.register_entity(":" .. old_mob, { + on_activate = function(self) + local pos = self.object:get_pos() + minetest.add_entity(pos, new_mob) + self.object:remove() + end, + }) +end + +-------------- +-- Spawning -- +-------------- + +animalia.registered_biome_groups = {} + +function animalia.register_biome_group(name, def) + animalia.registered_biome_groups[name] = def + animalia.registered_biome_groups[name].biomes = {} +end + +local function assign_biome_group(name) + local def = minetest.registered_biomes[name] + local turf = def.node_top + local heat = def.heat_point or 0 + local humidity = def.humidity_point or 50 + local y_min = def.y_min + local y_max = def.y_max + for group, params in pairs(animalia.registered_biome_groups) do -- k, v in pairs + if name:find(params.name_kw or "") + and turf and turf:find(params.turf_kw or "") + and heat >= params.min_heat + and heat <= params.max_heat + and humidity >= params.min_humidity + and humidity <= params.max_humidity + and (not params.min_height or y_min >= params.min_height) + and (not params.max_height or y_max <= params.max_height) then + table.insert(animalia.registered_biome_groups[group].biomes, name) + end + end +end + +minetest.register_on_mods_loaded(function() + for name in pairs(minetest.registered_biomes) do + assign_biome_group(name) + end +end) + +local spawn_biomes = { + ["animalia:bat"] = "cave", + ["animalia:bird"] = "temperate", + ["animalia:cat"] = "urban", + ["animalia:chicken"] = "tropical", + ["animalia:cow"] = "grassland", + ["animalia:tropical_fish"] = "ocean", + ["animalia:frog"] = "swamp", + ["animalia:horse"] = "grassland", + ["animalia:pig"] = "temperate", + ["animalia:reindeer"] = "boreal", + ["animalia:sheep"] = "grassland", + ["animalia:turkey"] = "boreal", + ["animalia:wolf"] = "boreal", +} + +animalia.register_biome_group("temperate", { + name_kw = "", + turf_kw = "grass", + min_heat = 45, + max_heat = 70, + min_humidity = 0, + max_humidity = 50 +}) + +animalia.register_biome_group("urban", { + name_kw = "", + turf_kw = "grass", + min_heat = 0, + max_heat = 100, + min_humidity = 0, + max_humidity = 100 +}) + +animalia.register_biome_group("grassland", { + name_kw = "", + turf_kw = "grass", + min_heat = 45, + max_heat = 90, + min_humidity = 0, + max_humidity = 80 +}) + +animalia.register_biome_group("boreal", { + name_kw = "", + turf_kw = "litter", + min_heat = 10, + max_heat = 55, + min_humidity = 0, + max_humidity = 80 +}) + +animalia.register_biome_group("ocean", { + name_kw = "ocean", + turf_kw = "", + min_heat = 0, + max_heat = 100, + min_humidity = 0, + max_humidity = 100, + max_height = 0 +}) + +animalia.register_biome_group("swamp", { + name_kw = "", + turf_kw = "", + min_heat = 55, + max_heat = 90, + min_humidity = 55, + max_humidity = 90, + max_height = 10, + min_height = -20 +}) + +animalia.register_biome_group("tropical", { + name_kw = "", + turf_kw = "litter", + min_heat = 70, + max_heat = 90, + min_humidity = 65, + max_humidity = 90 +}) + +animalia.register_biome_group("cave", { + name_kw = "under", + turf_kw = "", + min_heat = 0, + max_heat = 100, + min_humidity = 0, + max_humidity = 100, + max_height = 5 +}) + +animalia.register_biome_group("common", { + name_kw = "", + turf_kw = "", + min_heat = 25, + max_heat = 75, + min_humidity = 20, + max_humidity = 80, + min_height = 1 +}) + +--------------- +-- Libri API -- +--------------- + +local function contains_item(inventory, item) + return inventory and inventory:contains_item("main", ItemStack(item)) +end + +function animalia.get_libri(inventory) + local list = inventory:get_list("main") + for i = 1, inventory:get_size("main") do + local stack = list[i] + if stack:get_name() + and stack:get_name() == "animalia:libri_animalia" then + return stack, i + end + end +end + +local get_libri = animalia.get_libri + +function animalia.add_libri_page(self, player, page) + local inv = minetest.get_inventory({type = "player", name = player:get_player_name()}) + if contains_item(inv, "animalia:libri_animalia") then + local libri, list_i = get_libri(inv) + local pages = minetest.deserialize(libri:get_meta():get_string("pages")) or {} + if #pages > 0 then + local add_page = true + for i = 1, #pages do + if pages[i].name == page.name then + add_page = false + break + end + end + if add_page then + table.insert(pages, page) + libri:get_meta():set_string("pages", minetest.serialize(pages)) + inv:set_stack("main", list_i, libri) + return true + end + else + table.insert(pages, page) + libri:get_meta():set_string("pages", minetest.serialize(pages)) + inv:set_stack("main", list_i, libri) + return true + end + end +end + +function animalia.get_item_list(list, offset_x, offset_y) -- Creates a visual list of items for Libri formspecs + local size = 1 / #list + if size < 0.45 then size = 0.45 end + local spacing = 0.3 + local total_scale = size + spacing + local max_horiz = 3 + local max_verti = 6 + local form = {} + for i = 1, #list do + local vert_multi = math.floor((i - 1) / max_horiz) + local horz_multi = (total_scale * max_horiz) * vert_multi + table.insert(form, "item_image[" .. offset_x + ((total_scale * i) - horz_multi) .. "," .. offset_y + (total_scale * vert_multi ).. ";" .. size .. "," .. size .. ";" .. list[i] .. "]") + end + return table.concat(form, "") +end + +-- Libri should list: Spawn Biomes, Drops, Food, Taming Method, Catchability, and Lassoability + + +local function get_inventory_cube(name) + local def = minetest.registered_nodes[name] + local tiles + if name:find(".png") then + tiles = { + name, + name, + name + } + elseif def then + tiles = table.copy(def.tiles) or table.copy(def.textures) + else + return + end + if not tiles + or type(tiles) ~= "table" + or #tiles < 1 then + return + end + for i = 1, #tiles do + if type(tiles[i]) == "table" then + tiles[i] = tiles[i].name + end + end + local cube + if #tiles < 3 then + cube = minetest.inventorycube(tiles[1], tiles[1], tiles[1]) + else + cube = minetest.inventorycube(tiles[1], tiles[3], tiles[3]) + end + return cube +end + +local function get_textures(name) + local def = minetest.registered_entities[name] + local textures = def.textures + if not textures then + if #def.female_textures < 2 then + textures = {def.female_textures[1], def.male_textures[1]} + else + textures = {} + local num = #def.female_textures + for i = 1, num do + if num + #def.male_textures < 7 then + textures = {unpack(def.male_textures), unpack(def.female_textures)} + else + if i < num * 0.5 then + table.insert(textures, def.female_textures[i]) + else + table.insert(textures, def.male_textures[i]) + end + end + end + end + end + return textures +end + +local animalia_libri_info = {} + +local libri_animal_info = { + ["animalia:bat"] = { + invcube = "default:stone", + info = { + domestication = { + "While they can't be truly", + "domesticated, Bats will begin ", + "to trust you if you feed them ", + "often. A Bat that trusts you will ", + "not flee when you walk near it.", + "This is useful as it allows ", + "Players to keep them around ", + "to harvest their guano, which ", + "can be used as a powerful ", + "fertilizer." + }, + behavior = { + "Bats are mostly harmless, and ", + "can be found hanging from ", + "trees and cliff ceilings during ", + "the day. The only harm they ", + "can cause it to property, with ", + "guano accumulating ", + "underneath them while they ", + "rest. Being social creatures, it's ", + "not uncommon to see a few ", + "hanging from ceilings together ", + "or swarming, which often ", + "occurs at evening or when a ", + "Player approaches." + } + } + }, + ["animalia:bird"] = { + info = { + domestication = { + "Cannot be tamed.", + }, + behavior = { + "Song Birds are found across ", + "various biomes, except for ", + "biomes too inhospitable like ", + "deserts or tundras. They fly in ", + "flocks that vary in size from 4 ", + "or 5 individuals to large flocks ", + "exceeding a dozen individuals. Their calls vary between ", + "species, making it easy to tell ", + "what kind of birds are around." + } + } + }, + ["animalia:cat"] = { + info = { + domestication = { + "Unlike Wolves and Horses," , + "which are almost immediately ", + "trusting upon being tamed, ", + "Cats will remain untrusting ", + "until you gain their trust. To do ", + "so, you must feed and play ", + "with it often. As trust builds ", + "the cat will become more ", + "comfortable in your presence, ", + "and will be more receptive to ", + "commands.", + }, + behavior = { + "Cats are very annoying ", + "animals, to the point that ", + "some may even call them a ", + "pest. Their behavior in the ", + "wild is somehow more tame ", + "than their domesticated ", + "behavior. They find immense ", + "joy in running front of their ", + "owner and even destroying ", + "glass vessels. Despite this, ", + "they are an incredibly popular ", + "pet, especially for those who ", + "don't often leave their home. ", + "Like Wolves, a tamed Cat will ", + "follow commands, but only if it ", + "highly trusts it's owner." + } + } + }, + ["animalia:chicken"] = { + info = { + domestication = { + "Chickens are very valuable as a ", + "livestock. They're a good ", + "source of meat, but also lay ", + "eggs. This, paired with their ", + "small size, makes them great ", + "for farming with limited space." + }, + behavior = { + "Chickens, or Jungle Fowl, are ", + "most often found in groups. ", + "They exhibit gender ", + "dimorphism to a high degree, ", + "with males having large tail ", + "feathers. In the wild, they ", + "dwell jungle floors, picking up ", + "seeds and insects." + } + } + }, + ["animalia:cow"] = { + info = { + domestication = { + "Cows are commonplace on ", + "farms because of their many ", + "uses. They can be slaughtered ", + "for beef and leather, and ", + "females can be milked. Beef is ", + "one of the most valuable ", + "meats because of how much ", + "satiation it provides, and ", + "leather is valuable for crafting ", + "various items." + }, + behavior = { + "Cows are always found in ", + "groups of 3+ individuals. ", + "Despite being capable of ", + "inflicting damage, they will ", + "always choose to flee, even ", + "when in a large group. They ", + "exhibit gender dimorphism, ", + "with females having udders on ", + "their belly." + }, + } + }, + ["animalia:frog"] = { + info = { + domestication = { + "Cannot be tamed.", + }, + behavior = { + "Frogs are small creatures ", + "almost exclusively found near ", + "bodies of water. They will flee ", + "to nearby water when a Player ", + "approaches. They have quite ", + "an affinity for water, moving ", + "faster while in it and only ", + "being able to breed when ", + "submerged. They come to land ", + "to search for food, which they ", + "catch with their long tongue." + }, + } + }, + ["animalia:horse"] = { + info = { + domestication = { + "Horses are one of the most ", + "valuable animals to ", + "domesticate because of their ", + "ability carry Players and ", + "maintain speed. They can ", + "make traversing the world far ", + "faster and easier, but aren't ", + "easy to tame. To tame one, ", + "you must keep your line of ", + "sight lined up with the Horses ", + "for a varying period of time. ", + "This process is difficult but ", + "well worth it." + }, + behavior = { + "Horses live in large groups, ", + "wandering open grasslands. ", + "They have a number of colors ", + "and patterns, which are passed ", + "down to their offspring, as ", + "well as varying jumping and ", + "running abilities." + }, + } + }, + ["animalia:reindeer"] = { + info = { + domestication = { + "Cannot be tamed.", + }, + behavior = { + "Reindeer are found in large ", + "groups in cold regions. They ", + "stick tightly togther and move ", + "in coordinated directions, even ", + "while fleeing. They're also a ", + "common food source for those ", + "lost in taigas and tundras." + } + } + }, + ["animalia:pig"] = { + info = { + domestication = { + "Pigs are not quite as versatile ", + "as other livestock like Cows or ", + "Chickens, with their only ", + "valuable resource being pork. ", + "But they have a distinct ", + "advantage by being able to ", + "have more offspring at once ", + "than Cows while also being ", + "smaller." + }, + behavior = { + "Pigs in the wild can be very ", + "destructive of ecosystems if ", + "not controlled. Their ability to ", + "reproduce quickly means ", + "keeping populations under ", + "control can be an issue. They ", + "are known to destroy farmland ", + "and will go as far as destroying ", + "fences to do so." + }, + } + }, + ["animalia:sheep"] = { + info = { + domestication = { + "Sheep are one of the most ", + "useful animals to domesticate. ", + "Their wool is a great resource ", + "for crafting and building, and is ", + "entirely renewable. Their wool ", + "can also be dyed, though there ", + "is little use for this." + }, + behavior = { + "Sheep are well known for ", + "living in large groups. In the ", + "wild these groups range from 4 ", + "to 8 individuals, larger than ", + "most other animals." + } + } + }, + ["animalia:tropical_fish"] = { + special_models = { + [3] = "animalia_angelfish.b3d" + }, + info = { + domestication = { + "Cannot be tamed." + }, + behavior = { + "All varieties of Tropical Fish ", + "can be found in schools around ", + "reefs. While they don't ", + "provide food or any resources, ", + "they are a beautiful sight to ", + "see while traversing oceans." + }, + } + }, + ["animalia:turkey"] = { + info = { + domestication = { + "Even though Turkeys take up ", + "more space than Chickens, ", + "they also produce more meat, ", + "at the cost of laying less eggs. ", + "This makes them a good option ", + "for those who don't want to ", + "build a farm large enough to ", + "support Cows or other large ", + "livestock but also don't need ", + "many eggs." + }, + behavior = { + "Turkeys are similar ", + "behaviorally to Chickens, but ", + "spawn in colder biomes and ", + "are slightly larger. They exhibit ", + "gender dimorphism, with ", + "males having a large fan of ", + "feathers on their tail." + } + } + }, + ["animalia:wolf"] = { + info = { + domestication = { + "Their intelligence allows them ", + "not only to form tight bonds ", + "with players, but to also obey ", + "orders. Once ordered to attack ", + "a target, they will pursue it and ", + "attack relentlessly, even if ", + "death certain." + }, + behavior = { + "Wolves are found in packs of ", + "up to 3. They hunt down Sheep ", + "as a group and can quickly ", + "overwhelm their target with ", + "numbers. They're also ", + "remarkebly intelligent, and ", + "will remember players who ", + "have harmed them and will ", + "attack them on sight." + } + } + } +} + +-- Libri Utilities -- + +local function offset_info_text(offset_x, offset_y, tbl) + local info_text = {} + for i = 1, #tbl do + local str = tbl[i] + local center_offset = 0 + if string.len(str) < 30 then + center_offset = (30 - string.len(str)) * 0.05 + end + table.insert(info_text, "label[" .. offset_x + center_offset .. "," .. offset_y + i * 0.25 .. ";" .. minetest.colorize("#383329", tbl[i] .. "\n") .. "]") + end + return table.concat(info_text, "") +end + +local function get_libri_page(mob_name, player_name) + local def = minetest.registered_entities[mob_name] + local animal_info = libri_animal_info[mob_name] + -- Get Inventory Cube and Mob Texture + local biome_group = spawn_biomes[mob_name] + local spawn_biome = animalia.registered_biome_groups[biome_group].biomes[animalia_libri_info[player_name].biome_idx] or "grassland" + local invcube + if not minetest.registered_biomes[spawn_biome] + or not minetest.registered_biomes[spawn_biome].node_top then + invcube = get_inventory_cube("unknown_node.png") + else + invcube = get_inventory_cube(animal_info.invcube or minetest.registered_biomes[spawn_biome].node_top) + end + local texture = get_textures(mob_name)[animalia_libri_info[player_name].texture_idx] + local mesh = def.mesh + if libri_animal_info[mob_name].special_models + and libri_animal_info[mob_name].special_models[animalia_libri_info[player_name].texture_idx] then + mesh = libri_animal_info[mob_name].special_models[animalia_libri_info[player_name].texture_idx] + end + -- Create Formspec + local form = { + -- Background + "formspec_version[3]", + "size[16,10]", + "background[-0.7,-0.5;17.5,11.5;animalia_libri_bg_v2.png]", + "image[-0.7,-0.5;17.5,11.5;animalia_libri_info_fg.png]", + -- Mesh + "model[1.5,1.5;5,5;libri_mesh;" .. mesh .. ";" .. texture .. ";-30,225;false;false;0,0;0]", + -- Spawn Biome Group + "image[0.825,8.15;1,1;" .. invcube .. "]", + "tooltip[0.825,8.15;1,1;" .. correct_name(spawn_biome) .. "]", + -- Health + "image[2.535,8.15;1,1;animalia_libri_health_fg.png]", + "label[3.25,9;x" .. def.max_health / 2 .. "]", + -- Net + "item_image[4.25,8.15;1,1;animalia:lasso]", + "image[4.75,8.75;0.5,0.5;animalia_libri_true_icon.png]", + -- Lasso + "item_image[6,8.15;1,1;animalia:net]", + "image[6.5,8.75;0.5,0.5;animalia_libri_true_icon.png]", + -- Labels + "label[9.5,7.25;" .. minetest.colorize("#383329", "Drops:") .. "]", + "label[14,7.25;" .. minetest.colorize("#383329", "Eats:") .. "]", + -- Info Text + "label[9.25,1.5;" .. minetest.colorize("#000000", "Domestication:") .. "]", + "label[13.5,1.5;" .. minetest.colorize("#000000", "Behavior:") .. "]", + } + -- Mob Info + if libri_animal_info[mob_name] then + if libri_animal_info[mob_name].info.domestication then + table.insert(form, offset_info_text(8.5, 2, libri_animal_info[mob_name].info.domestication)) + end + if libri_animal_info[mob_name].info.behavior then + table.insert(form, offset_info_text(12.5, 2, libri_animal_info[mob_name].info.behavior)) + end + end + if def.follow then + table.insert(form, animalia.get_item_list(def.follow, 12.35, 8.05)) + end + if def.drops then + local drops = {} + for i = 1, #def.drops do + table.insert(drops, def.drops[i].name) + end + table.insert(form, animalia.get_item_list(drops, 8, 8.05)) + end + return table.concat(form, "") +end + +local function update_libri(player_name, mob_name) + if not animalia_libri_info[player_name] + or animalia_libri_info[player_name].name ~= mob_name then + return + end + local texture_idx = animalia_libri_info[player_name].texture_idx or 1 + local biome_idx = animalia_libri_info[player_name].biome_idx or 1 + if texture_idx >= #get_textures(mob_name) then + texture_idx = 1 + else + texture_idx = texture_idx + 1 + end + local spawn_biomes = animalia.registered_biome_groups[spawn_biomes[mob_name]].biomes + if biome_idx >= #spawn_biomes then + biome_idx = 1 + else + biome_idx = biome_idx + 1 + end + animalia_libri_info[player_name] = { + texture_idx = texture_idx, + biome_idx = biome_idx, + name = mob_name + } + minetest.show_formspec(player_name, "animalia:libri_" .. string.split(mob_name, ":")[2], get_libri_page(mob_name, player_name)) + minetest.after(4, function() + update_libri(player_name, mob_name) + end) +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + local player_name = player:get_player_name() + if formname == "animalia:libri_main" then + animalia_libri_info[player_name] = {} + for i = 1, #animalia.animals do + local name = string.split(animalia.animals[i], ":")[2] + if fields["pg_" .. name] then + -- Get data for mob and biome visuals + animalia_libri_info[player_name] = { + texture_idx = 1, + biome_idx = 1, + name = animalia.animals[i] + } + update_libri(player_name, animalia.animals[i]) + break + end + end + if fields["btn_next"] then + local pages = animalia.libri_pages[player_name] + if pages + and #pages > 1 then + animalia.show_libri_main_form(player, pages, 2) + end + end + end + if formname:match("^animalia:libri_") then + if fields.quit or fields.key_enter then + animalia_libri_info[player_name] = nil + end + end +end) \ No newline at end of file diff --git a/api/behaviors.lua b/api/behaviors.lua new file mode 100644 index 0000000..993b255 --- /dev/null +++ b/api/behaviors.lua @@ -0,0 +1,1582 @@ +--------------- +-- Behaviors -- +--------------- + +-- Math -- + +local abs = math.abs +local random = math.random +local ceil = math.ceil +local floor = math.floor +local rad = math.rad + +local function average(t) + local sum = 0 + for _,v in pairs(t) do -- Get the sum of all numbers in t + sum = sum + v + end + return sum / #t +end + +local function clamp(val, min, max) + if val < min then + val = min + elseif max < val then + val = max + end + return val +end + +-- Vector Math -- + +local vec_dist = vector.distance +local vec_dir = vector.direction +local vec_sub = vector.subtract +local vec_add = vector.add +local vec_multi = vector.multiply +local vec_normal = vector.normalize + +local function vec_raise(v, n) + return {x = v.x, y = v.y + n, z = v.z} +end + +local yaw2dir = minetest.yaw_to_dir +local dir2yaw = minetest.dir_to_yaw + +-------------- +-- Settings -- +-------------- + +------------ +-- Tables -- +------------ + +local is_flyable = {} +local is_liquid = {} +local is_solid = {} + +minetest.register_on_mods_loaded(function() + for name in pairs(minetest.registered_nodes) do + if name ~= "air" and name ~= "ignore" then + if minetest.registered_nodes[name].walkable + or minetest.registered_nodes[name].drawtype == "liquid" then + is_flyable[name] = true + if minetest.registered_nodes[name].walkable then + is_solid[name] = true + else + is_liquid[name] = true + end + end + end + end +end) + +--------------------- +-- Local Utilities -- +--------------------- + +local moveable = creatura.is_pos_moveable +local fast_ray_sight = creatura.fast_ray_sight + +local function get_ground_level(pos2, max_height) + local node = minetest.get_node(pos2) + local node_under = minetest.get_node({ + x = pos2.x, + y = pos2.y - 1, + z = pos2.z + }) + local height = 0 + local walkable = is_solid[node_under.name] and not is_solid[node.name] + if walkable then + return pos2 + elseif not walkable then + if not is_solid[node_under.name] then + while not is_solid[node_under.name] + and height < max_height do + pos2.y = pos2.y - 1 + node_under = minetest.get_node({ + x = pos2.x, + y = pos2.y - 1, + z = pos2.z + }) + height = height + 1 + end + else + while is_solid[node.name] + and height < max_height do + pos2.y = pos2.y + 1 + node = minetest.get_node(pos2) + height = height + 1 + end + end + return pos2 + end +end + +local function get_ceiling_positions(pos, range) + local walkable = minetest.find_nodes_in_area( + {x = pos.x + range, y = pos.y + range, z = pos.z + range}, + {x = pos.x - range, y = pos.y, z = pos.z - range}, + animalia.walkable_nodes + ) + if #walkable < 1 then return {} end + local output = {} + for i = 1, #walkable do + local i_pos = walkable[i] + local under = { + x = i_pos.x, + y = i_pos.y - 1, + z = i_pos.z + } + if minetest.get_node(under).name == "air" + and is_solid[minetest.get_node(i_pos).name] then + table.insert(output, i_pos) + end + end + return output +end + +local function get_obstacle_avoidance(self, lift) + local pos = self.object:get_pos() + local yaw = self.object:get_yaw() + pos.y = pos.y + self.stepheight + local vel = self.object:get_velocity() + local vel_len = abs(vector.length(vel)) + if vel_len < 1.5 then + vel_len = 1.5 + end + local dir = yaw2dir(yaw) + dir.y = lift + local outset = vec_add(pos, vec_multi(dir, vel_len)) + local pos2 + local obstacle = false + if not fast_ray_sight(pos, outset) then + pos2 = vec_add(pos, vec_multi(dir, -vel_len)) + obstacle = true + end + return pos2, obstacle +end + +local function get_wander_pos_3d(self, range) + local outset = random(range or 4) + local pos = self.object:get_pos() + local move_dir = { + x = random(-10, 10) * 0.1, + z = random(-10, 10) * 0.1, + y = random(-10, 10) * 0.1 + } + local pos2 = vec_add(pos, vec_multi(vec_normal(move_dir), random(1, outset))) + local sight, dist = fast_ray_sight(pos, pos2, true) + if not sight then + pos2 = vec_add(pos, vec_multi(vec_normal(move_dir), dist - 0.5)) + end + return pos2 +end + +local function get_boid_members(pos, radius, name, texture_no) + local objects = minetest.get_objects_inside_radius(pos, radius) + if #objects < 2 then return {} end + local members = {} + local max_boid = minetest.registered_entities[name].max_boids or 7 + for i = 1, #objects do + if #members > max_boid then break end + local object = objects[i] + if object:get_luaentity() + and object:get_luaentity().name == name + and object:get_luaentity().texture_no == texture_no then + object:get_luaentity().boid_heading = rad(random(360)) + table.insert(members, object) + end + end + return members +end + +---------------------- +-- Movement Methods -- +---------------------- + +local function movement_fly(self, pos2) + -- Initial Properties + local pos = self.object:get_pos() + local turn_rate = self.turn_rate or 10 + local speed = self.speed or 2 + self:animate("fly") + self:set_gravity(0) + -- Collision Avoidance + local temp_goal = self._movement_data.temp_goal + local obstacle = self._movement_data.obstacle or false + if not temp_goal + or self:pos_in_box(temp_goal, self.width) then + self._movement_data.temp_goal, self._movement_data.obstacle = get_obstacle_avoidance(self, vec_dir(pos, pos2).y) + end + local neighbor = self._movement_data.temp_goal + -- Calculate Movement + local dir = vector.direction(pos, pos2) + local tyaw = minetest.dir_to_yaw(dir) + if neighbor then + local lift = dir.y + dir = vector.direction(pos, neighbor) + if not obstacle then + dir.y = lift + end + tyaw = minetest.dir_to_yaw(dir) + end + if self._path + and #self._path > 1 then + neighbor = self._path[2] + dir = vector.direction(pos, neighbor) + tyaw = minetest.dir_to_yaw(dir) + if self:pos_in_box(neighbor, self.width + 0.2) then + table.remove(self._path, 1) + end + else + self._path = creatura.find_path(self, pos, pos2, self.width, self.height, 300, false, true) + end + -- Apply Movement + self:turn_to(boid_angle or tyaw, turn_rate) + self:set_forward_velocity(speed) + local v_speed = speed * dir.y + local vel = self.object:get_velocity() + vel.y = vel.y + (v_speed - vel.y) * 0.2 + self:set_vertical_velocity(vel.y) + if self:pos_in_box(pos2) then + self:halt() + end +end + +creatura.register_movement_method("animalia:fly_path", movement_fly) + +local function movement_fly_waypoints(self, pos2, speed) + -- Initial Properties + local pos = self.object:get_pos() + self:animate("fly") + self:set_gravity(0) + -- Collision Avoidance + local temp_goal = self._movement_data.temp_goal + local obstacle = self._movement_data.obstacle or false + if not temp_goal + or self:pos_in_box(temp_goal, 0.4) then + self._movement_data.temp_goal, self._movement_data.obstacle = get_obstacle_avoidance(self, vec_dir(pos, pos2).y) + end + local neighbor = self._movement_data.temp_goal + -- Calculate Movement + local dir = vector.direction(pos, pos2) + local tyaw = minetest.dir_to_yaw(dir) + local turn_rate = self.turn_rate or 10 + local speed = self.speed or 2 + if neighbor then + local lift = dir.y + dir = vector.direction(pos, neighbor) + if not obstacle then + dir.y = lift + end + tyaw = minetest.dir_to_yaw(dir) + end + -- Apply Movement + self:turn_to(boid_angle or tyaw, turn_rate) + self:set_forward_velocity(speed) + local v_speed = speed * dir.y + local vel = self.object:get_velocity() + vel.y = vel.y + (v_speed - vel.y) * 0.2 + self:set_vertical_velocity(vel.y) + if self:pos_in_box(pos2) then + self:halt() + end +end + +creatura.register_movement_method("animalia:fly_waypoints", movement_fly_waypoints) + +-- Fly Obstacle Avoidance -- + +local function movement_fly_obstacle_avoidance(self, pos2, speed) + -- Initial Properties + local pos = self.object:get_pos() + local turn_rate = self.turn_rate or 10 + local speed = self.speed or 2 + self:animate("fly") + self:set_gravity(0) + -- Collision Avoidance + local temp_goal = self._movement_data.temp_goal + local obstacle = self._movement_data.obstacle or false + local timer = self._movement_data.timer + if not temp_goal + or self:pos_in_box(temp_goal, 0.4) + or (timer + and timer <= 0) then + self._movement_data.temp_goal, self._movement_data.obstacle = get_obstacle_avoidance(self, vec_dir(pos, pos2).y) + temp_goal = self._movement_data.temp_goal + obstacle = self._movement_data.obstacle or false + if temp_goal then + self._movement_data.timer = 3 + end + end + if timer then + self._movement_data.timer = self._movement_data.timer - self.dtime + end + -- Calculate Movement + local dir = vector.direction(pos, pos2) + local tyaw = minetest.dir_to_yaw(dir) + if temp_goal + and obstacle then + dir = vector.direction(pos, temp_goal) + dir.y = vec_dir(pos, pos2).y + tyaw = minetest.dir_to_yaw(dir) + end + -- Apply Movement + self:turn_to(tyaw, turn_rate) + self:set_forward_velocity(speed) + local v_speed = (speed) * dir.y + local vel = self.object:get_velocity() + vel.y = vel.y + (v_speed - vel.y) * 0.2 + self:set_vertical_velocity(vel.y) + if self:pos_in_box(pos2, 0.5) then + self:halt() + end +end + +creatura.register_movement_method("animalia:fly_obstacle_avoidance", movement_fly_obstacle_avoidance) + +-- Swimming -- + +local function movement_swim_obstacle_avoidance(self, pos2, speed) + -- Initial Properties + local pos = self.object:get_pos() + self:animate("swim") + self:set_gravity(0) + -- Collision Avoidance + local temp_goal = self._movement_data.temp_goal + local obstacle = self._movement_data.obstacle or false + local timer = self._movement_data.timer + if not temp_goal + or self:pos_in_box(temp_goal, 0.4) + or (timer + and timer <= 0) then + self._movement_data.temp_goal, self._movement_data.obstacle = get_obstacle_avoidance(self, vec_dir(pos, pos2).y) + temp_goal = self._movement_data.temp_goal + obstacle = self._movement_data.obstacle or false + if temp_goal then + self._movement_data.timer = vec_dist(pos, temp_goal) / self.speed + end + end + if timer then + self._movement_data.timer = self._movement_data.timer - self.dtime + end + -- Calculate Movement + local dir = vector.direction(pos, pos2) + local tyaw = minetest.dir_to_yaw(dir) + if temp_goal + and obstacle then + dir = vector.direction(pos, temp_goal) + tyaw = minetest.dir_to_yaw(dir) + end + -- Apply Movement + self:turn_to(tyaw, turn_rate) + self:set_forward_velocity(speed) + local v_speed = speed * dir.y + local vel = self.object:get_velocity() + vel.y = vel.y + (v_speed - vel.y) * 0.2 + if not is_liquid[minetest.get_node(vec_raise(pos, 1)).name] + and vel.y > 0 then + vel.y = 0 + end + self:set_vertical_velocity(vel.y) + if self:pos_in_box(pos2) then + self:halt() + end +end + +creatura.register_movement_method("animalia:swim_obstacle_avoidance", movement_swim_obstacle_avoidance) + +------------- +-- Actions -- +------------- + +function animalia.action_fall(self) + local function func(self) + self:animate("fall") + self:set_gravity(-1) + local vel = self.object:get_velocity() + if vel.y < -3.8 then + self:set_vertical_velocity(-0.1) + end + self._fall_start = nil + if self.touching_ground then + return true + end + end + self:set_action(func) +end + +function animalia.action_punch(self, target) + local function func(self) + if not creatura.is_alive(target) then + return true + end + local yaw = self.object:get_yaw() + local pos = self.object:get_pos() + local tpos = target:get_pos() + local dir = vector.direction(pos, tpos) + local tyaw = minetest.dir_to_yaw(dir) + self:turn_to(tyaw) + if self.touching_ground then + self:animate("leap") + local jump_vel = vec_multi(dir, self.speed) + jump_vel.y = 3 + self.object:add_velocity(jump_vel) + end + if vec_dist(pos, tpos) < 2 then + self:punch_target(target) + return true + end + end + self:set_action(func) +end + +function animalia.action_latch_to_ceil(self, time, anim) + local timer = time + local function func(self) + self:halt() + self:set_forward_velocity(0) + self:set_vertical_velocity(9) + self:set_gravity(3) + self:animate(anim or "latch") + timer = timer - self.dtime + if timer <= 0 then + return true + end + end + self:set_action(func) +end + +function animalia.action_boid_move(self, pos2, timeout, method) + local boids = get_boid_members(self.object:get_pos(), 6, self.name, self.texture_no) + local timer = timeout + local goal = pos2 + local function func(self) + local pos = self.object:get_pos() + timer = timer - self.dtime + if #boids > 2 then + local boid_angle, boid_lift = creatura.get_boid_angle(self, boids, 6) + if boid_angle then + local dir2goal = vec_dir(pos, pos2) + local yaw2goal = minetest.dir_to_yaw(dir2goal) + boid_angle = boid_angle + (yaw2goal - boid_angle) * 0.15 + local boid_dir = minetest.yaw_to_dir(boid_angle) + if boid_lift then + boid_dir.y = boid_lift + (vec_dir(pos, goal).y - boid_lift) * 0.5 + else + boid_dir.y = vec_dir(pos, goal).y + end + pos2 = vec_add(pos, vec_multi(boid_dir, 4)) + end + end + if timer <= 0 + or self:pos_in_box(pos2, 0.25) then + self:halt() + return true + end + self:move(pos2, method or "animalia:fly_obstacle_avoidance", 1) + end + self:set_action(func) +end + +function animalia.action_boid_walk(self, pos2, timeout, method, speed_factor) + local boids = creatura.get_boid_members(self.object:get_pos(), 12, self.name) + local timer = timeout + local move_init = false + local goal = pos2 + local function func(self) + local pos = self.object:get_pos() + timer = timer - self.dtime + if #boids > 2 then + local boid_angle = creatura.get_boid_angle(self, boids, 12) + if boid_angle then + local dir2goal = vec_dir(pos, goal) + local yaw2goal = minetest.dir_to_yaw(dir2goal) + boid_angle = boid_angle + (yaw2goal - boid_angle) * 0.15 + local boid_dir = minetest.yaw_to_dir(boid_angle) + pos2 = get_ground_level(vec_add(pos, vec_multi(boid_dir, 4)), 2) + end + end + if not pos2 + or (move_init + and not self._movement_data.goal) then + return true + end + if timer <= 0 + or self:pos_in_box({x = goal.x, y = pos.y + 0.1, z = goal.z}) + or vec_dist(pos, goal) < 1 then + self:halt() + return true + end + self:move(pos2, method or "creatura:obstacle_avoidance", speed_factor or 1) + move_init = true + end + self:set_action(func) +end + +function animalia.action_swim(self, pos, timeout, method, speed_factor, anim) + local timer = timeout or 4 + local function func(self) + timer = timer - self.dtime + if timer <= 0 + or self:pos_in_box(pos) then + self:halt() + self:set_gravity(0) + return true + end + self:move(pos, method or "animalia:swim_obstacle_avoidance", speed_factor or 0.5, anim) + self:set_gravity(0) + end + self:set_action(func) +end + +--------------- +-- Behaviors -- +--------------- + +------------------------ +-- Register Utilities -- +------------------------ + +-- Wander + +creatura.register_utility("animalia:wander", function(self, group) + local idle_time = 3 + local move_probability = 3 + local group_tick = 0 + local far_from_group = false + local function func(self) + local pos = self.object:get_pos() + local random_goal = vector.new( + pos.x + random(-4, 4), + pos.y, + pos.z + random(-4, 4) + ) + if group + and self:timer(3) then + local range = self.tracking_range * 0.5 + local group_positions = animalia.get_group_positions(self.name, pos, range + 1) + if #group_positions > 2 then + local center = animalia.get_average_pos(group_positions) + if center + and (vec_dist(random_goal, center) > range + or vec_dist(pos, center) > range) then + random_goal = center + far_from_group = true + else + far_from_group = false + end + end + group_tick = 2 + end + if not self:get_action() then + local node = minetest.get_node(random_goal) + if minetest.registered_nodes[node.name].drawtype == "liquid" + or minetest.registered_nodes[node.name].walkable then + random_goal = nil + end + if self.lasso_pos + and vec_dist(pos, self.lasso_pos) > 10 then + random_goal = self.lasso_pos + end + if (random(move_probability) < 2 + and random_goal) + or far_from_group then + creatura.action_walk(self, random_goal) + else + creatura.action_idle(self, idle_time) + end + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:skittish_boid_wander", function(self) + local idle_time = 3 + local move_probability = 3 + local group_tick = 0 + local force_move = false + local function func(self) + local pos = self.object:get_pos() + local random_goal = vector.new( + pos.x + random(-4, 4), + pos.y, + pos.z + random(-4, 4) + ) + if self:timer(3) then + local range = self.tracking_range * 0.5 + local group_positions = animalia.get_group_positions(self.name, pos, range + 1) + if #group_positions > 2 then + local center = animalia.get_average_pos(group_positions) + if center + and (vec_dist(random_goal, center) > range + or vec_dist(pos, center) > range) then + random_goal = center + force_move = true + else + force_move = false + end + else + force_move = false + end + group_tick = 2 + local player = creatura.get_nearby_player(self) + if player then + local target_alive, line_of_sight, player_pos = self:get_target(player) + if target_alive + and line_of_sight + and vec_dist(pos, player_pos) < 8 then + force_move = true + local dir = vec_dir(pos, player_pos) + random_goal = vec_add(pos, vec_multi(vec_add(dir, {x = random(-10, 10) * 0.1, y = 0, z = random(-10, 10) * 0.1}), -3)) + end + end + end + if not self:get_action() then + local node = minetest.get_node(random_goal) + if minetest.registered_nodes[node.name].drawtype == "liquid" + or minetest.registered_nodes[node.name].walkable then + random_goal = nil + force_move = false + end + if self.lasso_pos + and vec_dist(pos, self.lasso_pos) > 10 then + random_goal = self.lasso_pos + end + if (random(move_probability) < 2 + and random_goal) + or force_move then + get_ground_level(random_goal, 1) + animalia.action_boid_walk(self, random_goal, 6, "creatura:obstacle_avoidance", 0.5) + else + creatura.action_idle(self, idle_time) + end + end + end + self:set_utility(func) +end) + + +creatura.register_utility("animalia:boid_wander", function(self, group) + local idle_time = 3 + local move_probability = 5 + local group_tick = 0 + local function func(self) + local pos = self.object:get_pos() + local random_goal = vector.new( + pos.x + random(-4, 4), + pos.y, + pos.z + random(-4, 4) + ) + if group + and self:timer(3) then + local range = self.tracking_range * 0.5 + local group_positions = animalia.get_group_positions(self.name, pos, range + 1) + if #group_positions > 2 then + local center = animalia.get_average_pos(group_positions) + if center + and (vec_dist(random_goal, center) > range + or vec_dist(pos, center) > range) then + random_goal = center + end + end + group_tick = 2 + end + if not self:get_action() then + local node = minetest.get_node(random_goal) + if minetest.registered_nodes[node.name].drawtype == "liquid" + or minetest.registered_nodes[node.name].walkable then + random_goal = nil + end + if self.lasso_pos + and vec_dist(pos, self.lasso_pos) > 10 then + random_goal = self.lasso_pos + end + if random(move_probability) < 2 + and random_goal then + animalia.action_boid_walk(self, random_goal, 6, "creatura:neighbors", 0.25) + else + creatura.action_idle(self, idle_time) + end + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:wander_water_surface", function(self) + local idle_time = 3 + local move_probability = 3 + local function func(self) + if not self.in_liquid then return true end + local pos = self.object:get_pos() + local random_goal = get_wander_pos_3d(self, 2) + if not self:get_action() then + if self.lasso_pos + and vec_dist(pos, self.lasso_pos) > 10 then + random_goal = self.lasso_pos + end + if random(move_probability) < 2 + and random_goal then + animalia.action_swim(self, random_goal) + else + creatura.action_idle(self, idle_time, "float") + end + end + self:set_gravity(0) + end + self:set_utility(func) +end) + +-- "Eat" nodes + +creatura.register_utility("animalia:eat_from_turf", function(self) + local function func(self) + local pos = self.object:get_pos() + local under = vector.new(pos.x, pos.y - 1, pos.z) + for _, node in ipairs(self.consumable_nodes) do + if node.name == minetest.get_node(under).name then + minetest.set_node(under, {name = node.replacement}) + local def = minetest.registered_nodes[node.name] + local texture = def.tiles[1] + texture = texture .. "^[resize:8x8" + minetest.add_particlespawner({ + amount = 6, + time = 0.1, + minpos = vector.new( + pos.x - 0.5, + pos.y + 0.1, + pos.z - 0.5 + ), + maxpos = vector.new( + pos.x + 0.5, + pos.y + 0.1, + pos.z + 0.5 + ), + minvel = {x=-1, y=1, z=-1}, + maxvel = {x=1, y=2, z=1}, + minacc = {x=0, y=-5, z=0}, + maxacc = {x=0, y=-9, z=0}, + minexptime = 1, + maxexptime = 1, + minsize = 1, + maxsize = 2, + collisiondetection = true, + vertical = false, + texture = texture, + }) + self.gotten = false + self:memorize("gotten", self.gotten) + return true + else + return true + end + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:eat_bug_nodes", function(self) + local timer = 0.2 + local pos = self.object:get_pos() + local food = minetest.find_nodes_in_area(vec_sub(pos, 1.5), vec_add(pos, 1.5), self.follow) + local function func(self) + pos = self.object:get_pos() + if food[1] then + local dist = vector.distance(pos, food[1]) + local dir = vec_dir(pos, food[1]) + local frame = floor(dist * 10) + self:turn_to(dir2yaw(dir)) + if frame < 15 + and frame > 1 then + animalia.move_head(self, dir2yaw(dir), dir.y) + creatura.action_idle(self, 0.1, "tongue_" .. frame) + timer = timer - self.dtime + elseif not self:get_action() then + local pos2 = vec_add(food[1], vec_multi(vec_normal(vec_dir(food[1], pos)), 0.25)) + creatura.action_walk(self, pos2) + end + else + return true + end + if timer <= 0 + and food[1] then + minetest.remove_node(food[1]) + return true + end + end + self:set_utility(func) +end) + +-- Escape Water + +creatura.register_utility("animalia:swim_to_land", function(self) + local init = false + local tpos = nil + local function func(self) + if not init then + for i = 1, 359, 15 do + local yaw = rad(i) + local dir = yaw2dir(yaw) + tpos = animalia.find_collision(self, dir) + if tpos then + local node = minetest.get_node({x = tpos.x, y = tpos.y + 1, z = tpos.z}) + if node.name == "air" then + break + else + tpos = nil + end + end + end + init = true + end + if tpos then + local pos = self.object:get_pos() + local yaw = self.object:get_yaw() + local tyaw = minetest.dir_to_yaw(vec_dir(pos, tpos)) + if abs(tyaw - yaw) > 0.1 then + self:turn_to(tyaw, 12) + end + self:set_gravity(-9.8) + self:set_forward_velocity(self.speed * 0.66) + self:animate("walk") + if vector.distance(pos, tpos) < 1 + or (not self.in_liquid + and self.touching_ground) then + return true + end + else + self.liquid_recovery_cooldown = 5 + return true + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:flop", function(self) + local function func(self) + if self.in_liquid then + return true + end + if not self:get_action() then + creatura.action_idle(self, 0.1, "flop") + end + self:set_vertical_velocity(0) + self:set_gravity(-9.8) + end + self:set_utility(func) +end) + +-- Player Interaction + +creatura.register_utility("animalia:flee_from_player", function(self, player, range) + range = range or self.tracking_range + local function func(self) + local target_alive, line_of_sight, tpos = self:get_target(player) + if not target_alive then return true end + local pos = self.object:get_pos() + local dir = vec_dir(pos, tpos) + local escape_pos = vec_add(pos, vec_multi(vec_add(dir, {x = random(-10, 10) * 0.1, y = 0, z = random(-10, 10) * 0.1}), -3)) + if not self:get_action() then + escape_pos = get_ground_level(escape_pos, 1) + if self.lasso_pos + and vec_dist(pos, self.lasso_pos) > 10 then + escape_pos = self.lasso_pos + end + creatura.action_walk(self, escape_pos, 2, "creatura:obstacle_avoidance", 1, "run") + end + if vec_dist(pos, tpos) > range then + return true + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:boid_flee_from_player", function(self, player, group) + local mobs_in_group = animalia.get_group(self) + if group then + if #mobs_in_group > 0 then + for i = 1, #mobs_in_group do + local mob = mobs_in_group[i] + mob:get_luaentity():initiate_utility("animalia:boid_flee_from_player", mob:get_luaentity(), player) + mob:get_luaentity():set_utility_score(1) + end + end + end + local function func(self) + local target_alive, line_of_sight, tpos = self:get_target(player) + if not target_alive then return true end + local pos = self.object:get_pos() + local dir = vec_dir(pos, tpos) + local escape_pos = vec_add(pos, vec_multi(vec_add(dir, {x = random(-10, 10) * 0.1, y = 0, z = random(-10, 10) * 0.1}), -3)) + if not self:get_action() then + escape_pos = get_ground_level(escape_pos, 1) + if self.lasso_pos + and vec_dist(pos, self.lasso_pos) > 10 then + escape_pos = self.lasso_pos + end + animalia.action_boid_walk(self, escape_pos, 6, "creatura:obstacle_avoidance", 1) + end + if vec_dist(pos, tpos) > self.tracking_range + (#mobs_in_group or 0) then + return true + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:flee_to_water", function(self) + local function func(self) + local pos = self.object:get_pos() + local water = minetest.find_nodes_in_area_under_air(vec_sub(pos, 3), vec_add(pos, 3), {"default:water_source"}) + if water[1] + and vec_dist(pos, water[1]) < 0.5 then + return true + end + if water[1] + and not self:get_action() then + creatura.action_walk(self, water[1]) + else + return true + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:follow_player", function(self, player, force) + local function func(self) + local player_alive, line_of_sight, tpos = self:get_target(player) + -- Return if player is dead, not holding food, or behavior isn't forced + if not player_alive + or (not self:follow_wielded_item(player) + and not force) then + return true + end + local pos = self.object:get_pos() + local dist = vec_dist(pos, tpos) + if dist > self.tracking_range then + return true + end + if not self:get_action() then + if dist > self:get_hitbox(self)[4] + 1.5 then + creatura.action_walk(self, tpos, 6, "creatura:pathfind") + else + creatura.action_idle(self, 0.1, "stand") + end + end + self.head_tracking = player + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:sporadic_flee", function(self) + local timer = 18 + self:clear_action() + if group then + local mobs_in_group = animalia.get_group(self) + if #mobs_in_group > 0 then + for i = 1, #mobs_in_group do + local mob = mobs_in_group[i] + animalia.bh_flee(mob:get_luaentity()) + end + end + end + local function func(self) + local pos = self.object:get_pos() + local random_goal = { + x = pos.x + random(-4, 4), + y = pos.y, + z = pos.z + random(-4, 4) + } + if not self:get_action() then + random_goal = get_ground_level(random_goal, 1) + local node = minetest.get_node(random_goal) + if minetest.registered_nodes[node.name].drawtype == "liquid" + or minetest.registered_nodes[node.name].walkable then + return + end + if self.lasso_pos + and vec_dist(pos, self.lasso_pos) > 10 then + random_goal = self.lasso_pos + end + self._movement_data.speed = self.speed * 1.5 + creatura.action_walk(self, random_goal, 4, "creatura:obstacle_avoidance", 1.5) + end + timer = timer - self.dtime + if timer <= 0 then + return true + end + end + self:set_utility(func) +end) + +-- Mob Interaction + +creatura.register_utility("animalia:mammal_breed", function(self) + local mate = animalia.get_nearby_mate(self, self.name) + if not mate then self.breeding = false return end + local breeding_time = 0 + local function func(self) + if not creatura.is_alive(mate) then + return true + end + local pos = self:get_center_pos() + local tpos = mate:get_pos() + local dist = vec_dist(pos, tpos) - abs(self:get_hitbox(self)[4]) + if dist < 1.75 then + breeding_time = breeding_time + self.dtime + end + if breeding_time >= 2 then + if self.gender == "female" then + for i = 1, self.birth_count or 1 do + local object = minetest.add_entity(pos, self.name) + local ent = object:get_luaentity() + ent.growth_scale = 0.7 + animalia.initialize_api(ent) + animalia.protect_from_despawn(ent) + end + end + self.breeding = false + self.breeding_cooldown = 300 + self:memorize("breeding", self.breeding) + self:memorize("breeding_time", self.breeding_time) + self:memorize("breeding_cooldown", self.breeding_cooldown) + local minp = vector.subtract(pos, 1) + local maxp = vec_add(pos, 1) + animalia.particle_spawner(pos, "heart.png", "float", minp, maxp) + return true + end + if not self:get_action() then + creatura.action_walk(self, tpos) + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:horse_breed", function(self) + local mate = animalia.get_nearby_mate(self, self.name) + if not mate then self.breeding = false return end + local breeding_time = 0 + local function func(self) + if not creatura.is_alive(mate) then + return true + end + local pos = self:get_center_pos() + local tpos = mate:get_pos() + local dist = vec_dist(pos, tpos) - abs(self:get_hitbox(self)[4]) + if dist < 1.75 then + breeding_time = breeding_time + self.dtime + end + if breeding_time >= 2 then + if self.gender == "female" then + local object = minetest.add_entity(pos, self.name) + object:get_luaentity().growth_scale = 0.7 + local ent = object:get_luaentity() + local tex_no = self.texture_no + if random(2) < 2 then + tex_no = mate:get_luaentity().texture_no + end + ent:memorize("texture_no", tex_no) + ent:memorize("speed", random(mate:get_luaentity().speed, self.speed)) + ent:memorize("jump_power", random(mate:get_luaentity().jump_power, self.jump_power)) + ent:memorize("max_hp", random(mate:get_luaentity().max_hp, self.max_hp)) + ent.speed = ent:recall("speed") + ent.jump_power = ent:recall("jump_power") + ent.max_hp = ent:recall("max_hp") + animalia.initialize_api(ent) + animalia.protect_from_despawn(ent) + end + self.breeding = false + self.breeding_cooldown = 300 + self:memorize("breeding", self.breeding) + self:memorize("breeding_time", self.breeding_time) + self:memorize("breeding_cooldown", self.breeding_cooldown) + local minp = vector.subtract(pos, 1) + local maxp = vec_add(pos, 1) + animalia.particle_spawner(pos, "heart.png", "float", minp, maxp) + return true + end + if not self:get_action() then + creatura.action_walk(self, tpos) + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:bird_breed", function(self) + local mate = animalia.get_nearby_mate(self, self.name) + if not mate then self.breeding = false return end + local breeding_time = 0 + local function func(self) + if not creatura.is_alive(mate) then + return true + end + local pos = self:get_center_pos() + local tpos = mate:get_pos() + local dist = vec_dist(pos, tpos) - abs(self:get_hitbox(self)[4]) + if dist < 1.75 then + breeding_time = breeding_time + self.dtime + end + if breeding_time >= 2 then + if self.gender == "female" then + minetest.add_particlespawner({ + amount = 6, + time = 0.25, + minpos = {x = pos.x - 7/16, y = pos.y - 5/16, z = pos.z - 7/16}, + maxpos = {x = pos.x + 7/16, y = pos.y - 5/16, z = pos.z + 7/16}, + minvel = vector.new(-1, 2, -1), + maxvel = vector.new(1, 5, 1), + minacc = vector.new(0, -9.81, 0), + maxacc = vector.new(0, -9.81, 0), + collisiondetection = true, + texture = "animalia_egg_fragment.png", + }) + for i = 1, self.birth_count or 1 do + local object = minetest.add_entity(pos, self.name) + local ent = object:get_luaentity() + ent.growth_scale = 0.7 + animalia.initialize_api(ent) + animalia.protect_from_despawn(ent) + end + end + self.breeding = false + self.breeding_cooldown = 300 + self:memorize("breeding", self.breeding) + self:memorize("breeding_time", self.breeding_time) + self:memorize("breeding_cooldown", self.breeding_cooldown) + local minp = vector.subtract(pos, 1) + local maxp = vec_add(pos, 1) + animalia.particle_spawner(pos, "heart.png", "float", minp, maxp) + return true + end + if not self:get_action() then + creatura.action_walk(self, tpos) + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:attack", function(self, target, group) + local punch_init = false + if group then + local allies = creatura.get_nearby_entities(self, self.name) + if #allies > 0 then + for i = 1, #allies do + allies[i]:get_luaentity():initiate_utility("animalia:attack", allies[i]:get_luaentity(), target) + allies[i]:get_luaentity():set_utility_score(1) + end + end + end + local function func(self) + local target_alive, line_of_sight, tpos = self:get_target(target) + if not target_alive then + return true + end + local pos = self.object:get_pos() + local dist = vec_dist(pos, tpos) + if not self:get_action() then + if punch_init then return true end + --if dist > self:get_hitbox(self)[4] then + creatura.action_walk(self, tpos, 6, "creatura:theta_pathfind", 1) + --end + end + if dist <= self:get_hitbox(self)[4] + 1 + and not punch_init then + animalia.action_punch(self, target) + punch_init = true + end + end + self:set_utility(func) +end) + +-- Flight + +creatura.register_utility("animalia:aerial_flock", function(self, scale) + local range = ceil(8 * scale) + local function func(self) + if self:timer(2) + and self.stamina <= 0 then + local boids = creatura.get_boid_members(self.object:get_pos(), 6, self.name) + if #boids > 1 then + for i = 1, #boids do + local boid = boids[i] + local ent = boid:get_luaentity() + ent.stamina = ent:memorize("stamina", 0) + ent.is_landed = ent:memorize("is_landed", true) + end + end + end + local dist2floor = creatura.sensor_floor(self, 2, true) + local dist2ceil = creatura.sensor_ceil(self, 2, true) + if self.in_liquid then + dist2floor = 0 + dist2ceil = 2 + end + if dist2floor < 2 + and dist2ceil < 2 then + self.is_landed = true + return true + end + if not self:get_action() + or (dist2floor < 2 + or dist2ceil < 2) then + local pos = self.object:get_pos() + local pos2 = self:get_wander_pos_3d(2, range) + if dist2ceil < 2 then + pos2.y = pos.y - 1 + end + if dist2floor < 2 then + pos2.y = pos.y + 1 + end + if self.in_liquid then + pos2.y = pos.y + 2 + end + animalia.action_boid_move(self, pos2, 2) + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:aerial_swarm", function(self, scale) + local function func(self) + if self:timer(2) + and self.stamina <= 0 then + local boids = creatura.get_boid_members(self.object:get_pos(), 6, self.name) + if #boids > 1 then + for i = 1, #boids do + local boid = boids[i] + local ent = boid:get_luaentity() + ent.stamina = ent:memorize("stamina", 0) + ent.is_landed = ent:memorize("is_landed", true) + end + end + end + local dist2floor = creatura.sensor_floor(self, 2, true) + local dist2ceil = creatura.sensor_ceil(self, 2, true) + if self.in_liquid then + dist2floor = 0 + dist2ceil = 2 + end + if not self:get_action() + or (dist2floor < 2 + or dist2ceil < 2) then + local pos = self.object:get_pos() + local pos2 = self:get_wander_pos_3d(1, 3) + if dist2floor < 2 then + pos2.y = pos.y + 1 + end + if dist2ceil < 2 then + pos2.y = pos.y - 1 + end + animalia.action_boid_move(self, pos2, 2) + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:land", function(self, scale) + local function func(self) + if self.touching_ground then return true end + local _, node = creatura.sensor_floor(self, 3, true) + if node and is_liquid[node.name] then self.is_landed = false return true end + scale = scale or 1 + local width = self.width + local pos = self.object:get_pos() + local pos2 + if self:timer(1) then + local offset = random(2 * scale, 3 * scale) + if random(2) < 2 then + offset = offset * -1 + end + pos2 = { + x = pos.x + offset, + y = pos.y, + z = pos.z + offset + } + pos2.y = pos2.y - (3 * scale) + end + if not self:get_action() + and pos2 then + self:animate("fly") + creatura.action_walk(self, pos2, 2, "animalia:fly_path", 1) + end + end + self:set_utility(func) +end) + +-- Swimming + +creatura.register_utility("animalia:schooling", function(self) + local pos = self.object:get_pos() + local water = minetest.find_nodes_in_area(vector.subtract(pos, 5), vector.add(pos, 5), {"group:water"}) + local function func(self) + if not self:get_action() then + if #water < 1 then return true end + local iter = random(#water) + local pos2 = water[iter] + table.remove(water, iter) + animalia.action_boid_move(self, pos2, 2, "animalia:swim_obstacle_avoidance") + end + end + self:set_utility(func) +end) + +-- Resist Fall + +creatura.register_utility("animalia:resist_fall", function(self) + local function func(self) + if not self:get_action() then + animalia.action_fall(self) + end + if self.touching_ground + or self.in_liquid then + creatura.action_idle(self, "stand") + self:set_gravity(-9.8) + return true + end + end + self:set_utility(func) +end) + +-- Die + +creatura.register_utility("animalia:die", function(self) + local timer = 1.5 + local init = false + local function func(self) + if not init then + self:play_sound("death") + creatura.action_fallover(self) + init = true + end + timer = timer - self.dtime + if timer <= 0 then + local pos = self.object:get_pos() + minetest.add_particlespawner({ + amount = 8, + time = 0.25, + minpos = {x = pos.x - 0.1, y = pos.y, z = pos.z - 0.1}, + maxpos = {x = pos.x + 0.1, y = pos.y + 0.1, z = pos.z + 0.1}, + minacc = {x = 0, y = 2, z = 0}, + maxacc = {x = 0, y = 3, z = 0}, + minvel = {x = random(-1, 1), y = -0.25, z = random(-1, 1)}, + maxvel = {x = random(-2, 2), y = -0.25, z = random(-2, 2)}, + minexptime = 0.75, + maxexptime = 1, + minsize = 4, + maxsize = 4, + texture = "creatura_smoke_particle.png", + animation = { + type = 'vertical_frames', + aspect_w = 4, + aspect_h = 4, + length = 1, + }, + glow = 1 + }) + creatura.drop_items(self) + self.object:remove() + end + end + self:set_utility(func) +end) + +-- Cat Exclusive Behaviors + +creatura.register_utility("animalia:find_and_break_glass_vessels", function(self) + local timer = 12 + local pos = self.object:get_pos() + local pos2 = nil + local nodes = minetest.find_nodes_in_area( + vector.subtract(pos, 8), + vec_add(pos, 8), + {"vessels:glass_bottle", "vessels:drinking_glass"} + ) + if #nodes > 0 then + pos2 = nodes[1] + end + local func = function(self) + if not pos2 then + return + end + pos = self.object:get_pos() + if not self:get_action() then + creatura.action_walk(self, pos2, 6, "pathfind") + end + if vector.distance(pos, pos2) <= 0.5 then + creatura.action_idle(self, 0.7, "smack") + minetest.remove_node(pos2) + minetest.add_item(pos2, "vessels:glass_fragments") + if minetest.get_node(pos2).name == "air" then + return true + end + end + timer = timer - self.dtime + if timer < 0 then return true end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:walk_ahead_of_player", function(self, player) + if not player then return end + local timer = 8 + local func = function(self) + if not creatura.is_alive(player) then + return true + end + local pos = self.object:get_pos() + local tpos = player:get_pos() + local dir = player:get_look_dir() + tpos.x = tpos.x + dir.x + tpos.z = tpos.z + dir.z + self.status = self:memorize("status", "following") + local dist = vec_dist(pos, tpos) + if dist > self.view_range then + self.status = self:memorize("status", "") + return true + end + if not self:get_action() then + if vec_dist(pos, tpos) > self.width + 0.5 then + creatura.action_walk(self, tpos, 6, "pathfind", 0.75) + else + creatura.action_idle(self, 0.1, "stand") + end + end + timer = timer - self.dtime + if timer < 0 then self.status = self:memorize("status", "") return true end + end + self:set_utility(func) +end) + +-- Bat Exclusive Behaviors + +creatura.register_utility("animalia:return_to_home", function(self) + local init = false + local tpos = nil + local function func(self) + if not self.home_position then return true end + local pos = self.object:get_pos() + local pos2 = self.home_position + if not self:get_action() then + creatura.action_walk(self, vec_raise(pos2, -1), 6, "animalia:fly_path", 1) + end + local dist = vec_dist(pos, pos2) + if dist < 2 then + if is_solid[minetest.get_node(vec_raise(pos, 1)).name] then + creatura.action_idle(self, 1, "latch") + self:set_gravity(9.8) + self.object:set_velocity({x = 0, y = 0, z = 0}) + end + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:find_home", function(self) + local init = false + local tpos = nil + local pos = self.object:get_pos() + local range = self.tracking_range + local ceiling = get_ceiling_positions(pos, range / 2) + local iter = 1 + local function func(self) + if not ceiling[1] then + return true + else + iter = random(#ceiling) + end + pos = self.object:get_pos() + if not self:get_action() then + local pos2 = get_wander_pos_3d(self, range) + local dist2floor = creatura.sensor_floor(self, 5, true) + local dist2ceil = creatura.sensor_ceil(self, 5, true) + if dist2floor < 4 then + pos2.y = pos.y + 2 + elseif dist2ceil < 4 then + pos2.y = pos.y - 1 + end + animalia.action_boid_move(self, pos2, 2) + end + if ceiling[iter] then + local pos2 = { + x = ceiling[iter].x, + y = ceiling[iter].y - 1, + z = ceiling[iter].z + } + local line_of_sight = fast_ray_sight(pos, pos2) + if line_of_sight then + self.home_position = self:memorize("home_position", ceiling[iter]) + return true + end + end + if self:timer(1) then + iter = iter + 1 + if iter > #ceiling then + return true + end + end + end + self:set_utility(func) +end) + +-- Tamed Animal Orders + +creatura.register_utility("animalia:sit", function(self) + local function func(self) + if self.order ~= "sit" then + return true + end + if not self:get_action() then + creatura.action_idle(self, 0.1, "sit") + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:mount", function(self, player) + local function func(self) + if not creatura.is_alive(player) then + return true + end + local anim = "stand" + local control = player:get_player_control() + local speed_factor = 0 + local vel = self.object:get_velocity() + if control.up then + speed_factor = 1 + if control.aux1 then + speed_factor = 1.5 + end + end + if control.jump + and self.touching_ground then + self.object:add_velocity({ + x = 0, + y = self.jump_power + (abs(self._movement_data.gravity) * 0.33), + z = 0 + }) + elseif not self.touching_ground then + speed_factor = speed_factor * 0.5 + end + local total_speed = vector.length(vel) + if total_speed > 0.2 then + anim = "walk" + if total_speed > self.speed then + anim = "run" + end + if not self.touching_ground + and not self.in_liquid + and vel.y > 0 then + anim = "rear_constant" + end + end + self:turn_to(player:get_look_horizontal()) + self:set_forward_velocity(self.speed * speed_factor) + self:animate(anim) + if control.sneak + or not self.rider then + animalia.mount(self, player) + return true + end + end + self:set_utility(func) +end) \ No newline at end of file diff --git a/api/lasso.lua b/api/lasso.lua new file mode 100644 index 0000000..c965656 --- /dev/null +++ b/api/lasso.lua @@ -0,0 +1,360 @@ +----------- +-- Lasso -- +----------- + +local abs = math.abs + +function animalia.initialize_lasso(self) + self.lasso_origin = self:recall("lasso_origin") or nil + if self.lasso_origin then + self.caught_with_lasso = true + if type(self.lasso_origin) == "table" + and minetest.get_item_group(minetest.get_node(self.lasso_origin).name, "fence") > 0 then + local object = minetest.add_entity(self.lasso_origin, "animalia:lasso_fence_ent") + object:get_luaentity().parent = self.object + elseif type(self.lasso_origin) == "string" + and minetest.get_player_by_name(self.lasso_origin) then + self.lasso_origin = minetest.get_player_by_name(self.lasso_origin) + else + self:forget("lasso_origin") + end + end +end + +function animalia.set_lasso_visual(self, target) + if not creatura.is_alive(self) + or (self.lasso_visual + and self.lasso_visual:get_luaentity()) then return end + local pos = self.object:get_pos() + local object = minetest.add_entity(pos, "animalia:lasso_visual") + local ent = object:get_luaentity() + self.lasso_visual = object + self.lasso_origin = target + ent.parent = self.object + ent.lasso_origin = target + return object +end + +function animalia.update_lasso_effects(self) + if not creatura.is_alive(self) then return end + if self.caught_with_lasso + and self.lasso_origin then + local pos = self.object:get_pos() + pos.y = pos.y + (self:get_height() * 0.5) + animalia.set_lasso_visual(self, self.lasso_origin) + if type(self.lasso_origin) == "userdata" + or type(self.lasso_origin) == "string" then + if type(self.lasso_origin) == "string" then + self.lasso_origin = minetest.get_player_by_name(self.lasso_origin) + if not self.lasso_origin then + self.caught_with_lasso = nil + self.lasso_origin = nil + self:forget("lasso_origin") + if self.lasso_visual then + self.lasso_visual:remove() + self.lasso_visual = nil + end + return + end + end + self:memorize("lasso_origin", self.lasso_origin:get_player_name()) + -- Get distance to lasso player + local player = self.lasso_origin + local lasso_origin = player:get_pos() + lasso_origin.y = lasso_origin.y + 1 + local dist = vector.distance(pos, lasso_origin) + if player:get_wielded_item():get_name() ~= "animalia:lasso" + or vector.distance(pos, lasso_origin) > 16 then + self.caught_with_lasso = nil + self.lasso_origin = nil + self:forget("lasso_origin") + if self.lasso_visual then + self.lasso_visual:remove() + self.lasso_visual = nil + end + end + -- Apply physics + if dist > 6 + or abs(lasso_origin.y - pos.y) > 8 then + local p_target = vector.add(pos, vector.multiply(vector.direction(pos, lasso_origin), dist * 0.8)) + local g = -0.18 + local v = vector.new(0, 0, 0) + v.x = (1.0 + (0.005 * dist)) * (p_target.x - pos.x) / dist + v.y = -((1.0 + (0.03 * dist)) * ((lasso_origin.y - 4) - pos.y) / (dist * (g * dist))) + v.z = (1.0 + (0.005 * dist)) * (p_target.z - pos.z) / dist + self.object:add_velocity(v) + end + elseif type(self.lasso_origin) == "table" then + self:memorize("lasso_origin", self.lasso_origin) + local lasso_origin = self.lasso_origin + local dist = vector.distance(pos, lasso_origin) + if dist > 6 + or abs(lasso_origin.y - pos.y) > 8 then + local p_target = vector.add(pos, vector.multiply(vector.direction(pos, lasso_origin), dist * 0.8)) + local g = -0.18 + local v = vector.new(0, 0, 0) + v.x = (1.0 + (0.005 * dist)) * (p_target.x - pos.x) / dist + v.y = -((1.0 + (0.03 * dist)) * ((lasso_origin.y - 4) - pos.y) / (dist * (g * dist))) + v.z = (1.0 + (0.005 * dist)) * (p_target.z - pos.z) / dist + self.object:add_velocity(v) + end + local objects = minetest.get_objects_inside_radius(lasso_origin, 1) + local is_lasso_attached = false + for _, object in ipairs(objects) do + if object + and object:get_luaentity() + and object:get_luaentity().name == "animalia:lasso_fence_ent" then + is_lasso_attached = true + end + end + if not is_lasso_attached then + self.caught_with_lasso = nil + self.lasso_origin = nil + self:forget("lasso_origin") + if self.lasso_visual then + self.lasso_visual:remove() + self.lasso_visual = nil + end + end + else + local objects = minetest.get_objects_inside_radius(self.lasso_origin, 0.4) + for _, object in ipairs(objects) do + if object + and object:get_luaentity() + and object:get_luaentity().name == "animalia:lasso_fence_ent" then + minetest.add_item(object:get_pos(), "animalia:lasso") + object:remove() + end + end + self.caught_with_lasso = nil + self.lasso_origin = nil + self:forget("lasso_origin") + if self.lasso_visual then + self.lasso_visual:remove() + self.lasso_visual = nil + end + end + end +end + +local function is_lasso_in_use(player) + for _, ent in pairs(minetest.luaentities) do + if ent.name + and ent.name:match("^animalia:") then + if ent.lasso_origin + and type(ent.lasso_origin) == "userdata" + and ent.lasso_origin == player then + return true + end + end + end + return false +end + +local function update_lasso_rotation(self) + if not self.parent + or not self.lasso_origin then self.object:remove() return end + local lasso_origin = self.lasso_origin + if type(lasso_origin) == "userdata" then + lasso_origin = lasso_origin:get_pos() + lasso_origin.y = lasso_origin.y + 1 + end + local object = self.parent + if not object then return end + local pos = object:get_pos() + pos.y = pos.y + object:get_luaentity():get_height() + local rot = vector.dir_to_rotation(vector.direction(lasso_origin, pos)) + self.object:set_pos(lasso_origin) + self.object:set_rotation(rot) + self.object:set_properties({ + visual_size = {x = 6, z = 10 * vector.distance(pos, lasso_origin), y = 6} + }) +end + +minetest.register_entity("animalia:lasso_visual", { + hp_max = 1, + physical = false, + collisionbox = {0, 0, 0, 0, 0, 0}, + visual = "mesh", + mesh = "animalia_lasso.b3d", + visual_size = {x = 2, y = 2}, + textures = {"animalia_lasso_cube.png"}, + is_visible = true, + makes_footstep_sound = false, + glow = 1, + on_step = function(self, dtime) + self.object:set_armor_groups({immortal = 1}) + if not self.parent + or not self.lasso_origin + or (self.parent + and (not creatura.is_alive(self.parent) + or not self.parent:get_luaentity().caught_with_lasso)) then + self.object:remove() + return + end + update_lasso_rotation(self) + end +}) + +minetest.register_entity("animalia:frog_tongue_visual", { + hp_max = 1, + physical = false, + collisionbox = {0, 0, 0, 0, 0, 0}, + visual = "mesh", + mesh = "animalia_lasso.b3d", + visual_size = {x = 2, y = 2}, + textures = {"animalia_frog_tongue.png"}, + is_visible = true, + makes_footstep_sound = false, + on_step = function(self, dtime) + self.object:set_armor_groups({immortal = 1}) + if not self.parent + or not self.lasso_origin + or (self.parent + and (not creatura.is_alive(self.parent) + or not self.parent:get_luaentity().caught_with_lasso)) then + self.object:remove() + return + end + update_lasso_rotation(self) + end +}) + +minetest.register_entity("animalia:lasso_fence_ent", { + physical = false, + collisionbox = {-0.25,-0.25,-0.25, 0.25,0.25,0.25}, + visual = "cube", + visual_size = {x = 0.3, y = 0.3}, + mesh = "model", + textures = { + "animalia_lasso_cube.png", + "animalia_lasso_cube.png", + "animalia_lasso_cube.png", + "animalia_lasso_cube.png", + "animalia_lasso_cube.png", + "animalia_lasso_cube.png", + }, + makes_footstep_sound = false, + on_step = function(self) + if not self.parent + or not self.parent:get_luaentity() + or not self.parent:get_luaentity().lasso_origin then + self.object:remove() + return + end + local pos = self.object:get_pos() + local node = minetest.get_node(pos) + if not minetest.registered_nodes[node.name].walkable + or minetest.get_item_group(node.name, "fence") < 1 then + local ent = self.parent:get_luaentity() + ent.lasso_origin = ent:memorize("lasso_origin", nil) + ent.caught_with_lasso = nil + if ent.lasso_visual then + ent.lasso_visual:remove() + ent.lasso_visual = nil + end + minetest.add_item(self.object:get_pos(), "animalia:lasso") + self.object:remove() + return + end + end, + on_rightclick = function(self) + if self.parent then + local ent = self.parent:get_luaentity() + ent.lasso_origin = ent:memorize("lasso_origin", nil) + ent.caught_with_lasso = nil + if ent.lasso_visual then + ent.lasso_visual:remove() + ent.lasso_visual = nil + end + end + local dirs = { + vector.new(1, 0, 0), + vector.new(-1, 0, 0), + vector.new(0, 1, 0), + vector.new(0, -1, 0), + vector.new(0, 0, 1), + vector.new(0, 0, -1), + } + for i = 1, 6 do + local pos = vector.add(self.object:get_pos(), dirs[i]) + local name = minetest.get_node(pos).name + if not minetest.registered_nodes[name].walkable then + minetest.add_item(pos, "animalia:lasso") + break + end + end + self.object:remove() + end, + on_punch = function(self) + if self.parent then + local ent = self.parent:get_luaentity() + ent.lasso_origin = ent:memorize("lasso_origin", nil) + ent.caught_with_lasso = nil + if ent.lasso_visual then + ent.lasso_visual:remove() + ent.lasso_visual = nil + end + end + local dirs = { + vector.new(1, 0, 0), + vector.new(-1, 0, 0), + vector.new(0, 1, 0), + vector.new(0, -1, 0), + vector.new(0, 0, 1), + vector.new(0, 0, -1), + } + for i = 1, 6 do + local pos = vector.add(self.object:get_pos(), dirs[i]) + local name = minetest.get_node(pos).name + if not minetest.registered_nodes[name].walkable then + minetest.add_item(pos, "animalia:lasso") + break + end + end + self.object:remove() + end +}) + +minetest.register_craftitem("animalia:lasso", { + description = "Lasso", + inventory_image = "animalia_lasso.png", + on_secondary_use = function(itemstack, placer, pointed_thing) + if pointed_thing.type == "object" then + if pointed_thing.ref:is_player() then return end + local ent = pointed_thing.ref:get_luaentity() + if not ent.caught_with_lasso + and not is_lasso_in_use(placer) then + ent.caught_with_lasso = true + ent.lasso_origin = placer + elseif ent.lasso_origin + and ent.lasso_origin == placer then + ent.caught_with_lasso = nil + ent.lasso_origin = nil + end + end + end, + on_place = function(itemstack, placer, pointed_thing) + if pointed_thing.type == "node" then + local pos = minetest.get_pointed_thing_position(pointed_thing) + if minetest.get_item_group(minetest.get_node(pos).name, "fence") > 0 then + local objects = minetest.get_objects_inside_radius(placer:get_pos(), 21) + for _, obj in ipairs(objects) do + if obj:get_luaentity() + and obj:get_luaentity().lasso_origin + and obj:get_luaentity().lasso_visual + and type(obj:get_luaentity().lasso_origin) == "userdata" + and obj:get_luaentity().lasso_origin == placer then + obj:get_luaentity().lasso_visual:get_luaentity().lasso_origin = pos + obj:get_luaentity().lasso_origin = pos + local object = minetest.add_entity(pos, "animalia:lasso_fence_ent") + object:get_luaentity().parent = obj + itemstack:take_item(1) + break + end + end + end + end + return itemstack + end +}) \ No newline at end of file diff --git a/api/spawning.lua b/api/spawning.lua new file mode 100644 index 0000000..ad5c539 --- /dev/null +++ b/api/spawning.lua @@ -0,0 +1,316 @@ +-------------- +-- Spawning -- +-------------- + +local random = math.random + +local path = minetest.get_modpath("animalia") + +local storage = dofile(path .. "/api/storage.lua") + +animalia.spawn_points = storage.spawn_points + +-- Get Biomes -- + +local chicken_biomes = {} + +local frog_biomes = {} + +local pig_biomes = {} + +local wolf_biomes = {} + +local function insert_all(tbl, tbl2) + for i = 1, #tbl2 do + table.insert(tbl, tbl2[i]) + end +end + +minetest.register_on_mods_loaded(function() + insert_all(chicken_biomes, animalia.registered_biome_groups["grassland"].biomes) + insert_all(chicken_biomes, animalia.registered_biome_groups["tropical"].biomes) + insert_all(pig_biomes, animalia.registered_biome_groups["temperate"].biomes) + insert_all(pig_biomes, animalia.registered_biome_groups["boreal"].biomes) + insert_all(frog_biomes, animalia.registered_biome_groups["swamp"].biomes) + insert_all(frog_biomes, animalia.registered_biome_groups["tropical"].biomes) +end) + +creatura.register_mob_spawn("animalia:bat", { + chance = 2, + min_radius = 4, + max_radius = 16, + min_light = 0, + min_height = -512, + max_height = 0, + min_group = 3, + max_group = 5, + biomes = animalia.registered_biome_groups["cave"].biomes, + spawn_in_nodes = true, + nodes = {"air", "ignore"} +}) + +creatura.register_mob_spawn("animalia:chicken", { + chance = 3, + min_group = 3, + max_group = 5, + biomes = chicken_biomes +}) + +creatura.register_mob_spawn("animalia:cow", { + chance = 3, + min_group = 3, + max_group = 4, + biomes = animalia.registered_biome_groups["grassland"].biomes +}) + +creatura.register_mob_spawn("animalia:frog", { + chance = 2, + min_radius = 4, + max_radius = 16, + min_light = 0, + min_height = -32, + max_height = 8, + min_group = 2, + max_group = 6, + biomes = frog_biomes, + spawn_cluster = true, + spawn_in_nodes = true, + nodes = {"default:water_source"}, + send_debug = true +}) + +creatura.register_mob_spawn("animalia:horse", { + chance = 3, + min_group = 4, + max_group = 5, + biomes = animalia.registered_biome_groups["grassland"].biomes +}) + +creatura.register_mob_spawn("animalia:pig", { + chance = 3, + min_group = 2, + max_group = 4, + biomes = pig_biomes +}) + +creatura.register_mob_spawn("animalia:reindeer", { + chance = 4, + min_group = 6, + max_group = 12, + biomes = animalia.registered_biome_groups["boreal"].biomes +}) + +creatura.register_mob_spawn("animalia:sheep", { + chance = 3, + min_group = 3, + max_group = 6, + biomes = animalia.registered_biome_groups["grassland"].biomes +}) + +creatura.register_mob_spawn("animalia:turkey", { + chance = 2, + min_group = 3, + max_group = 4, + biomes = animalia.registered_biome_groups["boreal"].biomes +}) + +creatura.register_mob_spawn("animalia:wolf", { + chance = 3, + min_group = 2, + max_group = 3, + biomes = animalia.registered_biome_groups["boreal"].biomes +}) + +creatura.register_mob_spawn("animalia:bird", { + chance = 4, + min_light = 0, + min_group = 12, + max_group = 16, + biomes = animalia.registered_biome_groups["common"].biomes, + spawn_cluster = true, + spawn_in_nodes = true, + nodes = {"air", "ignore"} +}) + +creatura.register_mob_spawn("animalia:tropical_fish", { + chance = 3, + min_height = -128, + max_height = 256, + min_group = 8, + max_group = 12, + spawn_cluster = true, + spawn_in_nodes = true, + nodes = {"default:water_source"} +}) + +--------------------- +-- Mapgen Spawning -- +--------------------- + +local function vec_raise(v, n) + return {x = v.x, y = v.y + n, z = v.z} +end + +function is_value_in_table(tbl, val) + for _, v in pairs(tbl) do + if v == val then + return true + end + end + return false +end + +function get_biome_name(pos) + if not pos then return end + return minetest.get_biome_name(minetest.get_biome_data(pos).biome) +end + +function get_ground_level(pos) + local node = minetest.get_node(pos) + local node_def = minetest.registered_nodes[node.name] + local height = 0 + while node_def.walkable + and height < 4 do + height = height + 1 + node = minetest.get_node(vec_raise(pos, height)) + node_def = minetest.registered_nodes[node.name] + end + return vec_raise(pos, height) +end + +local function dist_to_nearest_player(pos) + local dist + for _, player in pairs(minetest.get_connected_players()) do + local player_pos = player:get_pos() + if player_pos + and (not dist + or dist > vector.distance(pos, player_pos)) then + dist = vector.distance(pos, player_pos) + end + end + return dist or 100 +end + +local function get_spawnable_mobs(pos) + local biome = get_biome_name(pos) + if not biome then return end + local spawnable = {} + for k, v in pairs(creatura.registered_mob_spawns) do + if (not v.biomes + or is_value_in_table(v.biomes, biome)) + and k:match("^animalia:") + and not v.spawn_in_nodes then + table.insert(spawnable, k) + end + end + return spawnable +end + +local mapgen_spawning = minetest.settings:get_bool("animalia_mapgen_spawning") or true + +animalia.chunks_since_last_spawn = 0 + +local chunk_spawn_add_int = tonumber(minetest.settings:get("chunk_spawn_add_int")) or 6 + +animalia.spawn_queue = {} + +minetest.register_on_generated(function(minp, maxp) + if not mapgen_spawning then return end + animalia.chunks_since_last_spawn = animalia.chunks_since_last_spawn + 1 + local heightmap = minetest.get_mapgen_object("heightmap") + if not heightmap then return end + local pos = { + x = minp.x + math.floor((maxp.x - minp.x) / 2), + y = minp.y, + z = minp.z + math.floor((maxp.z - minp.z) / 2) + } + local hm_i = (pos.x - minp.x + 1) + (((pos.z - minp.z)) * 80) + pos.y = heightmap[hm_i] + if animalia.chunks_since_last_spawn > chunk_spawn_add_int + and pos.y > 0 then + local heightmap = minetest.get_mapgen_object("heightmap") + if not heightmap then return end + local center = { + x = math.floor(minp.x + ((maxp.x - minp.x) * 0.5) + 0.5), + y = minp.y, + z = math.floor(minp.z + ((maxp.z - minp.z) * 0.5) + 0.5), + } + local light = minetest.get_natural_light(center) + while center.y < maxp.y + and light < 10 do + center.y = center.y + 1 + light = minetest.get_natural_light(center) + end + local spawnable_mobs = get_spawnable_mobs(center) + if spawnable_mobs then + local mob = spawnable_mobs[random(#spawnable_mobs)] + table.insert(animalia.spawn_queue, {pos = center, mob = mob, group = random(3, 4)}) + table.insert(animalia.spawn_points, center) + end + animalia.chunks_since_last_spawn = 0 + end +end) + +local respawn_interval = 15 + +minetest.register_globalstep(function(dtime) + respawn_interval = respawn_interval - dtime + if respawn_interval <= 0 then + if #animalia.spawn_points > 0 then + for i = 1, #animalia.spawn_points do + local point = animalia.spawn_points[i] + if dist_to_nearest_player(point) < 48 + and minetest.get_node_or_nil(point) then + local spawnable_mobs = get_spawnable_mobs(point) + if spawnable_mobs then + local mob = spawnable_mobs[random(#spawnable_mobs)] + local objects = minetest.get_objects_inside_radius(point, 32) + local spawn = true + if #objects > 0 then + for i = 1, #objects do + local object = objects[i] + if object:get_luaentity() + and object:get_luaentity().name:find("animalia:") then + spawn = false + break + end + end + end + if spawn then + table.insert(animalia.spawn_queue, {pos = point, mob = mob, group = random(3, 4)}) + end + end + end + end + end + respawn_interval = 15 + end +end) + +local chunk_spawn_queue_int = tonumber(minetest.settings:get("chunk_spawn_queue_int")) or 16 + +local function spawn_queued() + if not mapgen_spawning then return end + local queue = animalia.spawn_queue + if #queue > 0 then + for i = #queue, 1, -1 do + if queue[i].mob then + local pos = queue[i].pos + for _ = 1, queue[i].group do + pos = { + x = pos.x + random(-3, 3), + y = pos.y, + z = pos.z + random(-3, 3) + } + pos = get_ground_level(pos) + minetest.add_node(pos, {name = "creatura:spawn_node"}) + local meta = minetest.get_meta(pos) + meta:set_string("mob", queue[i].mob) + end + end + table.remove(animalia.spawn_queue, i) + end + end + minetest.after(chunk_spawn_queue_int, spawn_queued) +end +minetest.after(chunk_spawn_queue_int, spawn_queued) \ No newline at end of file diff --git a/api/storage.lua b/api/storage.lua new file mode 100644 index 0000000..eb20542 --- /dev/null +++ b/api/storage.lua @@ -0,0 +1,20 @@ +local mod_storage = minetest.get_mod_storage() + +local data = { + spawn_points = minetest.deserialize(mod_storage:get_string("spawn_points")) or {}, +} + +local function save() + mod_storage:set_string("spawn_points", minetest.serialize(data.spawn_points)) +end + +minetest.register_on_shutdown(save) +minetest.register_on_leaveplayer(save) + +local function periodic_save() + save() + minetest.after(120, periodic_save) +end +minetest.after(120, periodic_save) + +return data \ No newline at end of file diff --git a/craftitems.lua b/craftitems.lua index 680b21a..6d0f143 100644 --- a/craftitems.lua +++ b/craftitems.lua @@ -1,11 +1,380 @@ ---------------- -- Craftitems -- ---------------- ----- Ver 0.1 --- ----------------- --- Animal Net -- Used to capture and store animals ----------------- +local random = math.random + +local function vec_raise(v, n) + return {x = v.x, y = v.y + n, z = v.z} +end + +local walkable_nodes = {} + +minetest.register_on_mods_loaded(function() + for name in pairs(minetest.registered_nodes) do + if name ~= "air" and name ~= "ignore" then + if minetest.registered_nodes[name].walkable then + table.insert(walkable_nodes, name) + end + end + end +end) + +local function correct_name(str) + if str then + if str:match(":") then str = str:split(":")[2] end + return (string.gsub(" " .. str, "%W%l", string.upper):sub(2):gsub("_", " ")) + end +end + +function register_egg(name, def) + + minetest.register_entity(def.mob .. "_egg_sprite", { + hp_max = 1, + physical = true, + collisionbox = {0, 0, 0, 0, 0, 0}, + visual = "sprite", + visual_size = {x = 0.5, y = 0.5}, + textures = {"animalia_egg.png"}, + initial_sprite_basepos = {x = 0, y = 0}, + is_visible = true, + on_step = function(self, dtime) + local pos = self.object:get_pos() + local objects = minetest.get_objects_inside_radius(pos, 1.5) + local cube = minetest.find_nodes_in_area( + vector.new(pos.x - 0.5, pos.y - 0.5, pos.z - 0.5), + vector.new(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5), + walkable_nodes) + if #objects >= 2 then + if objects[2]:get_armor_groups().fleshy then + objects[2]:punch(self.object, 2.0, {full_punch_interval = 0.1, damage_groups = {fleshy = 1}}, nil) + end + end + if #cube >= 1 then + minetest.add_particlespawner({ + amount = 6, + time = 0.25, + minpos = {x = pos.x - 7/16, y = pos.y - 5/16, z = pos.z - 7/16}, + maxpos = {x = pos.x + 7/16, y = pos.y - 5/16, z = pos.z + 7/16}, + minvel = vector.new(-1, 2, -1), + maxvel = vector.new(1, 5, 1), + minacc = vector.new(0, -9.81, 0), + maxacc = vector.new(0, -9.81, 0), + collisiondetection = true, + texture = "animalia_egg_fragment.png", + }) + if random(1, 3) < 2 then + local object = minetest.add_entity(pos, def.mob) + local ent = object:get_luaentity() + ent.growth_scale = 0.7 + animalia.initialize_api(ent) + animalia.protect_from_despawn(ent) + self.object:remove() + else + self.object:remove() + end + end + end + }) + + local function mobs_shoot_egg(item, player, pointed_thing) + local pos = player:get_pos() + + minetest.sound_play("default_place_node_hard", { + pos = pos, + gain = 1.0, + max_hear_distance = 5, + }) + + local vel = 19 + local gravity = 9 + + local obj = minetest.add_entity({ + x = pos.x, + y = pos.y +1.5, + z = pos.z + }, def.mob .. "_egg_sprite") + + local ent = obj:get_luaentity() + local dir = player:get_look_dir() + + ent.velocity = vel -- needed for api internal timing + ent.switch = 1 -- needed so that egg doesn't despawn straight away + + obj:set_velocity({ + x = dir.x * vel, + y = dir.y * vel, + z = dir.z * vel + }) + + obj:set_acceleration({ + x = dir.x * -3, + y = -gravity, + z = dir.z * -3 + }) + + -- pass player name to egg for chick ownership + local ent2 = obj:get_luaentity() + ent2.playername = player:get_player_name() + + item:take_item() + + return item + end + + minetest.register_craftitem(name, { + description = def.description, + inventory_image = def.inventory_image .. ".png", + on_use = mobs_shoot_egg, + groups = {food_egg = 1, flammable = 2}, + }) + + minetest.register_craftitem(name .. "_fried", { + description = "Fried " .. def.description, + inventory_image = def.inventory_image .. "_fried.png", + on_use = minetest.item_eat(4), + groups = {food_egg = 1, flammable = 2}, + }) + + minetest.register_craft({ + type = "cooking", + recipe = name, + output = name .. "_fried", + }) +end + +----------- +-- Drops -- +----------- + +minetest.register_craftitem("animalia:leather", { + description = "Leather", + inventory_image = "animalia_leather.png", + groups = {flammable = 2, leather = 1}, +}) + +minetest.register_craftitem("animalia:feather", { + description = "Feather", + inventory_image = "animalia_feather.png", + groups = {flammable = 2, feather = 1}, +}) + +-- Meat -- + +minetest.register_craftitem("animalia:beef_raw", { + description = "Raw Beef", + inventory_image = "animalia_beef_raw.png", + on_use = minetest.item_eat(1), + groups = {flammable = 2, meat = 1, food_meat = 1}, +}) + +minetest.register_craftitem("animalia:beef_cooked", { + description = "Steak", + inventory_image = "animalia_beef_cooked.png", + on_use = minetest.item_eat(8), + groups = {flammable = 2, meat = 1, food_meat = 1}, +}) + +minetest.register_craft({ + type = "cooking", + recipe = "animalia:beef_raw", + output = "animalia:beef_cooked", +}) + +minetest.register_craftitem("animalia:mutton_raw", { + description = "Raw Mutton", + inventory_image = "animalia_mutton_raw.png", + on_use = minetest.item_eat(1), + groups = {flammable = 2, meat = 1, food_meat = 1}, +}) + +minetest.register_craftitem("animalia:mutton_cooked", { + description = "Cooked Mutton", + inventory_image = "animalia_mutton_cooked.png", + on_use = minetest.item_eat(6), + groups = {flammable = 2, meat = 1, food_meat = 1}, +}) + +minetest.register_craft({ + type = "cooking", + recipe = "animalia:mutton_raw", + output = "animalia:mutton_cooked", +}) + +minetest.register_craftitem("animalia:porkchop_raw", { + description = "Raw Porkchop", + inventory_image = "animalia_porkchop_raw.png", + on_use = minetest.item_eat(1), + groups = {flammable = 2, meat = 1, food_meat = 1}, +}) + +minetest.register_craftitem("animalia:porkchop_cooked", { + description = "Cooked Porkchop", + inventory_image = "animalia_porkchop_cooked.png", + on_use = minetest.item_eat(7), + groups = {flammable = 2, meat = 1, food_meat = 1}, +}) + +minetest.register_craft({ + type = "cooking", + recipe = "animalia:porkchop_raw", + output = "animalia:porkchop_cooked", +}) + +minetest.register_craftitem("animalia:poultry_raw", { + description = "Raw Poultry", + inventory_image = "animalia_poultry_raw.png", + on_use = minetest.item_eat(1), + groups = {flammable = 2, meat = 1, food_meat = 1}, +}) + +minetest.register_craftitem("animalia:poultry_cooked", { + description = "Cooked Poultry", + inventory_image = "animalia_poultry_cooked.png", + on_use = minetest.item_eat(6), + groups = {flammable = 2, meat = 1, food_meat = 1}, +}) + +minetest.register_craft({ + type = "cooking", + recipe = "animalia:poultry_raw", + output = "animalia:poultry_cooked", +}) + +register_egg("animalia:chicken_egg", { + description = "Chicken Egg", + inventory_image = "animalia_egg", + mob = "animalia:chicken" +}) + +---------- +-- Misc -- +---------- + +minetest.register_craftitem("animalia:bucket_milk", { + description = "Bucket of Milk", + inventory_image = "animalia_milk_bucket.png", + stack_max = 1, + on_use = minetest.item_eat(8, "bucket:bucket_empty"), + groups = {food_milk = 1, flammable = 3}, +}) + +function grow_crops(pos, nodename) + local checkname = nodename:sub(1, string.len(nodename) - 1) + if minetest.registered_nodes[checkname .. "1"] + and minetest.registered_nodes[checkname .. "2"] + and minetest.registered_nodes[checkname .. "2"].drawtype == "plantlike" then -- node is more than likely a plant + local stage = tonumber(string.sub(nodename, -1)) or 0 + local newname = checkname .. (stage + 1) + if minetest.registered_nodes[newname] then + local def = minetest.registered_nodes[newname] + def = def and def.place_param2 or 0 + minetest.set_node(pos, {name = newname, param2 = def}) + minetest.add_particlespawner({ + amount = 6, + time = 0.1, + minpos = vector.subtract(pos, 0.5), + maxpos = vector.add(pos, 0.5), + minvel = { + x = -0.5, + y = 0.5, + z = -0.5 + }, + maxvel = { + x = 0.5, + y = 1, + z = 0.5 + }, + minacc = { + x = 0, + y = 2, + z = 0 + }, + maxacc = { + x = 0, + y = 4, + z = 0 + }, + minexptime = 0.5, + maxexptime = 1, + minsize = 1, + maxsize = 2, + collisiondetection = false, + vertical = false, + use_texture_alpha = true, + texture = "creatura_particle_green.png", + glow = 6 + }) + end + end +end + +local guano_fert = minetest.settings:get_bool("guano_fertilization") + +minetest.register_craftitem("animalia:bucket_guano", { + description = "Bucket of Guano", + inventory_image = "animalia_guano_bucket.png", + stack_max = 1, + groups = {flammable = 3}, + on_place = function(itemstack, placer, pointed_thing) + local pos = pointed_thing.above + if pos then + local under = minetest.get_node(pointed_thing.under) + local node = minetest.registered_nodes[under.name] + if node and node.on_rightclick then + return node.on_rightclick(pointed_thing.under, under, placer, + itemstack) + end + if pos + and not minetest.is_protected(pos, placer:get_player_name()) then + if guano_fert then + local nodes = minetest.find_nodes_in_area_under_air(vector.subtract(pos, 5), vector.add(pos, 5), {"group:grass", "group:plant", "group:flora"}) + if #nodes > 0 then + for n = 1, #nodes do + grow_crops(nodes[n], minetest.get_node(nodes[n]).name) + end + local replace = itemstack:get_meta():get_string("original_item") + if not replace + or replace == "" then + replace = "bucket:bucket_empty" + end + itemstack:set_name(replace) + end + else + minetest.set_node(pos, {name = "animalia:guano"}) + local replace = itemstack:get_meta():get_string("original_item") + if not replace + or replace == "" then + replace = "bucket:bucket_empty" + end + itemstack:set_name(replace) + end + end + end + return itemstack + end +}) + +----------- +-- Tools -- +----------- + +minetest.register_craftitem("animalia:cat_toy", { + description = "Cat Toy", + inventory_image = "animalia_cat_toy.png", + wield_image = "animalia_cat_toy.png^[transformFYR90", +}) + +minetest.register_craftitem("animalia:saddle", { + description = "Saddle", + inventory_image = "animalia_saddle.png", +}) + +minetest.register_tool("animalia:shears", { + description = "Shears", + inventory_image = "animalia_shears.png", + groups = {flammable = 2} +}) minetest.register_craftitem("animalia:net", { description = "Animal Net", @@ -18,8 +387,8 @@ minetest.register_craftitem("animalia:net", { if not ent.name:match("^animalia:") or not ent.catch_with_net then return end - local ent_name = mob_core.get_name_proper(ent.name) - local ent_gender = mob_core.get_name_proper(ent.gender) + local ent_name = correct_name(ent.name) + local ent_gender = correct_name(ent.gender) local meta = itemstack:get_meta() if not meta:get_string("mob") or meta:get_string("mob") == "" then if placer:get_wielded_item():get_count() > 1 then @@ -41,12 +410,13 @@ minetest.register_craftitem("animalia:net", { end meta:set_string("description", desc) placer:set_wielded_item(itemstack) + animalia.protect_from_despawn(ent) ent.object:remove() return itemstack else minetest.chat_send_player(placer:get_player_name(), "This Net already contains a " .. - mob_core.get_name_proper( + correct_name( meta:get_string("mob"))) return end @@ -80,267 +450,237 @@ minetest.register_craftitem("animalia:net", { end }) -minetest.register_craft({ - output = "animalia:net", - recipe = { - {"farming:string", "", "farming:string"}, - {"farming:string", "", "farming:string"}, - {"group:stick", "farming:string", ""} - } +----------- +-- Nodes -- +----------- + +minetest.register_node("animalia:guano", { + description = "Guano", + tiles = {"animalia_guano.png"}, + paramtype = "light", + buildable_to = true, + floodable = true, + walkable = false, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-0.5, -0.5, -0.5, 0.5, -0.25, 0.5}, + }, + }, + groups = {crumbly = 3, falling_node = 1}, + on_punch = function(pos, _, player) + local item = player:get_wielded_item() + local item_name = player:get_wielded_item():get_name() + if item_name:find("bucket") + and item_name:find("empty") then + local stack = ItemStack("animalia:bucket_guano") + stack:get_meta():set_string("original_item", item_name) + player:set_wielded_item(stack) + minetest.remove_node(pos) + end + end }) ----------- --- Lasso -- Used to pull animals around, and can be attached to fences +-- Libri -- ----------- -local function is_lasso_in_use(player) - for _, ent in pairs(minetest.luaentities) do - if ent.name - and ent.name:match("^animalia:") then - if ent.lasso_player - and ent.lasso_player == player then - return true - end - end - end - return false +animalia.libri_pages = {} + +function animalia.show_libri_main_form(player, pages, group) + group = group or 1 + local basic_form = table.concat({ + "formspec_version[3]", + "size[16,10]", + "background[-0.7,-0.5;17.5,11.5;animalia_libri_bg_v2.png]" + }, "") + if group == 1 then + if pages[1] then + basic_form = basic_form .. "button[1.75,1.5;4,1;".. pages[1].form .."]" + end + if pages[2] then + basic_form = basic_form .. "button[1.75,3.5;4,1;".. pages[2].form .."]" + end + if pages[3] then + basic_form = basic_form .. "button[1.75,5.5;4,1;".. pages[3].form .."]" + end + if pages[4] then + basic_form = basic_form .. "button[1.75,7.5;4,1;".. pages[4].form .."]" + end + if pages[5] then + basic_form = basic_form .. "button[10.25,1.5;4,1;".. pages[5].form .."]" + end + if pages[6] then + basic_form = basic_form .. "button[10.25,3.5;4,1;".. pages[6].form .."]" + end + if pages[7] then + basic_form = basic_form .. "button[10.25,5.5;4,1;".. pages[7].form .."]" + end + if pages[8] then + basic_form = basic_form .. "button[10.25,7.5;4,1;".. pages[8].form .."]" + end + if pages[9] then + basic_form = basic_form .. "button[12.25,9;1.5,1;btn_next;Next Page]" + end + elseif group == 2 then + if pages[9] then + basic_form = basic_form .. "button[1.75,1.5;4,1;".. pages[9].form .."]" + end + if pages[10] then + basic_form = basic_form .. "button[1.75,3.5;4,1;".. pages[10].form .."]" + end + if pages[11] then + basic_form = basic_form .. "button[1.75,5.5;4,1;".. pages[11].form .."]" + end + if pages[12] then + basic_form = basic_form .. "button[1.75,7.5;4,1;".. pages[12].form .."]" + end + if pages[13] then + basic_form = basic_form .. "button[10.25,1.5;4,1;".. pages[13].form .."]" + end + if pages[14] then + basic_form = basic_form .. "button[10.25,3.5;4,1;".. pages[14].form .."]" + end + if pages[15] then + basic_form = basic_form .. "button[10.25,5.5;4,1;".. pages[15].form .."]" + end + if pages[16] then + basic_form = basic_form .. "button[10.25,7.5;4,1;".. pages[16].form .."]" + end + end + animalia.libri_pages[player:get_player_name()] = pages + minetest.show_formspec(player:get_player_name(), "animalia:libri_main", basic_form) end - -minetest.register_entity("animalia:lasso_visual", { - hp_max = 1, - armor_groups = {immortal = 1}, - physical = false, - collisionbox = {0, 0, 0, 0, 0, 0}, - visual = "mesh", - mesh = "animalia_lasso.b3d", - visual_size = {x = 2, y = 2}, - textures = {"animalia_lasso_cube.png"}, - is_visible = true, - makes_footstep_sound = false, - glow = 1, - on_step = function(self, dtime) - self.object:set_armor_groups({immortal = 1}) - if not self.parent - or not self.anchor_pos - or (self.parent - and (not mobkit.is_alive(self.parent) - or not self.parent:get_luaentity().caught_with_lasso)) then - self.object:remove() - return - end - local pos = mobkit.get_stand_pos(self.parent) - pos.y = pos.y + (self.parent:get_luaentity().height * 0.5) - self.object:set_pos(self.anchor_pos) - local rot = vector.dir_to_rotation(vector.direction(self.anchor_pos, pos)) - self.object:set_rotation(rot) - self.object:set_properties({ - visual_size = {x = 6, z = 10 * vector.distance(self.anchor_pos, pos), y = 6} - }) - end +minetest.register_craftitem("animalia:libri_animalia", { + description = "Libri Animalia", + inventory_image = "animalia_libri_animalia.png", + stack_max = 1, + on_place = function(itemstack, player, pointed_thing) + if pointed_thing and pointed_thing.type == "object" then return end + local meta = itemstack:get_meta() + local pages = minetest.deserialize(meta:get_string("pages")) + local desc = meta:get_string("description") + if not pages + or #pages < 1 then return end + animalia.show_libri_main_form(player, pages) + end, + on_secondary_use = function(itemstack, player, pointed_thing) + if pointed_thing and pointed_thing.type == "object" then return end + local meta = itemstack:get_meta() + local pages = minetest.deserialize(meta:get_string("pages")) + local desc = meta:get_string("description") + if not pages + or #pages < 1 then return end + animalia.show_libri_main_form(player, pages) + end }) -minetest.register_entity("animalia:lasso_fence_ent", { - hp_max = 1, - physical = false, - collisionbox = {-0.25,-0.25,-0.25, 0.25,0.25,0.25}, - visual = "cube", - visual_size = {x = 0.3, y = 0.3}, - mesh = "model", - textures = { - "animalia_lasso_cube.png", - "animalia_lasso_cube.png", - "animalia_lasso_cube.png", - "animalia_lasso_cube.png", - "animalia_lasso_cube.png", - "animalia_lasso_cube.png", - }, - makes_footstep_sound = false, - on_step = function(self) - if not self.parent - or not self.parent:get_luaentity() - or not self.parent:get_luaentity().lasso_pos then - self.object:remove() - return - end - local pos = self.object:get_pos() - local node = minetest.get_node(pos) - if not minetest.registered_nodes[node.name].walkable - or minetest.get_item_group(node.name, "fence") < 1 then - local ent = self.parent:get_luaentity() - ent.lasso_pos = mobkit.remember(ent, "lasso_pos", nil) - ent.caught_with_lasso = nil - if ent.lasso_visual then - ent.lasso_visual:remove() - ent.lasso_visual = nil - end - minetest.add_item(self.object:get_pos(), "animalia:lasso") - self.object:remove() - return - end - end, - on_rightclick = function(self) - if self.parent then - local ent = self.parent:get_luaentity() - ent.lasso_pos = mobkit.remember(ent, "lasso_pos", nil) - ent.caught_with_lasso = nil - if ent.lasso_visual then - ent.lasso_visual:remove() - ent.lasso_visual = nil - end - end - local dirs = { - vector.new(1, 0, 0), - vector.new(-1, 0, 0), - vector.new(0, 1, 0), - vector.new(0, -1, 0), - vector.new(0, 0, 1), - vector.new(0, 0, -1), - } - for i = 1, 6 do - local pos = vector.add(self.object:get_pos(), dirs[i]) - local name = minetest.get_node(pos).name - if not minetest.registered_nodes[name].walkable then - minetest.add_item(pos, "animalia:lasso") - break - end - end - self.object:remove() - end, - on_punch = function(self) - if self.parent then - local ent = self.parent:get_luaentity() - ent.lasso_pos = mobkit.remember(ent, "lasso_pos", nil) - ent.caught_with_lasso = nil - if ent.lasso_visual then - ent.lasso_visual:remove() - ent.lasso_visual = nil - end - end - local dirs = { - vector.new(1, 0, 0), - vector.new(-1, 0, 0), - vector.new(0, 1, 0), - vector.new(0, -1, 0), - vector.new(0, 0, 1), - vector.new(0, 0, -1), - } - for i = 1, 6 do - local pos = vector.add(self.object:get_pos(), dirs[i]) - local name = minetest.get_node(pos).name - if not minetest.registered_nodes[name].walkable then - minetest.add_item(pos, "animalia:lasso") - break - end - end - self.object:remove() - end -}) +-------------- +-- Crafting -- +-------------- -minetest.register_craftitem("animalia:lasso", { - description = "Lasso", - inventory_image = "animalia_lasso.png", - on_secondary_use = function(itemstack, placer, pointed_thing) - if pointed_thing.type == "object" then - if pointed_thing.ref:is_player() then return end - local ent = pointed_thing.ref:get_luaentity() - if not ent.name:match("^animalia:") or not ent.catch_with_net then - return - end - if not ent.caught_with_lasso - and not is_lasso_in_use(placer) then - ent.caught_with_lasso = true - ent.lasso_player = placer - elseif ent.lasso_player - and ent.lasso_player == placer then - ent.caught_with_lasso = nil - ent.lasso_player = nil - end +minetest.register_on_mods_loaded(function() + for name, def in pairs(minetest.registered_items) do + if string.find(name, "ingot") + and string.find(name, "steel") then + if not def.groups then + def.groups = {} + end + def.groups["steel_ingot"] = 1 + minetest.register_item(":" .. name, def) + elseif string.find(name, "string") then + if not def.groups then + def.groups = {} + end + def.groups["string"] = 1 + minetest.register_item(":" .. name, def) end - end, - on_place = function(itemstack, placer, pointed_thing) - if pointed_thing.type == "node" then - local pos = minetest.get_pointed_thing_position(pointed_thing) - if minetest.get_item_group(minetest.get_node(pos).name, "fence") > 0 then - local objects = minetest.get_objects_inside_radius(placer:get_pos(), 21) - for _, obj in ipairs(objects) do - if obj:get_luaentity() - and obj:get_luaentity().lasso_player - and obj:get_luaentity().lasso_player == placer then - obj:get_luaentity().lasso_pos = pos - obj:get_luaentity().lasso_player = nil - local object = minetest.add_entity(pos, "animalia:lasso_fence_ent") - object:get_luaentity().parent = obj - itemstack:take_item(1) - break - end - end - end - end - return itemstack end +end) + +minetest.register_craft({ + output = "animalia:cat_toy", + recipe = { + {"", "", "group:string"}, + {"", "group:stick", "group:string"}, + {"group:stick", "", "group:feather"} + } }) minetest.register_craft({ output = "animalia:lasso", recipe = { - {"", "farming:string", "farming:string"}, - {"", "animalia:leather", "farming:string"}, - {"farming:string", "", ""} + {"", "group:string", "group:string"}, + {"", "group:leather", "group:string"}, + {"group:string", "", ""} } }) -------------- --- Cat Toy -- Used to quickly increase trust with Cats -------------- - -minetest.register_craftitem("animalia:cat_toy", { - description = "Cat Toy", - inventory_image = "animalia_cat_toy.png", - wield_image = "animalia_cat_toy.png^[transformFYR90", -}) - minetest.register_craft({ - output = "animalia:cat_toy", + output = "animalia:net", recipe = { - {"", "", "farming:string"}, - {"", "group:stick", "farming:string"}, - {"group:stick", "", "group:feather"} + {"group:string", "", "group:string"}, + {"group:string", "", "group:string"}, + {"group:stick", "group:string", ""} } }) ------------- --- Saddle -- Can be attached to a tamed Horse to make it ridable ------------- - -minetest.register_craftitem("animalia:saddle", { - description = "Saddle", - inventory_image = "animalia_saddle.png", -}) minetest.register_craft({ output = "animalia:saddle", recipe = { - {"animalia:leather", "animalia:leather", "animalia:leather"}, - {"animalia:leather", "default:steel_ingot", "animalia:leather"}, - {"farming:string", "", "farming:string"} + {"group:leather", "group:leather", "group:leather"}, + {"group:leather", "group:steel_ingot", "group:leather"}, + {"group:string", "", "group:string"} } }) ------------- --- Shears -- Used to shear Sheep ------------- - -minetest.register_tool("animalia:shears", { - description = "Shears", - inventory_image = "animalia_shears.png", - groups = {flammable = 2} -}) - minetest.register_craft({ output = "animalia:shears", recipe = { - {"", "default:steel_ingot", ""}, - {"", "animalia:leather", "default:steel_ingot"} + {"", "group:steel_ingot", ""}, + {"", "group:leather", "group:steel_ingot"} } -}) \ No newline at end of file +}) + +minetest.register_craft({ + output = "animalia:libri_animalia", + recipe = { + {"", "", ""}, + {"animalia:feather", "", ""}, + {"group:book", "group:color_green", ""} + } +}) + +minetest.register_craft({ + output = "animalia:libri_animalia", + recipe = { + {"", "", ""}, + {"animalia:feather", "", ""}, + {"group:book", "group:unicolor_green", ""} + } +}) + +minetest.register_craft({ + output = "animalia:libri_animalia 2", + recipe = { + {"", "", ""}, + {"animalia:libri_animalia", "group:book", ""}, + {"", "", ""} + } +}) + +minetest.register_on_craft(function(itemstack, player, old_craft_grid) + if itemstack:get_name() == "animalia:libri_animalia" + and itemstack:get_count() > 1 then + for _, old_libri in pairs(old_craft_grid) do + if old_libri:get_meta():get_string("pages") then + local pages = old_libri:get_meta():get_string("pages") + itemstack:get_meta():set_string("pages", pages) + return itemstack + end + end + end +end) \ No newline at end of file diff --git a/init.lua b/init.lua index 34a884a..bfabd5e 100644 --- a/init.lua +++ b/init.lua @@ -1,150 +1,83 @@ animalia = {} better_fauna = animalia -animalia.mobkit_mobs = {} -animalia.walkable_nodes = {} -animalia.spawn_interval = tonumber(minetest.settings:get("animalia_spawn_int")) or 60 -animalia.mobs = {} +animalia.pets = {} -minetest.register_on_mods_loaded(function() - for name in pairs(minetest.registered_entities) do - local mob = minetest.registered_entities[name] - if mob.logic or mob.brainfunc then - table.insert(animalia.mobkit_mobs, name) - end - end - for name in pairs(minetest.registered_nodes) do - if name ~= "air" and name ~= "ignore" then - if minetest.registered_nodes[name].walkable then - table.insert(animalia.walkable_nodes, name) - end - end - end +minetest.register_on_joinplayer(function(player) + local name = player:get_player_name() + animalia.pets[name] = {} end) -animalia.frame_blend = 0 - -if minetest.has_feature("object_step_has_moveresult") then - animalia.frame_blend = 0.3 -end - -local fancy_step = minetest.settings:get_bool("animalia_fancy_step") - -local stepheight = 1.1 - -if fancy_step then - stepheight = 0.1 -end - -function animalia.register_mob(name, def) - minetest.register_entity("animalia:".. name, { - physical = true, - collide_with_objects = true, - visual = "mesh", - makes_footstep_sound = true, - static_save = true, - timeout = 0, - -- Stats - max_hp = def.health or 20, - armor_groups = {fleshy = def.fleshy}, - view_range = def.view_range or 32, - lung_capacity = def.lung_capacity or 10, - -- Visual - collisionbox = def.collisionbox, - visual_size = def.visual_size, - mesh = def.mesh, - textures = def.textures or nil, - scale_stage1 = def.scale_stage1 or 0.75, - scale_stage2 = def.scale_stage2 or 0.85, - scale_stage3 = def.scale_stage3 or 0.95, - female_textures = def.female_textures or nil, - male_textures = def.male_textures or nil, - child_textures = def.child_textures or nil, - animation = def.animations, - -- Physics - ignore_liquidflag = false, - push_on_collide = true, - buoyancy = def.buoyancy or 0.25, - max_speed = def.speed, - jump_height = def.jump_height or 1.1, - stepheight = stepheight, - max_fall = def.max_fall or 2, - -- Attributes - sounds = def.sounds, - obstacle_avoidance_range = def.obstacle_avoidance_range or nil, - surface_avoidance_range = def.surface_avoidance_range or nil, - floor_avoidance_range = def.floor_avoidance_range or nil, - fall_damage = def.fall_damage or true, - igniter_damage = def.igniter_damage or 2, - reach = def.reach or 2, - damage = def.damage or 2, - knockback = def.knockback or 4, - punch_cooldown = def.punch_cooldown or 1, - core_growth = def.growth or true, - catch_with_net = def.catch_with_net or true, - driver_scale = def.driver_scale or nil, - player_rotation = def.player_rotation or nil, - driver_attach_at = def.driver_attach_at or nil, - driver_attach_bone = def.driver_attach_bone or nil, - driver_eye_offset = def.driver_eye_offset or nil, - -- Behavior - defend_owner = def.defend_owner, - follow = def.follow, - consumable_nodes = def.consumable_nodes or nil, - drops = def.drops, - -- Functions - head_data = def.head_data or nil, - register_targets = def.register_targets or nil, - physics = def.physics or nil, - logic = def.logic, - get_staticdata = mobkit.statfunc, - on_step = def.on_step, - on_activate = def.on_activate, - on_rightclick = def.on_rightclick, - on_punch = def.on_punch, - }) - table.insert(animalia.mobs, "animalia:" .. name) -end +minetest.register_on_leaveplayer(function(player) + local name = player:get_player_name() + animalia.pets[name] = nil +end) local path = minetest.get_modpath("animalia") -local spawn_mobs = minetest.settings:get_bool("spawn_mobs") or true dofile(path.."/api/api.lua") -if spawn_mobs then - dofile(path.."/api/spawn.lua") -end +dofile(path.."/api/behaviors.lua") +dofile(path.."/api/lasso.lua") dofile(path.."/craftitems.lua") -dofile(path.."/mobs/cat.lua") -dofile(path.."/mobs/chicken.lua") -dofile(path.."/mobs/cow.lua") -dofile(path.."/mobs/horse.lua") -dofile(path.."/mobs/pig.lua") -dofile(path.."/mobs/reindeer.lua") -dofile(path.."/mobs/sheep.lua") -dofile(path.."/mobs/turkey.lua") -dofile(path.."/mobs/wolf.lua") -dofile(path.."/api/legacy_convert.lua") -local convert_redo_items = minetest.settings:get_bool("convert_redo_items") or false +animalia.animals = { + "animalia:bat", + "animalia:bird", + "animalia:cat", + "animalia:chicken", + "animalia:cow", + "animalia:tropical_fish", + "animalia:frog", + "animalia:horse", + "animalia:pig", + "animalia:reindeer", + "animalia:sheep", + "animalia:turkey", + "animalia:wolf", +} -if convert_redo_items then - minetest.register_alias_force("mobs:lasso","animalia:lasso") - minetest.register_alias_force("mobs:saddle","animalia:saddle") - minetest.register_alias_force("mobs:shears","animalia:shears") - minetest.register_alias_force("mobs_animal:chicken_raw","animalia:poultry_raw") - minetest.register_alias_force("mobs_animal:chicken_feather","animalia:feather") - minetest.register_alias_force("mobs:meat_raw" ,"animalia:beef_raw") - minetest.register_alias_force("mobs:meat","animalia:beef_cooked") - minetest.register_alias_force("mobs_animal:mutton_raw","animalia:mutton_raw") - minetest.register_alias_force("mobs_animal:mutton_cooked","animalia:mutton_cooked") - minetest.register_alias_force("mobs:leather" ,"animalia:leather") - minetest.register_alias_force("mobs_animal:egg","animalia:chicken_egg") - minetest.register_alias_force("mobs_animal:chicken_egg_fried" ,"animalia:chicken_egg_fried") - minetest.register_alias_force("mobs_animal:milk_bucket","animalia:bucket_milk") - minetest.register_alias_force("mobs_animal:chicken_cooked" ,"animalia:poultry_cooked") - minetest.register_alias_force("mobs_animal:pork_raw" ,"animalia:porkchop_raw") - minetest.register_alias_force("mobs_animal:pork_cooked","animalia:porkchop_cooked") +for i = 1, #animalia.animals do + local name = string.split(animalia.animals[i], ":")[2] + dofile(path.."/mobs/" .. name .. ".lua") end -minetest.log("action", "[MOD] Animalia [0.2] loaded") +if minetest.settings:get_bool("spawn_mobs", true) then + dofile(path.."/api/spawning.lua") +end + +minetest.register_on_mods_loaded(function() + for name, def in pairs(minetest.registered_entities) do + if def.logic + or def.brainfunc + or def.bh_tree + or def._cmi_is_mob then + local old_punch = def.on_punch + if not old_punch then + old_punch = function() end + end + local on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir) + old_punch(self, puncher, time_from_last_punch, tool_capabilities, dir) + local pos = self.object:get_pos() + if not pos then + return + end + if puncher:is_player() + and animalia.pets[puncher:get_player_name()] then + local pets = animalia.pets[puncher:get_player_name()] + if #pets < 1 then return end + for i = 1, #pets do + local ent = pets[i]:get_luaentity() + if ent.assist_owner then + ent.owner_target = self + end + end + end + end + def.on_punch = on_punch + minetest.register_entity(":" .. name, def) + end + end +end) + +minetest.log("action", "[MOD] Animalia [0.3] loaded") diff --git a/mobs/bat.lua b/mobs/bat.lua new file mode 100644 index 0000000..47b5916 --- /dev/null +++ b/mobs/bat.lua @@ -0,0 +1,274 @@ +--------- +-- Bat -- +--------- + + +local function get_ceiling_positions(pos, range) + local walkable = minetest.find_nodes_in_area( + {x = pos.x + range, y = pos.y + range, z = pos.z + range}, + {x = pos.x - range, y = pos.y, z = pos.z - range}, + animalia.walkable_nodes + ) + if #walkable < 1 then return {} end + local output = {} + for i = 1, #walkable do + local i_pos = walkable[i] + local under = { + x = i_pos.x, + y = i_pos.y - 1, + z = i_pos.z + } + if minetest.get_node(under).name == "air" + and minetest.registered_nodes[minetest.get_node(i_pos).name].walkable then + table.insert(output, i_pos) + end + end + return output +end + +local guano_accumulation = minetest.settings:get_bool("guano_accumulation") + +-- Math -- + +local function clamp(val, min, max) + if val < min then + val = min + elseif max < val then + val = max + end + return val +end + +local random = math.random +local floor = math.floor + +-- Vector Math -- + +local vec_dist = vector.distance +local vec_add = vector.add + +local function vec_raise(v, n) + return {x = v.x, y = v.y + n, z = v.z} +end + +local function is_node_walkable(name) + local def = minetest.registered_nodes[name] + return def and def.walkable +end + +creatura.register_mob("animalia:bat", { + -- Stats + max_health = 5, + armor_groups = {fleshy = 200}, + damage = 0, + speed = 4, + tracking_range = 16, + despawn_after = 2500, + -- Entity Physics + stepheight = 1.1, + max_fall = 100, + turn_rate = 12, + -- Visuals + mesh = "animalia_bat.b3d", + hitbox = { + width = 0.15, + height = 0.3 + }, + visual_size = {x = 7, y = 7}, + textures = { + "animalia_bat_1.png", + "animalia_bat_2.png", + "animalia_bat_3.png" + }, + animations = { + stand = {range = {x = 1, y = 40}, speed = 10, frame_blend = 0.3, loop = true}, + walk = {range = {x = 50, y = 90}, speed = 30, frame_blend = 0.3, loop = true}, + fly = {range = {x = 100, y = 140}, speed = 80, frame_blend = 0.3, loop = true}, + latch = {range = {x = 150, y = 150}, speed = 1, frame_blend = 0, loop = false} + }, + -- Misc + sounds = { + random = { + name = "animalia_bat", + gain = 0.5, + distance = 16, + variations = 2 + }, + }, + catch_with_net = true, + follow = { + "butterflies:butterfly_red", + "butterflies:butterfly_white", + "butterflies:butterfly_violet" + }, + -- Function + utility_stack = { + [1] = { + utility = "animalia:wander", + get_score = function(self) + if self.is_landed then + return 0.1, {self} + end + return 0 + end + }, + [2] = { + utility = "animalia:aerial_swarm", + get_score = function(self) + if self:get_utility() == "animalia:return_to_home" + or self:get_utility() == "animalia:wander" then + local pos = self.object:get_pos() + local player = creatura.get_nearby_player(self) + if player + and not player:get_player_control().sneak then + local dist = vector.distance(pos, player:get_pos()) + self._nearby_player = player + self.is_landed = false + return (12 - dist) * 0.1, {self, 1} + end + end + if self.in_liquid + or not self.is_landed then + return 0.11, {self, 1} + end + return 0 + end + }, + [3] = { + utility = "animalia:land", + get_score = function(self) + if not self.is_landed + and not self.touching_ground then + return 0.12, {self} + end + return 0 + end + }, + [4] = { + utility = "animalia:return_to_home", + get_score = function(self) + if not self.home_position then return 0 end + local player = self._nearby_player + if player then + local pos = self.object:get_pos() + local dist = vector.distance(pos, player:get_pos()) + if dist < 9 then + return 0 + end + end + local time = (minetest.get_timeofday() * 24000) or 0 + local is_day = time < 19500 and time > 4500 + if is_day then + return 0.6, {self} + end + return 0 + end + }, + [5] = { + utility = "animalia:find_home", + get_score = function(self) + if self.home_position then return 0 end + local pos = self.object:get_pos() + local range = self.tracking_range + local ceiling = get_ceiling_positions(pos, range / 2) + if not ceiling[1] then return 0 end + return 1, {self} + end + } + }, + activate_func = function(self) + animalia.initialize_api(self) + animalia.initialize_lasso(self) + self.trust = self:recall("trust") or {} + self.home_position = self:recall("home_position") or nil + self.is_landed = self:recall("is_landed") or false + self.stamina = self:recall("stamina") or 30 + end, + step_func = function(self) + animalia.step_timers(self) + --animalia.head_tracking(self, 0.75, 0.75) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + if self.stamina > 0 then + if not self.is_landed then + self.stamina = self:memorize("stamina", self.stamina - self.dtime) + else + self.stamina = self:memorize("stamina", self.stamina + self.dtime) + end + if self.stamina > 25 + and self.is_landed then + self.is_landed = self:memorize("is_landed", false) + end + else + self.stamina = self:memorize("stamina", self.stamina + self.dtime) + self.is_landed = self:memorize("is_landed", true) + end + if self._anim == "fly" then + local vel_y = self.object:get_velocity().y + local rot = self.object:get_rotation() + self.object:set_rotation({ + x = clamp(vel_y * 0.25, -0.75, 0.75), + y = rot.y, + z = rot.z + }) + end + if self:timer(random(3,4)) then + self:play_sound("random") + if guano_accumulation + and random(16) < 2 + and self:get_utility() == "animalia:return_to_home" then + local pos = self.object:get_pos() + pos = { + x = floor(pos.x + 0.5), + y = floor(pos.y + 0.5), + z = floor(pos.z + 0.5) + } + if not is_node_walkable(minetest.get_node(vec_raise(pos, 1)).name) then + return + end + local fail_safe = 1 + while not is_node_walkable(minetest.get_node(floor_pos).name) + and fail_safe < 16 do + pos.y = pos.y - 1 + end + if is_node_walkable(minetest.get_node(pos).name) then + if minetest.get_node(vec_raise(pos, 1)).name ~= "animalia:guano" then + minetest.set_node(vec_raise(pos, 1), {name = "animalia:guano"}) + else + local nodes = minetest.find_nodes_in_area_under_air( + vector.subtract(pos, 3), + vec_add(pos, 3), + animalia.walkable_nodes + ) + if #nodes > 0 then + pos = nodes[random(#nodes)] + if minetest.get_node(vec_raise(pos, 1)).name ~= "animalia:guano" then + minetest.set_node(vec_raise(pos, 1), {name = "animalia:guano"}) + end + end + end + end + end + end + end, + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) + end + end, + on_rightclick = function(self, clicker) + if animalia.feed(self, clicker, false, false) then + self.trust[clicker:get_player_name()] = 1 + self:memorize("trust", self.trust) + return + end + animalia.add_libri_page(self, clicker, {name = "bat", form = "pg_bat;Bats"}) + end, + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + self.trust[puncher:get_player_name()] = 0 + self:memorize("trust", self.trust) + end +}) + +creatura.register_spawn_egg("animalia:bat", "392517", "321b0b") \ No newline at end of file diff --git a/mobs/bird.lua b/mobs/bird.lua new file mode 100644 index 0000000..9eea12e --- /dev/null +++ b/mobs/bird.lua @@ -0,0 +1,182 @@ +--------------- +-- Song Bird -- +--------------- + +local random = math.random + +local function clamp(val, min, max) + if val < min then + val = min + elseif max < val then + val = max + end + return val +end + +creatura.register_mob("animalia:bird", { + -- Stats + max_health = 5, + armor_groups = {fleshy = 200}, + damage = 0, + speed = 4, + tracking_range = 16, + despawn_after = 100, + -- Entity Physics + stepheight = 1.1, + max_fall = 100, + turn_rate = 6, + boid_seperation = 1, + -- Visuals + mesh = "animalia_bird.b3d", + hitbox = { + width = 0.15, + height = 0.3 + }, + visual_size = {x = 7, y = 7}, + textures = { + "animalia_bird_cardinal.png", + "animalia_bird_eastern_blue.png", + "animalia_bird_goldfinch.png" + }, + animations = { + stand = {range = {x = 1, y = 40}, speed = 10, frame_blend = 0.3, loop = true}, + walk = {range = {x = 50, y = 70}, speed = 30, frame_blend = 0.3, loop = true}, + fly = {range = {x = 120, y = 140}, speed = 80, frame_blend = 0.3, loop = true} + }, + -- Misc + catch_with_net = true, + sounds = { + cardinal = { + name = "animalia_cardinal", + gain = 0.5, + distance = 63, + variations = 3 + }, + eastern_blue = { + name = "animalia_eastern_blue", + gain = 0.5, + distance = 63, + variations = 3 + }, + goldfinch = { + name = "animalia_goldfinch", + gain = 0.5, + distance = 63, + variations = 3 + }, + }, + follow = follows, + -- Function + utility_stack = { + { + utility = "animalia:boid_wander", + get_score = function(self) + return 0.1, {self, true} + end + }, + { + utility = "animalia:aerial_flock", + get_score = function(self) + if not self.is_landed then + return 0.11, {self, 1} + else + local pos = self.object:get_pos() + if self.in_liquid then + self.stamina = self:memorize("stamina", 30) + self.is_landed = false + return 0.15, {self, 1} + end + local player = creatura.get_nearby_player(self) + if player then + local dist = vector.distance(pos, player:get_pos()) + self.is_landed = false + return (16 - dist) * 0.1, {self, 1} + end + end + return 0 + end + }, + { + utility = "animalia:land", + get_score = function(self) + if not self.is_landed + and not self.touching_ground + and not self.in_liquid then + return 0.12, {self} + end + return 0 + end + } + }, + activate_func = function(self) + animalia.initialize_api(self) + animalia.initialize_lasso(self) + self.trust = self:recall("trust") or {} + self.is_landed = self:recall("is_landed") or true + self.stamina = self:recall("stamina") or 0.1 + self._path = {} + end, + step_func = function(self) + animalia.step_timers(self) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + if self:timer(random(10,15)) then + if self.texture_no == 1 then + self:play_sound("cardinal") + elseif self.texture_no == 2 then + self:play_sound("eastern_blue") + else + self:play_sound("goldfinch") + end + end + if self._anim == "fly" then + local vel_y = self.object:get_velocity().y + local rot = self.object:get_rotation() + self.object:set_rotation({ + x = clamp(vel_y * 0.25, -0.75, 0.75), + y = rot.y, + z = rot.z + }) + end + if self.stamina > 0 then + if not self.is_landed then + self.stamina = self:memorize("stamina", self.stamina - self.dtime) + else + self.stamina = self:memorize("stamina", self.stamina + self.dtime) + end + if self.stamina > 25 + and self.is_landed then + self.is_landed = self:memorize("is_landed", false) + end + else + self.stamina = self:memorize("stamina", self.stamina + self.dtime) + self.is_landed = self:memorize("is_landed", true) + end + if not self.is_landed + or not self.touching_ground then + self.speed = 4 + else + self.speed = 1 + end + end, + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) + end + end, + on_rightclick = function(self, clicker) + if animalia.feed(self, clicker, false, false) then + self.trust[clicker:get_player_name()] = 1 + self:memorize("trust", self.trust) + return + end + animalia.add_libri_page(self, clicker, {name = "bird", form = "pg_bird;Birds"}) + end, + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + self.trust[puncher:get_player_name()] = 0 + self:memorize("trust", self.trust) + end +}) + +creatura.register_spawn_egg("animalia:bird", "ae2f2f", "f3ac1c") \ No newline at end of file diff --git a/mobs/cat.lua b/mobs/cat.lua index 6f3200e..21cf017 100644 --- a/mobs/cat.lua +++ b/mobs/cat.lua @@ -2,10 +2,6 @@ -- Cat -- --------- -local clamp_bone_rot = animalia.clamp_bone_rot - -local interp = animalia.interp - local follow = { "animalia:poultry_raw" } @@ -17,146 +13,23 @@ if minetest.registered_items["ethereal:fish_raw"] then } end -local function cat_logic(self) - - if self.hp <= 0 then - mob_core.on_die(self) - return - end - - if self._anim == "run" then - local pos = self.object:get_pos() - minetest.add_particlespawner({ - amount = 1, - time = 0.25, - minpos = pos, - maxpos = pos, - minvel = vector.new(-1, 1, -1), - maxvel = vector.new(1, 2, 1), - minacc = vector.new(0, -9.81, 0), - maxacc = vector.new(0, -9.81, 0), - minsize = 0.25, - maxsize = 0.5, - collisiondetection = true, - texture = "default_dirt.png", - }) - end - - animalia.head_tracking(self, 0.25, 0.25) - - if mobkit.timer(self, 1) then - - local prty = mobkit.get_queue_priority(self) - local player = mobkit.get_nearby_player(self) - local trust = 0 - - mob_core.random_sound(self, 64) - mob_core.growth(self) - - if player then - if not self.trust[player:get_player_name()] then - self.trust[player:get_player_name()] = 0 - mobkit.remember(self, "trust", self.trust) - else - trust = self.trust[player:get_player_name()] - end - end - - if self.trust_cooldown > 0 then - self.trust_cooldown = mobkit.remember(self, "trust_cooldown", self.trust_cooldown - 1) - end - - if self.interact_sound_cooldown > 0 then - self.interact_sound_cooldown = self.interact_sound_cooldown - 1 - end - - if self.owner - and self.trust[self.owner] > 7 then - if prty < 22 - and self.order == "sit" then - if not mobkit.is_queue_empty_high(self) then - mobkit.clear_queue_high(self) - end - mobkit.animate(self, "sit") - return - end - - if prty < 21 - and self.owner_target then - if not mob_core.shared_owner(self, self.owner_target) then - animalia.hq_attack(self, 21, self.owner_target) - end - end - - if prty < 20 - and self.order == "follow" - and minetest.get_player_by_name(self.owner) then - local owner = minetest.get_player_by_name(self.owner) - animalia.hq_follow_player(self, 20, owner, true) - end - - if prty < 4 - and self.breeding then - animalia.hq_breed(self, 3) - end - end - - if prty < 5 - and self.isinliquid then - animalia.hq_go_to_land(self, 5) - end - - if prty < 3 - and player then - if player:get_velocity() - and vector.length(player:get_velocity()) < 2 then - if mob_core.follow_holding(self, player) - and trust >= 4 then - animalia.hq_follow_player(self, 3, player) - end - elseif player:get_wielded_item():get_name() == "animalia:cat_toy" then - animalia.hq_follow_player(self, 3, player, true) - return - end - end - - if player - and prty == 3 - and not mob_core.follow_holding(self, player) - and player:get_wielded_item():get_name() ~= "animalia:cat_toy" then - mobkit.clear_queue_high(self) - end - - if prty < 2 - and player - and trust > 4 then - local r = math.random(48) - if r < 2 then - animalia.hq_walk_in_front_of_player(self, 2, player) - elseif r < 3 then - animalia.hq_find_and_break_glass(self, 2) - end - end - - if mobkit.is_queue_empty_high(self) then - animalia.hq_wander_ranged(self, 0) - end - end -end - -animalia.register_mob("cat", { +creatura.register_mob("animalia:cat", { -- Stats - health = 10, - fleshy = 100, - view_range = 32, - lung_capacity = 10, - -- Visual - collisionbox = {-0.2, 0, -0.2, 0.2, 0.4, 0.2}, + max_health = 10, + armor_groups = {fleshy = 200}, + damage = 1, + speed = 5, + tracking_range = 24, + despawn_after = 2000, + -- Entity Physics + stepheight = 1.1, + -- Visuals + mesh = "animalia_cat.b3d", + hitbox = { + width = 0.2, + height = 0.4 + }, visual_size = {x = 6, y = 6}, - scale_stage1 = 0.5, - scale_stage2 = 0.65, - scale_stage3 = 0.80, - mesh = "animalia_cat.b3d", textures = { "animalia_cat_1.png", "animalia_cat_2.png", @@ -170,12 +43,9 @@ animalia.register_mob("cat", { sit = {range = {x = 140, y = 180}, speed = 10, frame_blend = 0.3, loop = true}, smack = {range = {x = 190, y = 210}, speed = 40, frame_blend = 0.1, loop = true}, }, - -- Physics - speed = 8, - max_fall = 4, - -- Attributes - sounds = { - alter_child_pitch = true, + -- Misc + catch_with_net = true, + sounds = { random = { name = "animalia_cat_idle", gain = 0.25, @@ -197,97 +67,244 @@ animalia.register_mob("cat", { distance = 8 } }, - reach = 2, - damage = 3, - knockback = 2, - punch_cooldown = 1, - -- Behavior - defend_owner = true, - follow = follow, - -- Functions + follow = follow, head_data = { - offset = {x = 0, y = 0.17, z = 0}, + offset = {x = 0, y = 0.22, z = 0}, pitch_correction = -20, - pivot_h = 0.35, - pivot_v = 0.2 + pivot_h = 0.65, + pivot_v = 0.65 }, - logic = cat_logic, - get_staticdata = mobkit.statfunc, - on_step = animalia.on_step, - on_activate = function(self, staticdata, dtime_s) - animalia.on_activate(self, staticdata, dtime_s) - self.trust = mobkit.recall(self, "trust") or {} - self.trust_cooldown = mobkit.recall(self, "trust_cooldown") or 0 + -- Function + activate_func = function(self) + animalia.initialize_api(self) + animalia.initialize_lasso(self) + self._path = {} self.interact_sound_cooldown = 0 - end, - on_rightclick = function(self, clicker) - local item = clicker:get_wielded_item():get_name() - if item == "animalia:net" then return end - if not self.trust[clicker:get_player_name()] then - self.trust[clicker:get_player_name()] = 0 - mobkit.remember(self, "trust", self.trust) + self.trust_cooldown = self:recall("trust_cooldown") or 0 + self.order = self:recall("order") or "wander" + self.owner = self:recall("owner") or nil + self.trust = self:recall("trust") or {} + if self.owner + and minetest.get_player_by_name(self.owner) then + if not animalia.pets[self.owner][self.object] then + table.insert(animalia.pets[self.owner], self.object) + end end - local trust = self.trust[clicker:get_player_name()] - local pos = self.object:get_pos() - local prt_pos = vector.new(pos.x, pos.y + 0.5, pos.z) - local minppos = vector.add(prt_pos, 1) - local maxppos = vector.subtract(prt_pos, 1) - if animalia.feed_tame(self, clicker, math.random(3, 5), trust >= 10, trust >= 10) then + end, + utility_stack = { + [1] = { + utility = "animalia:wander", + get_score = function(self) + return 0.1, {self, true} + end + }, + [2] = { + utility = "animalia:swim_to_land", + get_score = function(self) + if self.in_liquid then + return 0.9, {self} + end + return 0 + end + }, + [3] = { + utility = "animalia:find_and_break_glass_vessels", + get_score = function(self) + return math.random(10) * 0.01, {self} + end + }, + [4] = { + utility = "animalia:walk_ahead_of_player", + get_score = function(self) + local player = creatura.get_nearby_player(self) + if player then + local trust = 0 + if not self.trust[player:get_player_name()] then + self.trust[player:get_player_name()] = 0 + self:memorize("trust", self.trust) + else + trust = self.trust[player:get_player_name()] + end + self._nearby_player = player + if trust > 3 then + return math.random(10) * 0.01, {self, player} + else + return 0 + end + end + return 0 + end + }, + [5] = { + utility = "animalia:flee_from_player", + get_score = function(self) + local player = self._nearby_player + if player then + local trust = self.trust[player:get_player_name()] or 0 + if trust < 1 then + if self.owner + and minetest.get_player_by_name(self.owner) then + local pos = self.object:get_pos() + local owner = minetest.get_player_by_name(self.owner) + local owner_pos = owner:get_pos() + if owner ~= player + and vector.distance(pos, owner_pos) < vector.distance(pos, player:get_pos()) then + return 0 + end + end + return 0.5, {self, player} + end + end + return 0 + end + }, + [6] = { + utility = "animalia:sit", + get_score = function(self) + if self.order == "sit" + and self.trust[self.owner] > 7 then + return 0.8, {self} + end + return 0 + end + }, + [7] = { + utility = "animalia:follow_player", + get_score = function(self) + if self.order == "follow" + and minetest.get_player_by_name(self.owner) + and self.trust[self.owner] > 7 then + return 1, {self, minetest.get_player_by_name(self.owner)} + end + local trust = 0 + local player = self._nearby_player + if player then + if not self.trust[player:get_player_name()] then + self.trust[player:get_player_name()] = 0 + self:memorize("trust", self.trust) + else + trust = self.trust[player:get_player_name()] + end + else + return 0 + end + if player:get_velocity() + and vector.length(player:get_velocity()) < 2 + and self:follow_wielded_item(player) + and trust >= 4 then + return 0.6, {self, player} + elseif player:get_wielded_item():get_name() == "animalia:cat_toy" then + return 0.6, {self, player, true} + end + return 0 + end + }, + [8] = { + utility = "animalia:mammal_breed", + get_score = function(self) + if self.breeding then + return 0.7, {self} + end + return 0 + end + } + }, + step_func = function(self) + animalia.step_timers(self) + animalia.head_tracking(self, 0.75, 0.75) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + if self:timer(1) then + if self.trust_cooldown > 0 then + self.trust_cooldown = self:memorize("trust_cooldown", self.trust_cooldown - 1) + end + if self.interact_sound_cooldown > 0 then + self.interact_sound_cooldown = self.interact_sound_cooldown - 1 + end + end + end, + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) + end + end, + on_rightclick = function(self, clicker) + local item_name = clicker:get_wielded_item():get_name() + if item_name == "animalia:net" then return end + local trust = self.trust[clicker:get_player_name()] or 0 + local pos = self:get_center_pos() + local minppos = vector.add(pos, 1) + local maxppos = vector.subtract(pos, 1) + if animalia.feed(self, clicker, true, true) then if self.trust_cooldown <= 0 and trust < 10 then self.trust[clicker:get_player_name()] = trust + 1 - self.trust_cooldown = mobkit.remember(self, "trust_cooldown", 60) - mobkit.remember(self, "trust", self.trust) - animalia.particle_spawner(prt_pos, "mob_core_green_particle.png", "float", minppos, maxppos) + self.trust_cooldown = self:memorize("trust_cooldown", 60) + self:memorize("trust", self.trust) + animalia.particle_spawner(pos, "creatura_particle_green.png", "float", minppos, maxppos) end return end - mob_core.protect(self, clicker, true) - mob_core.nametag(self, clicker, true) - if mobkit.get_queue_priority(self) == 3 - and clicker:get_wielded_item():get_name() == "animalia:cat_toy" then + -- Initiate trust + if not self.trust[clicker:get_player_name()] then + self.trust[clicker:get_player_name()] = 0 + self:memorize("trust", self.trust) + end + -- Increase trust by playing + if item_name == "animalia:cat_toy" + and self:get_utility() == "animalia:follow_player" then if trust < 10 then self.trust[clicker:get_player_name()] = trust + 1 - mobkit.remember(self, "trust", self.trust) - animalia.particle_spawner(prt_pos, "mob_core_green_particle.png", "float", minppos, maxppos) + self:memorize("trust", self.trust) + animalia.particle_spawner(pos, "creatura_particle_green.png", "float", minppos, maxppos) if self.interact_sound_cooldown <= 0 then self.sounds["purr"].gain = 1 self.interact_sound_cooldown = 3 - mobkit.make_sound(self, "purr") + self:play_sound("purr") end end end - + -- Purr to indicate trust level (louder = more trust) + if clicker:get_player_control().sneak then + if self.interact_sound_cooldown <= 0 then + self.sounds["purr"].gain = 0.15 * trust + self.interact_sound_cooldown = 3 + self:play_sound("purr") + end + end + animalia.add_libri_page(self, clicker, {name = "cat", form = "pg_cat;Cats"}) if not self.owner or clicker:get_player_name() ~= self.owner then return end + if trust <= 7 then + if self.interact_sound_cooldown <= 0 then + self.interact_sound_cooldown = 3 + self:play_sound("random") + end + return + end if clicker:get_player_control().sneak then if self.interact_sound_cooldown <= 0 then self.sounds["purr"].gain = 0.15 * self.trust[self.owner] self.interact_sound_cooldown = 3 - mobkit.make_sound(self, "purr") + self:play_sound("purr") end - end - if trust <= 7 then - if self.interact_sound_cooldown <= 0 then - self.interact_sound_cooldown = 3 - mobkit.make_sound(self, "random") + local order = self.order + if order == "wander" then + self.order = "follow" + elseif order == "follow" then + self.order = "sit" + else + self.order = "wander" end - return + self:memorize("order", self.order) end - if self.order == "wander" then - self.order = "follow" - elseif self.order == "follow" then - self.order = "sit" - else - self.order = "wander" - end - mobkit.remember(self, "order", self.order) end, - on_punch = function(self, puncher, _, tool_capabilities, dir) - mob_core.on_punch_basic(self, puncher, tool_capabilities, dir) - animalia.hq_sporadic_flee(self, 10) + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + self:initiate_utility("animalia:flee_from_player", self, puncher) + self:set_utility_score(1) if not self.trust[puncher:get_player_name()] then self.trust[puncher:get_player_name()] = 0 else @@ -295,12 +312,18 @@ animalia.register_mob("cat", { self.trust[puncher:get_player_name()] = trust - 1 end local pos = self.object:get_pos() - local prt_pos = vector.new(pos.x, pos.y + 0.5, pos.z) - local minppos = vector.add(prt_pos, 1) - local maxppos = vector.subtract(prt_pos, 1) - animalia.particle_spawner(prt_pos, "mob_core_red_particle.png", "float", minppos, maxppos) - mobkit.remember(self, "trust", self.trust) + pos = vector.new(pos.x, pos.y + 0.5, pos.z) + local minppos = vector.add(pos, 1) + local maxppos = vector.subtract(pos, 1) + animalia.particle_spawner(pos, "creatura_particle_red.png", "float", minppos, maxppos) + self:memorize("trust", self.trust) + end, + deactivate_func = function(self) + if self.owner + and animalia.pets[self.owner][self.object] then + animalia.pets[self.owner][self.object] = nil + end end }) -mob_core.register_spawn_egg("animalia:cat", "db9764" ,"cf8d5a") +creatura.register_spawn_egg("animalia:cat", "db9764" ,"cf8d5a") \ No newline at end of file diff --git a/mobs/chicken.lua b/mobs/chicken.lua index 45685ed..1781ba4 100644 --- a/mobs/chicken.lua +++ b/mobs/chicken.lua @@ -2,91 +2,36 @@ -- Chicken -- ------------- -local clamp_bone_rot = animalia.clamp_bone_rot +local follows = {} -local interp = animalia.interp - -local random = math.random - -local function chicken_logic(self) - if self.hp <= 0 then - mob_core.on_die(self) - return - end - - if self.status ~= "following" then - if self.attention_span > 1 then - self.attention_span = self.attention_span - self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - else - self.attention_span = self.attention_span + self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - - if self.fall_start - and self.fall_start - mobkit.get_stand_pos(self).y > 2 then - mobkit.animate(self, "flap") - self.object:set_acceleration({x = 0, y = -3.1, z = 0}) - end - - animalia.head_tracking(self, 0.45, 0.25) - - if mobkit.timer(self, 4) then - - local prty = mobkit.get_queue_priority(self) - local player = mobkit.get_nearby_player(self) - - mob_core.random_sound(self, 14) - mob_core.random_drop(self, 10, 1800, "animalia:chicken_egg") - - if prty < 4 - and self.isinliquid then - animalia.hq_go_to_land(self, 4) - end - - if prty < 3 - and self.breeding then - animalia.hq_fowl_breed(self, 3) - end - - if prty == 2 - and not self.lasso_player - and (not player - or not mob_core.follow_holding(self, player)) then - mobkit.clear_queue_high(self) - end - - if prty < 2 then - if self.caught_with_lasso - and self.lasso_player then - animalia.hq_follow_player(self, 2, self.lasso_player, true) - elseif player then - if self.attention_span < 5 then - if mob_core.follow_holding(self, player) then - animalia.hq_follow_player(self, 2, player) - self.attention_span = self.attention_span + 1 - end - end - end +minetest.register_on_mods_loaded(function() + for name, def in pairs(minetest.registered_items) do + if name:match(":seed_") + or name:match("_seed") then + table.insert(follows, name) end + end +end) - if mobkit.is_queue_empty_high(self) then - animalia.hq_wander_group(self, 0, 8) - end - end -end - -animalia.register_mob("chicken", { - -- Stats - health = 10, - fleshy = 100, - view_range = 8, - lung_capacity = 10, - -- Visual - collisionbox = {-0.2, -0.15, -0.2, 0.2, 0.3, 0.2}, - visual_size = {x = 6, y = 6}, - mesh = "animalia_chicken.b3d", +creatura.register_mob("animalia:chicken", { + -- Stats + max_health = 5, + armor_groups = {fleshy = 150}, + damage = 0, + speed = 4, + tracking_range = 16, + despawn_after = 1500, + -- Entity Physics + stepheight = 1.1, + max_fall = 8, + turn_rate = 7, + -- Visuals + mesh = "animalia_chicken.b3d", + hitbox = { + width = 0.15, + height = 0.3 + }, + visual_size = {x = 7, y = 7}, female_textures = { "animalia_chicken_1.png", "animalia_chicken_2.png", @@ -102,14 +47,11 @@ animalia.register_mob("chicken", { stand = {range = {x = 0, y = 0}, speed = 1, frame_blend = 0.3, loop = true}, walk = {range = {x = 10, y = 30}, speed = 30, frame_blend = 0.3, loop = true}, run = {range = {x = 10, y = 30}, speed = 45, frame_blend = 0.3, loop = true}, - fall = {range = {x = 40, y = 60}, speed = 30, frame_blend = 0.3, loop = true}, + fall = {range = {x = 40, y = 60}, speed = 70, frame_blend = 0.3, loop = true}, }, - -- Physics - speed = 5, - max_fall = 6, - -- Attributes + -- Misc + catch_with_net = true, sounds = { - alter_child_pitch = true, random = { name = "animalia_chicken_idle", gain = 0.5, @@ -126,172 +68,96 @@ animalia.register_mob("chicken", { distance = 8 } }, - fall_damage = false, - -- Behavior - defend_owner = false, - follow = { - "farming:seed_cotton", - "farming:seed_wheat" - }, - drops = { - {name = "animalia:feather", chance = 1, min = 1, max = 2}, - {name = "animalia:poultry_raw", chance = 1, min = 1, max = 4} - }, - -- Functions + drops = { + {name = "animalia:poultry_raw", min = 1, max = 3, chance = 1}, + {name = "animalia:feather", min = 1, max = 3, chance = 2} + }, + follow = follows, head_data = { offset = {x = 0, y = 0.15, z = 0}, pitch_correction = 55, pivot_h = 0.25, pivot_v = 0.55 }, - logic = chicken_logic, - get_staticdata = mobkit.statfunc, - on_step = animalia.on_step, - on_activate = animalia.on_activate, - on_rightclick = function(self, clicker) - if animalia.feed_tame(self, clicker, 1, false, true) then return end - mob_core.protect(self, clicker, true) - mob_core.nametag(self, clicker, true) - end, - on_punch = function(self, puncher, _, tool_capabilities, dir) - mob_core.on_punch_basic(self, puncher, tool_capabilities, dir) - animalia.hq_sporadic_flee(self, 10) - end, -}) - -mob_core.register_spawn_egg("animalia:chicken", "c6c6c6", "d22222") - -minetest.register_craftitem("animalia:poultry_raw", { - description = "Raw Poultry", - inventory_image = "animalia_poultry_raw.png", - on_use = minetest.item_eat(1), - groups = {flammable = 2, meat = 1, food_meat = 1}, -}) - -minetest.register_craftitem("animalia:poultry_cooked", { - description = "Cooked Poultry", - inventory_image = "animalia_poultry_cooked.png", - on_use = minetest.item_eat(6), - groups = {flammable = 2, meat = 1, food_meat = 1}, -}) - -minetest.register_craft({ - type = "cooking", - recipe = "animalia:poultry_raw", - output = "animalia:poultry_cooked", -}) - -minetest.register_entity("animalia:chicken_egg_sprite", { - hp_max = 1, - physical = true, - collisionbox = {0, 0, 0, 0, 0, 0}, - visual = "sprite", - visual_size = {x = 0.5, y = 0.5}, - textures = {"animalia_egg.png"}, - initial_sprite_basepos = {x = 0, y = 0}, - is_visible = true, - on_step = function(self, dtime) - local pos = self.object:get_pos() - local objects = minetest.get_objects_inside_radius(pos, 1.5) - local cube = minetest.find_nodes_in_area( - vector.new(pos.x - 0.5, pos.y - 0.5, pos.z - 0.5), - vector.new(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5), - animalia.walkable_nodes) - if #objects >= 2 then - if objects[2]:get_armor_groups().fleshy then - objects[2]:punch(self.object, 2.0, {full_punch_interval = 0.1, damage_groups = {fleshy = 1}}, nil) + -- Function + utility_stack = { + [1] = { + utility = "animalia:wander", + get_score = function(self) + return 0.1, {self, true} end - end - if #cube >= 1 then - minetest.add_particlespawner({ - amount = 6, - time = 0.25, - minpos = {x = pos.x - 7/16, y = pos.y - 5/16, z = pos.z - 7/16}, - maxpos = {x = pos.x + 7/16, y = pos.y - 5/16, z = pos.z + 7/16}, - minvel = vector.new(-1, 2, -1), - maxvel = vector.new(1, 5, 1), - minacc = vector.new(0, -9.81, 0), - maxacc = vector.new(0, -9.81, 0), - collisiondetection = true, - texture = "animalia_egg_fragment.png", - }) - if random(1, 3) < 2 then - mob_core.spawn_child(pos, "animalia:chicken") - self.object:remove() - else - self.object:remove() + }, + [2] = { + utility = "animalia:resist_fall", + get_score = function(self) + if not self.touching_ground then + return 0.11, {self} + end + return 0 end + }, + [3] = { + utility = "animalia:swim_to_land", + get_score = function(self) + if self.in_liquid then + return 1, {self} + end + return 0 + end + }, + [4] = { + utility = "animalia:follow_player", + get_score = function(self) + if self.lasso_origin + and type(self.lasso_origin) == "userdata" then + return 0.8, {self, self.lasso_origin, true} + end + local player = creatura.get_nearby_player(self) + if player + and self:follow_wielded_item(player) then + return 0.8, {self, player} + end + return 0 + end + }, + [5] = { + utility = "animalia:bird_breed", + get_score = function(self) + if self.breeding then + return 0.9, {self} + end + return 0 + end + } + }, + activate_func = function(self) + animalia.initialize_api(self) + animalia.initialize_lasso(self) + self.attention_span = 8 + self._path = {} + end, + step_func = function(self) + animalia.step_timers(self) + animalia.head_tracking(self, 0.75, 0.75) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + end, + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) end + end, + on_rightclick = function(self, clicker) + if animalia.feed(self, clicker, false, true) then + return + end + animalia.add_libri_page(self, clicker, {name = "chicken", form = "pg_chicken;Chickens"}) + end, + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + self:initiate_utility("animalia:flee_from_player", self, puncher) + self:set_utility_score(1) end }) -local mobs_shoot_egg = function (item, player, pointed_thing) - local pos = player:get_pos() - - minetest.sound_play("default_place_node_hard", { - pos = pos, - gain = 1.0, - max_hear_distance = 5, - }) - - local vel = 19 - local gravity = 9 - - local obj = minetest.add_entity({ - x = pos.x, - y = pos.y +1.5, - z = pos.z - }, "animalia:chicken_egg_sprite") - - local ent = obj:get_luaentity() - local dir = player:get_look_dir() - - ent.velocity = vel -- needed for api internal timing - ent.switch = 1 -- needed so that egg doesn't despawn straight away - - obj:set_velocity({ - x = dir.x * vel, - y = dir.y * vel, - z = dir.z * vel - }) - - obj:set_acceleration({ - x = dir.x * -3, - y = -gravity, - z = dir.z * -3 - }) - - -- pass player name to egg for chick ownership - local ent2 = obj:get_luaentity() - ent2.playername = player:get_player_name() - - item:take_item() - - return item -end - -minetest.register_craftitem("animalia:chicken_egg", { - description = "Chicken Egg", - inventory_image = "animalia_egg.png", - on_use = mobs_shoot_egg, - groups = {food_egg = 1, flammable = 2}, -}) - -minetest.register_craftitem("animalia:chicken_egg_fried", { - description = "Fried Chicken Egg", - inventory_image = "animalia_egg_fried.png", - on_use = minetest.item_eat(4), - groups = {food_egg = 1, flammable = 2}, -}) - -minetest.register_craft({ - type = "cooking", - recipe = "animalia:chicken_egg", - output = "animalia:chicken_egg_fried", -}) - -minetest.register_craftitem("animalia:feather", { - description = "Feather", - inventory_image = "animalia_feather.png", - groups = {flammable = 2, feather = 1}, -}) \ No newline at end of file +creatura.register_spawn_egg("animalia:chicken", "c6c6c6", "d22222") \ No newline at end of file diff --git a/mobs/cow.lua b/mobs/cow.lua index 4b0dc0b..0df247a 100644 --- a/mobs/cow.lua +++ b/mobs/cow.lua @@ -2,97 +2,36 @@ -- Cow -- --------- -local clamp_bone_rot = animalia.clamp_bone_rot +local follows = {} -local interp = animalia.interp - -local random = math.random -local blend = animalia.frame_blend - -local function cow_logic(self) - - if self.hp <= 0 then - mob_core.on_die(self) - return - end - - if self.status ~= "following" then - if self.attention_span > 1 then - self.attention_span = self.attention_span - self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - else - self.attention_span = self.attention_span + self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - - animalia.head_tracking(self, 0.75, 0.75) - - if mobkit.timer(self, 3) then - - local prty = mobkit.get_queue_priority(self) - local player = mobkit.get_nearby_player(self) - - mob_core.random_sound(self, 14) - mob_core.growth(self) - - if random(1, 64) < 2 then - self.gotten = mobkit.remember(self, "gotten", false) - end - - if prty < 5 - and self.isinliquid then - animalia.hq_go_to_land(self, 5) - end - - if prty < 4 - and self.breeding then - animalia.hq_breed(self, 4) - end - - if prty < 3 - and self.gotten - and random(1, 16) < 2 then - animalia.hq_eat(self, 3) - end - - if prty == 2 - and not self.lasso_player - and (not player - or not mob_core.follow_holding(self, player)) then - mobkit.clear_queue_high(self) - end - - if prty < 2 then - if self.caught_with_lasso - and self.lasso_player then - animalia.hq_follow_player(self, 2, self.lasso_player, true) - elseif player then - if self.attention_span < 5 then - if mob_core.follow_holding(self, player) then - animalia.hq_follow_player(self, 2, player) - self.attention_span = self.attention_span + 1 - end - end - end +minetest.register_on_mods_loaded(function() + for name, def in pairs(minetest.registered_items) do + if (name:match(":wheat") + or minetest.get_item_group(name, "food_wheat") > 0) + and not name:find("seed") then + table.insert(follows, name) end + end +end) - if mobkit.is_queue_empty_high(self) then - animalia.hq_wander_group(self, 0, 10) - end - end -end - -animalia.register_mob("cow", { +creatura.register_mob("animalia:cow", { -- Stats - health = 20, - fleshy = 100, - view_range = 32, - lung_capacity = 10, - -- Visual - collisionbox = {-0.45, 0, -0.45, 0.45, 0.9, 0.45}, - visual_size = {x = 10, y = 10}, - mesh = "animalia_cow.b3d", + max_health = 20, + armor_groups = {fleshy = 150}, + damage = 0, + speed = 3, + tracking_range = 16, + despawn_after = 1500, + -- Entity Physics + stepheight = 1.1, + turn_rate = 6, + -- Visuals + mesh = "animalia_cow.b3d", + hitbox = { + width = 0.45, + height = 0.9 + }, + visual_size = {x = 10, y = 10}, female_textures = { "animalia_cow_1.png^animalia_cow_udder.png", "animalia_cow_2.png^animalia_cow_udder.png", @@ -114,35 +53,33 @@ animalia.register_mob("cow", { animations = { stand = {range = {x = 1, y = 60}, speed = 10, frame_blend = 0.3, loop = true}, walk = {range = {x = 70, y = 110}, speed = 40, frame_blend = 0.3, loop = true}, - run = {range = {x = 70, y = 110}, speed = 50, frame_blend = 0.3, loop = true}, + run = {range = {x = 70, y = 110}, speed = 60, frame_blend = 0.3, loop = true}, }, - -- Physics - speed = 4, - max_fall = 3, - -- Attributes - sounds = { - alter_child_pitch = true, + -- Misc + catch_with_net = true, + sounds = { random = { - name = "animalia_cow_idle", - gain = 1.0, - distance = 8 + name = "animalia_cow_random", + gain = 0.4, + distance = 8, + variations = 3 }, hurt = { name = "animalia_cow_hurt", - gain = 1.0, + gain = 0.4, distance = 8 }, death = { name = "animalia_cow_death", - gain = 1.0, + gain = 0.4, distance = 8 } }, - -- Behavior - defend_owner = false, - follow = { - "farming:wheat", - }, + drops = { + {name = "animalia:beef_raw", min = 1, max = 3, chance = 1}, + {name = "animalia:leather", min = 1, max = 3, chance = 2} + }, + follow = follows, consumable_nodes = { { name = "default:dirt_with_grass", @@ -153,36 +90,95 @@ animalia.register_mob("cow", { replacement = "default:dry_dirt" } }, - drops = { - {name = "animalia:leather", chance = 2, min = 1, max = 2}, - {name = "animalia:beef_raw", chance = 1, min = 1, max = 4} - }, - -- Functions head_data = { offset = {x = 0, y = 0.5, z = 0}, pitch_correction = -45, pivot_h = 0.75, pivot_v = 1 }, - logic = cow_logic, - get_staticdata = mobkit.statfunc, - on_step = animalia.on_step, - on_activate = animalia.on_activate, + -- Function + utility_stack = { + [1] = { + utility = "animalia:wander", + get_score = function(self) + return 0.1, {self, true} + end + }, + [2] = { + utility = "animalia:eat_from_turf", + get_score = function(self) + if math.random(25) < 2 then + return 0.1, {self} + end + return 0 + end + }, + [3] = { + utility = "animalia:swim_to_land", + get_score = function(self) + if self.in_liquid then + return 1, {self} + end + return 0 + end + }, + [4] = { + utility = "animalia:follow_player", + get_score = function(self) + if self.lasso_origin + and type(self.lasso_origin) == "userdata" then + return 0.8, {self, self.lasso_origin, true} + end + local player = creatura.get_nearby_player(self) + if player + and self:follow_wielded_item(player) then + return 0.8, {self, player} + end + return 0 + end + }, + [5] = { + utility = "animalia:mammal_breed", + get_score = function(self) + if self.breeding then + return 0.9, {self} + end + return 0 + end + } + }, + activate_func = function(self) + animalia.initialize_api(self) + animalia.initialize_lasso(self) + self.gotten = self:recall("gotten") or false + self.attention_span = 8 + self._path = {} + end, + step_func = function(self) + animalia.step_timers(self) + animalia.head_tracking(self, 0.75, 0.75) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + end, + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) + end + end, on_rightclick = function(self, clicker) - if animalia.feed_tame(self, clicker, 1, false, true) then return end - mob_core.protect(self, clicker, true) - mob_core.nametag(self, clicker, true) - + if animalia.feed(self, clicker, false, true) then + return + end local tool = clicker:get_wielded_item() local name = clicker:get_player_name() if tool:get_name() == "bucket:bucket_empty" then - if self.child == true then + if self.growth_scale < 1 then return end - if self.gotten == true then + if self.gotten then minetest.chat_send_player(name, "This Cow has already been milked.") return end @@ -195,53 +191,21 @@ animalia.register_mob("cow", { if inv:room_for_item("main", {name = "animalia:bucket_milk"}) then clicker:get_inventory():add_item("main", "animalia:bucket_milk") else - local pos = self.object:get_pos() + local pos = self:get_pos("floor") pos.y = pos.y + 0.5 minetest.add_item(pos, {name = "animalia:bucket_milk"}) end - self.gotten = mobkit.remember(self, "gotten", true) + self.gotten = self:memorize("gotten", true) return end + animalia.add_libri_page(self, clicker, {name = "cow", form = "pg_cow;Cows"}) end, - on_punch = function(self, puncher, _, tool_capabilities, dir) - mob_core.on_punch_basic(self, puncher, tool_capabilities, dir) - animalia.hq_sporadic_flee(self, 10) + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + self:initiate_utility("animalia:flee_from_player", self, puncher) + self:set_utility_score(1) end }) -minetest.register_craftitem("animalia:leather", { - description = "Leather", - inventory_image = "animalia_leather.png" -}) - -minetest.register_craftitem("animalia:bucket_milk", { - description = "Bucket of Milk", - inventory_image = "animalia_milk_bucket.png", - stack_max = 1, - on_use = minetest.item_eat(8, "bucket:bucket_empty"), - groups = {food_milk = 1, flammable = 3}, -}) - -minetest.register_craftitem("animalia:beef_raw", { - description = "Raw Beef", - inventory_image = "animalia_beef_raw.png", - on_use = minetest.item_eat(1), - groups = {flammable = 2, meat = 1, food_meat = 1}, -}) - -minetest.register_craftitem("animalia:beef_cooked", { - description = "Steak", - inventory_image = "animalia_beef_cooked.png", - on_use = minetest.item_eat(8), - groups = {flammable = 2, meat = 1, food_meat = 1}, -}) - -minetest.register_craft({ - type = "cooking", - recipe = "animalia:beef_raw", - output = "animalia:beef_cooked", -}) - - -mob_core.register_spawn_egg("animalia:cow", "cac3a1" ,"464438") \ No newline at end of file +creatura.register_spawn_egg("animalia:cow", "cac3a1" ,"464438") \ No newline at end of file diff --git a/mobs/frog.lua b/mobs/frog.lua new file mode 100644 index 0000000..6b6f2fa --- /dev/null +++ b/mobs/frog.lua @@ -0,0 +1,195 @@ +---------- +-- Frog -- +---------- + +local random = math.random + +local vec_dist = vector.distance + +creatura.register_mob("animalia:frog", { + -- Stats + max_health = 5, + armor_groups = {fleshy = 200}, + damage = 0, + speed = 4, + tracking_range = 16, + despawn_after = 2500, + -- Entity Physics + stepheight = 1.1, + max_fall = 100, + turn_rate = 10, + bouyancy_multiplier = 0, + -- Visuals + mesh = "animalia_frog.b3d", + hitbox = { + width = 0.15, + height = 0.3 + }, + visual_size = {x = 7, y = 7}, + textures = { + "animalia_frog_1.png", + "animalia_frog_2.png" + }, + child_textures = { + "animalia_tadpole.png" + }, + animations = { + stand = {range = {x = 1, y = 40}, speed = 10, frame_blend = 0.3, loop = true}, + float = {range = {x = 90, y = 90}, speed = 1, frame_blend = 0.3, loop = true}, + swim = {range = {x = 90, y = 110}, speed = 50, frame_blend = 0.3, loop = true}, + walk = {range = {x = 50, y = 80}, speed = 50, frame_blend = 0.3, loop = true} + }, + -- Misc + makes_footstep_sound = true, + catch_with_net = true, + sounds = { + random = { + name = "animalia_frog", + gain = 0.5, + distance = 32, + variations = 3 + } + }, + follow = { + "butterflies:butterfly_red", + "butterflies:butterfly_white", + "butterflies:butterfly_violet" + }, + head_data = { + offset = {x = 0, y = 0.43, z = 0}, + pitch_correction = -15, + pivot_h = 0.3, + pivot_v = 0.3 + }, + -- Function + utility_stack = { + [1] = { + utility = "animalia:wander", + get_score = function(self) + return 0.1, {self} + end + }, + [2] = { + utility = "animalia:wander_water_surface", + get_score = function(self) + if self.in_liquid then + return 0.11, {self} + end + return 0 + end + }, + [3] = { + utility = "animalia:eat_bug_nodes", + get_score = function(self) + local pos = self.object:get_pos() + if math.random(12) * 0.01 then + local food = minetest.find_nodes_in_area(vector.subtract(pos, 1.5), vector.add(pos, 1.5), self.follow) + if food[1] then + return 0.2, {self} + end + end + return 0 + end + }, + [4] = { + utility = "animalia:flop", + get_score = function(self) + if not self.in_liquid + and self.growth_scale <= 0.6 then + return 1 + end + return 0 + end + }, + [5] = { + utility = "animalia:breed_water_surface", + get_score = function(self) + if self.breeding + and self.in_liquid then + return 1 + end + return 0 + end + }, + [6] = { + utility = "animalia:flee_from_player", + get_score = function(self) + if self.in_liquid then return 0 end + local player = creatura.get_nearby_player(self) + if player then + local trust = self.trust[player:get_player_name()] or 0 + self._nearby_player = player -- stored to memory to avoid calling get_nearby_player again + return (10 - (vec_dist(self.object:get_pos(), player:get_pos()) + trust)) * 0.1, {self, player} + end + return 0 + end + }, + [7] = { + utility = "animalia:flee_to_water", + get_score = function(self) + if self.in_liquid then return 0 end + local pos = self.object:get_pos() + local water = minetest.find_nodes_in_area(vector.subtract(pos, 1.5), vector.add(pos, 1.5), {"default:water_source"}) + if not water[1] then return 0 end + local player = self._nearby_player + if player then + local trust = self.trust[player:get_player_name()] or 0 + return (10 - (vec_dist(self.object:get_pos(), player:get_pos()) + trust)) * 0.1, {self, player} + end + return 0 + end + } + }, + activate_func = function(self) + animalia.initialize_api(self) + animalia.initialize_lasso(self) + self.trust = self:recall("trust") or {} + for i = 1, 15 do + local frame = 120 + i + local anim = {range = {x = frame, y = frame}, speed = 1, frame_blend = 0.3, loop = false} + self.animations["tongue_" .. i] = anim + end + end, + step_func = function(self) + animalia.step_timers(self) + animalia.head_tracking(self, 0.2, 0.2) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + if self:timer(random(5, 10)) then + self:play_sound("random") + end + local props = self.object:get_properties() + if self.growth_scale <= 0.6 + and props.mesh ~= "animalia_tadpole.b3d" then + self.object:set_properties({ + mesh = "animalia_tadpole.b3d" + }) + end + end, + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) + end + end, + on_rightclick = function(self, clicker) + if animalia.feed(self, clicker, false, true) then + local name = clicker:get_player_name() + if self.trust[name] then + self.trust[name] = self.trust[name] + 1 + else + self.trust[name] = 1 + end + if self.trust[name] > 5 then self.trust[name] = 5 end + self:memorize("trust", self.trust) + return + end + animalia.add_libri_page(self, clicker, {name = "frog", form = "pg_frog;Frogs"}) + end, + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + self.trust[puncher:get_player_name()] = 0 + self:memorize("trust", self.trust) + end +}) + +creatura.register_spawn_egg("animalia:frog", "67942e", "294811") \ No newline at end of file diff --git a/mobs/horse.lua b/mobs/horse.lua index be1f5f1..0201813 100644 --- a/mobs/horse.lua +++ b/mobs/horse.lua @@ -4,14 +4,26 @@ local random = math.random +local follows = {} + +minetest.register_on_mods_loaded(function() + for name, def in pairs(minetest.registered_items) do + if (name:match(":wheat") + or minetest.get_item_group(name, "food_wheat") > 0) + and not name:find("seed") then + table.insert(follows, name) + end + end +end) + local function set_pattern(self) local types = { "spots", "patches" } - if mobkit.recall(self, "pattern") - and not mobkit.recall(self, "pattern"):find("better_fauna") then - local pattern = mobkit.recall(self, "pattern") + if self:recall("pattern") + and not self:recall("pattern"):find("better_fauna") then + local pattern = self:recall("pattern") local texture = self.object:get_properties().textures[1] self.object:set_properties({ textures = {texture .. "^" .. pattern} @@ -38,128 +50,29 @@ local function set_pattern(self) self.object:set_properties({ textures = {texture .. "^" .. overlay} }) - mobkit.remember(self, "pattern", overlay) + self:memorize("pattern", overlay) end end -local function horse_logic(self) - - if self.hp <= 0 then - mob_core.on_die(self) - return - end - - if self.status ~= "following" then - if self.attention_span > 1 then - self.attention_span = self.attention_span - self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - else - self.attention_span = self.attention_span + self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - - animalia.head_tracking(self) - - if mobkit.timer(self, 1) then - - local prty = mobkit.get_queue_priority(self) - local player = mobkit.get_nearby_player(self) - local pos = self.object:get_pos() - - mob_core.random_sound(self, 24) - mob_core.growth(self) - - if self.breaking then - if not minetest.get_player_by_name(self.breaker) - or not self.driver then - self.breaking = nil - self.breaker = nil - else - local yaw = self.object:get_yaw() - local yaw2 = minetest.get_player_by_name(self.breaker):get_look_horizontal() - if math.abs(yaw - yaw2) > 5.8 - or math.abs(yaw - yaw2) < 0.5 then - self.breaking_progress = self.breaking_progress + 1 - else - self.breaking_progress = self.breaking_progress - 1 - end - animalia.hq_sporadic_flee(self, 10) - if self.breaking_progress < -5 - or minetest.get_player_by_name(self.breaker):get_player_control().sneak then - mob_core.detach(self.driver, {x = 1, y = 0, z = 1}) - mobkit.lq_idle(self, 0.5, "rear") - self.breaking = nil - self.breaker = nil - self.breaking_progress = nil - elseif self.breaking_progress > 5 then - mob_core.set_owner(self, self.breaker) - self.breaking = nil - self.breaker = nil - self.breaking_progress = nil - local prt_pos = vector.new(pos.x, pos.y + 2, pos.z) - local minppos = vector.add(prt_pos, 1) - local maxppos = vector.subtract(prt_pos, 1) - animalia.particle_spawner(prt_pos, "mob_core_green_particle.png", "float", minppos, maxppos) - mobkit.clear_queue_high(self) - end - end - return - end - - if prty < 20 - and self.driver - and not self.breaking then - animalia.hq_mount_logic(self, 20) - end - - if prty < 5 - and self.isinliquid then - animalia.hq_go_to_land(self, 5) - end - - if prty < 4 - and self.breeding then - animalia.hq_horse_breed(self, 4) - end - - if prty == 2 - and not self.lasso_player - and (not player - or not mob_core.follow_holding(self, player)) then - mobkit.clear_queue_high(self) - end - - if prty < 2 then - if self.caught_with_lasso - and self.lasso_player then - animalia.hq_follow_player(self, 2, self.lasso_player, true) - elseif player then - if self.attention_span < 5 then - if mob_core.follow_holding(self, player) then - animalia.hq_follow_player(self, 2, player) - self.attention_span = self.attention_span + 1 - end - end - end - end - - if mobkit.is_queue_empty_high(self) then - animalia.hq_wander_group(self, 0, 12) - end - end -end - -animalia.register_mob("horse", { +creatura.register_mob("animalia:horse", { -- Stats - health = 40, - fleshy = 100, - view_range = 32, - lung_capacity = 10, - -- Visual - collisionbox = {-0.65, 0, -0.65, 0.65, 1.95, 0.65}, - visual_size = {x = 10, y = 10}, - mesh = "animalia_horse.b3d", + max_health = 40, + armor_groups = {fleshy = 100}, + damage = 0, + speed = 10, + tracking_range = 24, + despawn_after = 2000, + -- Entity Physics + stepheight = 1.1, + turn_rate = 6, + boid_seperation = 1.5, + -- Visuals + mesh = "animalia_horse.b3d", + hitbox = { + width = 0.65, + height = 1.95 + }, + visual_size = {x = 10, y = 10}, textures = { "animalia_horse_1.png", "animalia_horse_2.png", @@ -175,29 +88,16 @@ animalia.register_mob("horse", { rear = {range = {x = 120, y = 150}, speed = 27, frame_blend = 0.2, loop = false}, rear_constant = {range = {x = 130, y = 140}, speed = 20, frame_blend = 0.3, loop = true} }, - -- Physics - speed = 10, - max_fall = 8, - -- Attributes + -- Misc + catch_with_net = true, sounds = { alter_child_pitch = true, random = { - { - name = "animalia_horse_idle_1", - gain = 1.0, - distance = 8 - }, - { - name = "animalia_horse_idle_2", - gain = 1.0, - distance = 8 - }, - { - name = "animalia_horse_idle_3", - gain = 1.0, - distance = 8 - } - }, + name = "animalia_horse_idle", + gain = 1.0, + distance = 8, + variations = 3, + }, hurt = { name = "animalia_horse_hurt", gain = 1.0, @@ -209,37 +109,103 @@ animalia.register_mob("horse", { distance = 8 } }, - -- Behavior - defend_owner = false, - follow = { - "farming:wheat", + drops = { + {name = "animalia:leather", min = 1, max = 4, chance = 2} + }, + follow = follows, + consumable_nodes = { + { + name = "default:dirt_with_grass", + replacement = "default:dirt" + }, + { + name = "default:dry_dirt_with_dry_grass", + replacement = "default:dry_dirt" + } }, - drops = { - {name = "animalia:leather", chance = 2, min = 1, max = 4}, - }, - player_rotation = {x = -60, y = 180, z = 0}, - driver_scale = {x = 0.1, y = 0.1}, - driver_attach_at = {x = 0, y = 1.1, z = 0.5}, - driver_attach_bone = "Torso", - driver_eye_offset = {{x = 0, y = 15, z = 0}, {x = 0, y = 15, z = 15}}, - -- Functions head_data = { bone = "Neck.CTRL", - offset = {x = 0, y = 1.98, z = 0}, + offset = {x = 0, y = 2, z = 0}, pitch_correction = 35, pivot_h = 1, pivot_v = 1.5 }, - logic = horse_logic, - get_staticdata = mobkit.statfunc, - on_step = animalia.on_step, - on_activate = function(self, staticdata, dtime_s) - animalia.on_activate(self, staticdata, dtime_s) + -- Function + utility_stack = { + [1] = { + utility = "animalia:boid_wander", + get_score = function(self) + return 0.1, {self, true} + end + }, + [2] = { + utility = "animalia:eat_from_turf", + get_score = function(self) + return math.random(11) * 0.01, {self} + end + }, + [3] = { + utility = "animalia:swim_to_land", + get_score = function(self) + if self.in_liquid then + return 0.95, {self} + end + return 0 + end + }, + [4] = { + utility = "animalia:follow_player", + get_score = function(self) + if self.lasso_origin + and type(self.lasso_origin) == "userdata" then + return 0.8, {self, self.lasso_origin, true} + end + local player = creatura.get_nearby_player(self) + if player + and self:follow_wielded_item(player) then + return 0.8, {self, player} + end + return 0 + end + }, + [5] = { + utility = "animalia:horse_breed", + get_score = function(self) + if self.breeding then + return 0.9, {self} + end + return 0 + end + }, + [6] = { + utility = "animalia:mount", + get_score = function(self) + if self.rider + and self.saddled then + return 1, {self, self.rider} + end + return 0 + end + } + }, + activate_func = function(self) + animalia.initialize_api(self) + animalia.initialize_lasso(self) + self._path = {} set_pattern(self) - self.saddled = mobkit.recall(self, "saddled") or false - self.max_hp = mobkit.recall(self, "max_hp") or random(30, 45) - self.speed = mobkit.recall(self, "speed") or random(5, 10) - self.jump_power = mobkit.recall(self, "speed") or random(2, 5) + self.owner = self:recall("owner") or nil + if self.owner then + self._despawn = nil + self.despawn_after = nil + end + self.rider = nil + self.saddled = self:recall("saddled") or false + self.max_health = self:recall("max_health") or random(30, 45) + self.speed = self:recall("speed") or random(5, 10) + self.jump_power = self:recall("jump_power") or random(2, 5) + self:memorize("max_health", self.max_health) + self:memorize("speed", self.speed) + self:memorize("jump_power", self.jump_power) if self.saddled then local texture = self.object:get_properties().textures[1] self.object:set_properties({ @@ -250,38 +216,95 @@ animalia.register_mob("horse", { {name = "animalia:saddle", chance = 1, min = 1, max = 1} } end - end, + end, + step_func = function(self) + animalia.step_timers(self) + animalia.head_tracking(self) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + if self.breaking + and self:timer(1) then + local pos = self:get_center_pos() + if not minetest.get_player_by_name(self.breaker) then + self.breaking = nil + self.breaker = nil + else + local yaw = self.object:get_yaw() + local yaw2 = minetest.get_player_by_name(self.breaker):get_look_horizontal() + if math.abs(yaw - yaw2) > 5.8 + or math.abs(yaw - yaw2) < 0.5 then + self.breaking_progress = self.breaking_progress + 1 + else + self.breaking_progress = self.breaking_progress - 1 + end + self:initiate_utility("animalia:sporadic_flee", self, puncher, true) + if self.breaking_progress < -5 + or minetest.get_player_by_name(self.breaker):get_player_control().sneak then + animalia.mount(self, minetest.get_player_by_name(self.breaker)) + creatura.action_idle(self, 0.5, "rear") + self.breaking = nil + self.breaker = nil + self.breaking_progress = nil + elseif self.breaking_progress > 5 then + animalia.mount(self, minetest.get_player_by_name(self.breaker)) + self.owner = self:memorize("owner", self.breaker) + animalia.protect_from_despawn(self) + self.breaking = nil + self.breaker = nil + self.breaking_progress = nil + local prt_pos = vector.new(pos.x, pos.y + 2, pos.z) + local minppos = vector.add(prt_pos, 1) + local maxppos = vector.subtract(prt_pos, 1) + animalia.particle_spawner(prt_pos, "creatura_particle_green.png", "float", minppos, maxppos) + self:clear_behavior() + end + end + end + end, + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) + end + end, on_rightclick = function(self, clicker) - if animalia.feed_tame(self, clicker, 1, false, true) then return end - mob_core.protect(self, clicker, false) - mob_core.nametag(self, clicker, true) + if animalia.feed(self, clicker, false, true) then + return + end local tool = clicker:get_wielded_item() - if self.tamed + local tool_name = clicker:get_wielded_item():get_name() + if self.owner and self.owner == clicker:get_player_name() then if self.saddled - and tool:get_name() == "" then - mob_core.mount(self, clicker) - elseif tool:get_name() == "animalia:saddle" then - self.saddled = mobkit.remember(self, "saddled", true) + and tool_name == "" then + animalia.mount(self, clicker, {rot = {x = -60, y = 180, z = 0}, pos = {x = 0, y = 1.1, z = 0.5}}) + self:initiate_utility("animalia:mount", self, clicker) + elseif tool_name == "animalia:saddle" then + self.saddled = self:memorize("saddled", true) local texture = self.object:get_properties().textures[1] self.object:set_properties({ textures = {texture .. "^animalia_horse_saddle.png"} }) + self.drops = { + {name = "animalia:leather", chance = 2, min = 1, max = 4}, + {name = "animalia:saddle", chance = 1, min = 1, max = 1} + } tool:take_item() clicker:set_wielded_item(tool) end - elseif not self.tamed - and tool:get_name() == "" then - mob_core.mount(self, clicker) + elseif not self.owner + and tool_name == "" then + animalia.mount(self, clicker, {rot = {x = -60, y = 180, z = 0}, pos = {x = 0, y = 1.1, z = 0.5}}) self.breaking = true self.breaker = clicker:get_player_name() self.breaking_progress = 0 end + animalia.add_libri_page(self, clicker, {name = "horse", form = "pg_horse;Horses"}) end, - on_punch = function(self, puncher, _, tool_capabilities, dir) - mob_core.on_punch_basic(self, puncher, tool_capabilities, dir) - animalia.hq_sporadic_flee(self, 10) + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + self:initiate_utility("animalia:boid_flee_from_player", self, puncher, true) + self:set_utility_score(1) end }) -mob_core.register_spawn_egg("animalia:horse", "ebdfd8" ,"653818") \ No newline at end of file +creatura.register_spawn_egg("animalia:horse", "ebdfd8" ,"653818") \ No newline at end of file diff --git a/mobs/pig.lua b/mobs/pig.lua index 69e4b62..a6fa7c3 100644 --- a/mobs/pig.lua +++ b/mobs/pig.lua @@ -2,78 +2,47 @@ -- Pig -- --------- -local function pig_logic(self) - - if self.hp <= 0 then - mob_core.on_die(self) - return - end +local follows = {} - if self.status ~= "following" then - if self.attention_span > 1 then - self.attention_span = self.attention_span - self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - else - self.attention_span = self.attention_span + self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - - if mobkit.timer(self, 3) then - - local prty = mobkit.get_queue_priority(self) - local player = mobkit.get_nearby_player(self) - - mob_core.random_sound(self, 14) - mob_core.growth(self) - - if prty < 4 - and self.isinliquid then - animalia.hq_go_to_land(self, 4) - end - - if prty < 3 - and self.breeding then - animalia.hq_breed(self, 3) - end - - if prty == 2 - and not self.lasso_player - and (not player - or not mob_core.follow_holding(self, player)) then - mobkit.clear_queue_high(self) - end - - if prty < 2 then - if self.caught_with_lasso - and self.lasso_player then - animalia.hq_follow_player(self, 2, self.lasso_player, true) - elseif player then - if self.attention_span < 5 then - if mob_core.follow_holding(self, player) then - animalia.hq_follow_player(self, 2, player) - self.attention_span = self.attention_span + 3 - end - end - end +minetest.register_on_mods_loaded(function() + for name, def in pairs(minetest.registered_items) do + if name:match(":carrot") + and (minetest.get_item_group(name, "food") > 0 + or minetest.get_item_group(name, "food_carrot") > 0) then + table.insert(follows, name) end + end +end) - if mobkit.is_queue_empty_high(self) then - animalia.hq_wander_group(self, 0, 8) +local destroyable_crops = {} + +minetest.register_on_mods_loaded(function() + for name in pairs(minetest.registered_nodes) do + if name:match("^crops:") + or name:match("^farming:") then + table.insert(destroyable_crops, {name = name, replacement = "air"}) end end -end +end) -animalia.register_mob("pig", { +creatura.register_mob("animalia:pig", { -- Stats - health = 20, - fleshy = 100, - view_range = 32, - lung_capacity = 10, - -- Visual - collisionbox = {-0.35, -0.45, -0.35, 0.35, 0.4, 0.35}, - visual_size = {x = 11, y = 11}, - mesh = "animalia_pig.b3d", + max_health = 10, + armor_groups = {fleshy = 100}, + damage = 0, + speed = 3, + tracking_range = 16, + despawn_after = 1500, + -- Entity Physics + stepheight = 1.1, + turn_rate = 6, + -- Visuals + mesh = "animalia_pig.b3d", + hitbox = { + width = 0.35, + height = 0.7 + }, + visual_size = {x = 10, y = 10}, female_textures = { "animalia_pig_1.png", "animalia_pig_2.png", @@ -94,71 +63,109 @@ animalia.register_mob("pig", { walk = {range = {x = 1, y = 20}, speed = 30, frame_blend = 0.3, loop = true}, run = {range = {x = 1, y = 20}, speed = 45, frame_blend = 0.3, loop = true}, }, - -- Physics - speed = 4, - max_fall = 3, - -- Attributes - sounds = { - alter_child_pitch = true, + -- Misc + consumable_nodes = destroyable_crops, + birth_count = 2, + catch_with_net = true, + sounds = { random = { name = "animalia_pig_idle", gain = 1.0, distance = 8 }, hurt = { - name = "animalia_pig_idle", + name = "animalia_pig_hurt", gain = 1.0, - pitch = 0.5, distance = 8 }, death = { name = "animalia_pig_death", - gain = 1.0, + gain = 1.0, distance = 8 } }, - -- Behavior - defend_owner = false, - follow = { - "farming:carrot" + drops = { + {name = "animalia:porkchop_raw", min = 1, max = 3, chance = 1} + }, + follow = follows, + -- Function + utility_stack = { + [1] = { + utility = "animalia:wander", + get_score = function(self) + return 0.1, {self, true} + end + }, + [2] = { + utility = "animalia:eat_from_turf", + get_score = function(self) + if math.random(25) < 2 then + return 0.1, {self} + end + return 0 + end + }, + [3] = { + utility = "animalia:swim_to_land", + get_score = function(self) + if self.in_liquid then + return 1, {self} + end + return 0 + end + }, + [4] = { + utility = "animalia:follow_player", + get_score = function(self) + if self.lasso_origin + and type(self.lasso_origin) == "userdata" then + return 0.8, {self, self.lasso_origin, true} + end + local player = creatura.get_nearby_player(self) + if player + and self:follow_wielded_item(player) then + return 0.8, {self, player} + end + return 0 + end + }, + [5] = { + utility = "animalia:mammal_breed", + get_score = function(self) + if self.breeding then + return 0.9, {self} + end + return 0 + end + } }, - drops = { - {name = "animalia:porkchop_raw", chance = 1, min = 1, max = 4} - }, - -- Functions - logic = pig_logic, - get_staticdata = mobkit.statfunc, - on_step = animalia.on_step, - on_activate = animalia.on_activate, + activate_func = function(self) + animalia.initialize_api(self) + animalia.initialize_lasso(self) + self.attention_span = 8 + self._path = {} + end, + step_func = function(self) + animalia.step_timers(self) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + end, + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) + end + end, on_rightclick = function(self, clicker) - if animalia.feed_tame(self, clicker, 1, false, true) then return end - mob_core.protect(self, clicker, true) - mob_core.nametag(self, clicker, true) + if animalia.feed(self, clicker, false, true) then + return + end + animalia.add_libri_page(self, clicker, {name = "pig", form = "pg_pig;Pigs"}) end, - on_punch = function(self, puncher, _, tool_capabilities, dir) - mob_core.on_punch_basic(self, puncher, tool_capabilities, dir) - animalia.hq_sporadic_flee(self, 10) + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + self:initiate_utility("animalia:flee_from_player", self, puncher) + self:set_utility_score(1) end }) -minetest.register_craftitem("animalia:porkchop_raw", { - description = "Raw Porkchop", - inventory_image = "animalia_porkchop_raw.png", - on_use = minetest.item_eat(1), - groups = {flammable = 2, meat = 1, food_meat = 1}, -}) - -minetest.register_craftitem("animalia:porkchop_cooked", { - description = "Cooked Porkchop", - inventory_image = "animalia_porkchop_cooked.png", - on_use = minetest.item_eat(7), - groups = {flammable = 2, meat = 1, food_meat = 1}, -}) - -minetest.register_craft({ - type = "cooking", - recipe = "animalia:porkchop_raw", - output = "animalia:porkchop_cooked", -}) - -mob_core.register_spawn_egg("animalia:pig", "e0b1a7" ,"cc9485") \ No newline at end of file +creatura.register_spawn_egg("animalia:pig", "e0b1a7" ,"cc9485") \ No newline at end of file diff --git a/mobs/reindeer.lua b/mobs/reindeer.lua index 0ef2f50..7926167 100644 --- a/mobs/reindeer.lua +++ b/mobs/reindeer.lua @@ -1,151 +1,148 @@ ---------- --- Cow -- ---------- +-------------- +-- Reindeer -- +-------------- -local clamp_bone_rot = animalia.clamp_bone_rot +local follows = {} -local interp = animalia.interp +minetest.register_on_mods_loaded(function() + for name, def in pairs(minetest.registered_items) do + if (name:match(":wheat") + or minetest.get_item_group(name, "food_wheat") > 0) + and not name:find("seed") then + table.insert(follows, name) + end + end +end) local random = math.random -local blend = animalia.frame_blend -local function reindeer_logic(self) - - if self.hp <= 0 then - mob_core.on_die(self) - return - end - - if self.status ~= "following" then - if self.attention_span > 1 then - self.attention_span = self.attention_span - self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - else - self.attention_span = self.attention_span + self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - - animalia.head_tracking(self, 0.75, 0.75) - - if mobkit.timer(self, 3) then - - local prty = mobkit.get_queue_priority(self) - local player = mobkit.get_nearby_player(self) - - mob_core.random_sound(self, 14) - mob_core.growth(self) - - if prty < 4 - and self.isinliquid then - animalia.hq_go_to_land(self, 4) - end - - if prty < 3 - and self.breeding then - animalia.hq_breed(self, 3) - end - - if prty == 2 - and not self.lasso_player - and (not player - or not mob_core.follow_holding(self, player)) then - mobkit.clear_queue_high(self) - end - - if prty < 2 then - if self.caught_with_lasso - and self.lasso_player then - animalia.hq_follow_player(self, 2, self.lasso_player, true) - elseif player then - if self.attention_span < 5 then - if mob_core.follow_holding(self, player) then - animalia.hq_follow_player(self, 2, player) - self.attention_span = self.attention_span + 1 - end - end - end - end - - if mobkit.is_queue_empty_high(self) then - animalia.hq_wander_group(self, 0, 10) - end - end -end - -animalia.register_mob("reindeer", { +creatura.register_mob("animalia:reindeer", { -- Stats - health = 20, - fleshy = 100, - view_range = 32, - lung_capacity = 10, - -- Visual - collisionbox = {-0.45, 0, -0.45, 0.45, 0.9, 0.45}, - visual_size = {x = 10, y = 10}, - mesh = "animalia_reindeer.b3d", - textures = { - "animalia_reindeer.png", - }, - child_textures = { - "animalia_reindeer_calf.png", + max_health = 20, + armor_groups = {fleshy = 125}, + damage = 0, + speed = 3, + boid_seperation = 1, + tracking_range = 16, + despawn_after = 1500, + -- Entity Physics + stepheight = 1.1, + -- Visuals + mesh = "animalia_reindeer.b3d", + hitbox = { + width = 0.45, + height = 0.9 }, + visual_size = {x = 10, y = 10}, + textures = {"animalia_reindeer.png"}, + child_textures = {"animalia_reindeer_calf.png"}, animations = { stand = {range = {x = 1, y = 60}, speed = 10, frame_blend = 0.3, loop = true}, walk = {range = {x = 70, y = 110}, speed = 40, frame_blend = 0.3, loop = true}, run = {range = {x = 70, y = 110}, speed = 50, frame_blend = 0.3, loop = true}, }, - -- Physics - speed = 4, - max_fall = 3, - -- Behavior - defend_owner = false, - follow = { - "farming:wheat", + -- Misc + catch_with_net = true, + drops = { + {name = "animalia:venison_raw", min = 1, max = 3, chance = 1}, + {name = "animalia:leather", min = 1, max = 3, chance = 2} + }, + follow = follows, + consumable_nodes = { + { + name = "default:dirt_with_grass", + replacement = "default:dirt" + }, + { + name = "default:dry_dirt_with_dry_grass", + replacement = "default:dry_dirt" + } }, - drops = { - {name = "animalia:venison_raw", chance = 1, min = 1, max = 4} - }, - -- Functions head_data = { offset = {x = 0, y = 0.7, z = 0}, pitch_correction = -45, pivot_h = 1, pivot_v = 1 }, - logic = reindeer_logic, - get_staticdata = mobkit.statfunc, - on_step = animalia.on_step, - on_activate = animalia.on_activate, + -- Function + utility_stack = { + [1] = { + utility = "animalia:boid_wander", + get_score = function(self) + return 0.1, {self, true} + end + }, + [2] = { + utility = "animalia:eat_from_turf", + get_score = function(self) + if math.random(25) < 2 then + return 0.1, {self} + end + return 0 + end + }, + [3] = { + utility = "animalia:swim_to_land", + get_score = function(self) + if self.in_liquid then + return 1, {self} + end + return 0 + end + }, + [4] = { + utility = "animalia:follow_player", + get_score = function(self) + if self.lasso_origin + and type(self.lasso_origin) == "userdata" then + return 0.8, {self, self.lasso_origin, true} + end + local player = creatura.get_nearby_player(self) + if player + and self:follow_wielded_item(player) then + return 0.8, {self, player} + end + return 0 + end + }, + [5] = { + utility = "animalia:mammal_breed", + get_score = function(self) + if self.breeding then + return 0.9, {self} + end + return 0 + end + } + }, + activate_func = function(self) + animalia.initialize_api(self) + animalia.initialize_lasso(self) + self.attention_span = 8 + self._path = {} + end, + step_func = function(self) + animalia.step_timers(self) + animalia.head_tracking(self, 0.75, 0.75) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + end, + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) + end + end, on_rightclick = function(self, clicker) - if animalia.feed_tame(self, clicker, 1, false, true) then return end - mob_core.protect(self, clicker, true) - mob_core.nametag(self, clicker, true) + if animalia.feed(self, clicker, false, true) then + return + end + animalia.add_libri_page(self, clicker, {name = "reindeer", form = "pg_reindeer;Reindeer"}) end, - on_punch = function(self, puncher, _, tool_capabilities, dir) - mob_core.on_punch_basic(self, puncher, tool_capabilities, dir) - animalia.hq_sporadic_flee(self, 10) + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + self:initiate_utility("animalia:boid_flee_from_player", self, puncher, true) + self:set_utility_score(1) end }) -minetest.register_craftitem("animalia:venison_raw", { - description = "Raw Venison", - inventory_image = "animalia_venison_raw.png", - on_use = minetest.item_eat(1), - groups = {flammable = 2, meat = 1, food_meat = 1}, -}) - -minetest.register_craftitem("animalia:venison_cooked", { - description = "Venison Steak", - inventory_image = "animalia_venison_cooked.png", - on_use = minetest.item_eat(6), - groups = {flammable = 2, meat = 1, food_meat = 1}, -}) - -minetest.register_craft({ - type = "cooking", - recipe = "animalia:venison_raw", - output = "animalia:venison_cooked", -}) - - -mob_core.register_spawn_egg("animalia:reindeer", "8c8174" ,"3d3732") \ No newline at end of file +creatura.register_spawn_egg("animalia:reindeer", "cac3a1" ,"464438") \ No newline at end of file diff --git a/mobs/sheep.lua b/mobs/sheep.lua index 7352029..7aa964f 100644 --- a/mobs/sheep.lua +++ b/mobs/sheep.lua @@ -2,6 +2,24 @@ -- Sheep -- ----------- +local follows = {} + +minetest.register_on_mods_loaded(function() + for name, def in pairs(minetest.registered_items) do + if (name:match(":wheat") + or minetest.get_item_group(name, "food_wheat") > 0) + and not name:find("seed") then + table.insert(follows, name) + end + end +end) + +local wool_block = "wool:wool" + +if not minetest.get_modpath("wool") then + wool_block = nil +end + local creative = minetest.settings:get_bool("creative_mode") local palette = { @@ -22,108 +40,38 @@ local palette = { {"yellow", "Yellow", "#e3ff0070"}, } -local clamp_bone_rot = animalia.clamp_bone_rot - -local interp = animalia.interp - -local min = math.min -local abs = math.abs -local random = math.random - -local function sheep_logic(self) - - if self.hp <= 0 then - mob_core.on_die(self) - return - end - - if self.status ~= "following" then - if self.attention_span > 1 then - self.attention_span = self.attention_span - self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - else - self.attention_span = self.attention_span + self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - - animalia.head_tracking(self, 0.5, 0.5) - - if mobkit.timer(self, 3) then - - local pos = mobkit.get_stand_pos(self) - local prty = mobkit.get_queue_priority(self) - local player = mobkit.get_nearby_player(self) - - mob_core.random_sound(self, 14) - mob_core.growth(self) - - if prty < 5 - and self.isinliquid then - animalia.hq_go_to_land(self, 5) - end - - if prty < 4 - and self.breeding then - animalia.hq_breed(self, 4) - end - - if prty < 3 - and self.gotten - and math.random(1, 16) == 1 then - animalia.hq_eat(self, 3) - end - - if prty == 2 - and not self.lasso_player - and (not player - or not mob_core.follow_holding(self, player)) then - mobkit.clear_queue_high(self) - end - - if prty < 2 then - if self.caught_with_lasso - and self.lasso_player then - animalia.hq_follow_player(self, 2, self.lasso_player, true) - elseif player then - if self.attention_span < 5 then - if mob_core.follow_holding(self, player) then - animalia.hq_follow_player(self, 2, player) - self.attention_span = self.attention_span + 3 - end - end - end - end - - if mobkit.is_queue_empty_high(self) then - animalia.hq_wander_group(self, 0, 12) - end - end -end - -animalia.register_mob("sheep", { +creatura.register_mob("animalia:sheep", { -- Stats - health = 20, - fleshy = 100, - view_range = 32, - lung_capacity = 10, - -- Visual - collisionbox = {-0.4, 0, -0.4, 0.4, 0.8, 0.4}, + max_health = 15, + armor_groups = {fleshy = 125}, + damage = 0, + speed = 3, + tracking_range = 16, + despawn_after = 1500, + -- Entity Physics + stepheight = 1.1, + -- Visuals + mesh = "animalia_sheep.b3d", + hitbox = { + width = 0.4, + height = 0.8 + }, visual_size = {x = 10, y = 10}, - mesh = "animalia_sheep.b3d", - textures = {"animalia_sheep.png^animalia_sheep_wool.png"}, - child_textures = {"animalia_sheep.png"}, + textures = { + "animalia_sheep.png^animalia_sheep_wool.png" + }, + child_textures = { + "animalia_sheep.png" + }, animations = { stand = {range = {x = 1, y = 60}, speed = 10, frame_blend = 0.3, loop = true}, walk = {range = {x = 70, y = 110}, speed = 40, frame_blend = 0.3, loop = true}, run = {range = {x = 70, y = 110}, speed = 50, frame_blend = 0.3, loop = true}, }, - -- Physics - speed = 4, - max_fall = 3, - -- Attributes - sounds = { - alter_child_pitch = true, + use_texture_alpha = true, + -- Misc + catch_with_net = true, + sounds = { random = { name = "animalia_sheep_idle", gain = 1.0, @@ -140,11 +88,11 @@ animalia.register_mob("sheep", { distance = 8 } }, - -- Behavior - defend_owner = false, - follow = { - "farming:wheat", - }, + drops = { + {name = "animalia:mutton_raw", min = 1, max = 3, chance = 1}, + {name = wool_block, min = 1, max = 3, chance = 2} + }, + follow = follows, consumable_nodes = { { name = "default:dirt_with_grass", @@ -155,33 +103,67 @@ animalia.register_mob("sheep", { replacement = "default:dry_dirt" } }, - drops = { - {name = "animalia:mutton_raw", chance = 1, min = 1, max = 4} - }, - -- Functions head_data = { offset = {x = 0, y = 0.41, z = 0}, pitch_correction = -45, pivot_h = 0.75, pivot_v = 0.85 }, - logic = sheep_logic, - get_staticdata = mobkit.statfunc, - on_step = function(self, dtime, moveresult) - animalia.on_step(self, dtime, moveresult) - if mobkit.is_alive(self) then - if self.object:get_properties().textures[1] == "animalia_sheep.png" - and not self.gotten then - self.object:set_properties({ - textures = {"animalia_sheep.png^animalia_sheep_wool.png"}, - }) + -- Function + utility_stack = { + [1] = { + utility = "animalia:wander", + get_score = function(self) + return 0.1, {self, true} end - end - end, - on_activate = function(self, staticdata, dtime_s) - animalia.on_activate(self, staticdata, dtime_s) - self.dye_color = mobkit.recall(self, "dye_color") or "white" - self.dye_hex = mobkit.recall(self, "dye_hex") or "" + }, + [2] = { + utility = "animalia:eat_from_turf", + get_score = function(self) + if math.random(25) < 2 then + return 0.1, {self} + end + return 0 + end + }, + [3] = { + utility = "animalia:swim_to_land", + get_score = function(self) + if self.in_liquid then + return 1, {self} + end + return 0 + end + }, + [4] = { + utility = "animalia:follow_player", + get_score = function(self) + if self.lasso_origin + and type(self.lasso_origin) == "userdata" then + return 0.8, {self, self.lasso_origin, true} + end + local player = creatura.get_nearby_player(self) + if player + and self:follow_wielded_item(player) then + return 0.8, {self, player} + end + return 0 + end + }, + [5] = { + utility = "animalia:mammal_breed", + get_score = function(self) + if self.breeding then + return 0.9, {self} + end + return 0 + end + } + }, + activate_func = function(self) + self.gotten = self:recall("gotten") or false + self.dye_color = self:recall("dye_color") or "white" + self.dye_hex = self:recall("dye_hex") or "" if self.dye_color ~= "white" and not self.gotten then self.object:set_properties({ @@ -193,47 +175,61 @@ animalia.register_mob("sheep", { textures = {"animalia_sheep.png"}, }) end - end, + self.attention_span = 8 + self._path = {} + animalia.initialize_api(self) + animalia.initialize_lasso(self) + end, + step_func = function(self) + animalia.step_timers(self) + animalia.head_tracking(self, 0.75, 0.75) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + end, + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) + end + end, on_rightclick = function(self, clicker) - if animalia.feed_tame(self, clicker, 1, false, true) then return end - mob_core.protect(self, clicker, true) - mob_core.nametag(self, clicker, true) - local item = clicker:get_wielded_item() - local itemname = item:get_name() - local name = clicker:get_player_name() - if itemname == "animalia:shears" + if animalia.feed(self, clicker, false, true) then + return + end + local tool = clicker:get_wielded_item() + local tool_name = tool:get_name() + if tool_name == "animalia:shears" and not self.gotten - and not self.child then + and self.growth_scale > 0.9 then if not minetest.get_modpath("wool") then return end - local obj = minetest.add_item( + minetest.add_item( self.object:get_pos(), ItemStack( "wool:" .. self.dye_color .. " " .. math.random(1, 3) ) ) - self.gotten = mobkit.remember(self, "gotten", true) - self.dye_color = mobkit.remember(self, "dye_color", "white") - self.dye_hex = mobkit.remember(self, "dye_hex", "#abababc000") + self.gotten = self:memorize("gotten", true) + self.dye_color = self:memorize("dye_color", "white") + self.dye_hex = self:memorize("dye_hex", "#abababc000") - item:add_wear(650) -- 100 uses + tool:add_wear(650) -- 100 uses - clicker:set_wielded_item(item) + clicker:set_wielded_item(tool) self.object:set_properties({ textures = {"animalia_sheep.png"}, }) end for _, color in ipairs(palette) do - if itemname:find("dye:") + if tool_name:find("dye:") and not self.gotten - and not self.child then - local dye = string.split(itemname, ":")[2] + and self.growth_scale > 0.9 then + local dye = string.split(tool_name, ":")[2] if color[1] == dye then - self.dye_color = mobkit.remember(self, "dye_color", color[1]) - self.dye_hex = mobkit.remember(self, "dye_hex", color[3]) + self.dye_color = self:memorize("dye_color", color[1]) + self.dye_hex = self:memorize("dye_hex", color[3]) self.drops = { {name = "animalia:mutton_raw", chance = 1, min = 1, max = 4}, @@ -245,38 +241,20 @@ animalia.register_mob("sheep", { }) if not creative then - item:take_item() - clicker:set_wielded_item(item) + tool:take_item() + clicker:set_wielded_item(tool) end break end end end + animalia.add_libri_page(self, clicker, {name = "sheep", form = "pg_sheep;Sheep"}) end, - on_punch = function(self, puncher, _, tool_capabilities, dir) - mob_core.on_punch_basic(self, puncher, tool_capabilities, dir) - animalia.hq_sporadic_flee(self, 10) + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + self:initiate_utility("animalia:boid_flee_from_player", self, puncher, true) + self:set_utility_score(1) end }) -mob_core.register_spawn_egg("animalia:sheep", "f4e6cf", "e1ca9b") - -minetest.register_craftitem("animalia:mutton_raw", { - description = "Raw Mutton", - inventory_image = "animalia_mutton_raw.png", - on_use = minetest.item_eat(1), - groups = {flammable = 2, meat = 1, food_meat = 1}, -}) - -minetest.register_craftitem("animalia:mutton_cooked", { - description = "Cooked Mutton", - inventory_image = "animalia_mutton_cooked.png", - on_use = minetest.item_eat(6), - groups = {flammable = 2, meat = 1, food_meat = 1}, -}) - -minetest.register_craft({ - type = "cooking", - recipe = "animalia:mutton_raw", - output = "animalia:mutton_cooked", -}) \ No newline at end of file +creatura.register_spawn_egg("animalia:sheep", "f4e6cf", "e1ca9b") \ No newline at end of file diff --git a/mobs/tropical_fish.lua b/mobs/tropical_fish.lua new file mode 100644 index 0000000..0c6e91d --- /dev/null +++ b/mobs/tropical_fish.lua @@ -0,0 +1,89 @@ +---------- +-- Fish -- +---------- + +creatura.register_mob("animalia:tropical_fish", { + -- Stats + max_health = 5, + armor_groups = {fleshy = 150}, + damage = 0, + speed = 2, + tracking_range = 6, + despawn_after = 2500, + -- Entity Physics + stepheight = 0.1, + max_fall = 8, + turn_rate = 8, + boid_seperation = 0.3, + bouyancy_multiplier = 0, + -- Visuals + mesh = "animalia_clownfish.b3d", + hitbox = { + width = 0.15, + height = 0.3 + }, + visual_size = {x = 7, y = 7}, + textures = { + "animalia_clownfish.png", + "animalia_blue_tang.png", + "animalia_angelfish.png" + }, + animations = { + swim = {range = {x = 1, y = 20}, speed = 20, frame_blend = 0.3, loop = true}, + flop = {range = {x = 30, y = 40}, speed = 20, frame_blend = 0.3, loop = true}, + }, + -- Misc + catch_with_net = true, + makes_footstep_sound = false, + -- Function + utility_stack = { + { + utility = "animalia:schooling", + get_score = function(self) + return 0.1, {self} + end + }, + { + utility = "animalia:flop", + get_score = function(self) + if not self.in_liquid then + self:hurt(1) + return 1, {self} + end + return 0 + end + }, + }, + activate_func = function(self) + animalia.initialize_api(self) + animalia.initialize_lasso(self) + self.attention_span = 8 + if self.texture_no == 3 then + self.object:set_properties({ + mesh = "animalia_angelfish.b3d", + }) + end + end, + step_func = function(self) + animalia.step_timers(self) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + end, + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) + end + end, + on_rightclick = function(self, clicker) + animalia.add_libri_page(self, clicker, {name = "tropical_fish", form = "pg_tropical_fish;Tropical Fish"}) + end, + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + end +}) + +creatura.register_spawn_egg("animalia:tropical_fish", "e28821", "f6e5d2") + +animalia.alias_mob("animalia:clownfish", "animalia:tropical_fish") +animalia.alias_mob("animalia:blue_tang", "animalia:tropical_fish") +animalia.alias_mob("animalia:angelfish", "animalia:tropical_fish") \ No newline at end of file diff --git a/mobs/turkey.lua b/mobs/turkey.lua index 1b7b88a..310f60f 100644 --- a/mobs/turkey.lua +++ b/mobs/turkey.lua @@ -2,83 +2,35 @@ -- Turkey -- ------------ -local clamp_bone_rot = animalia.clamp_bone_rot +local follows = {} -local interp = animalia.interp - -local function turkey_logic(self) - - if self.hp <= 0 then - mob_core.on_die(self) - return - end - - if self.status ~= "following" then - if self.attention_span > 1 then - self.attention_span = self.attention_span - self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - else - self.attention_span = self.attention_span + self.dtime - mobkit.remember(self, "attention_span", self.attention_span) - end - - animalia.head_tracking(self, 0.45, 0.25) - - if mobkit.timer(self, 3) then - - local prty = mobkit.get_queue_priority(self) - local player = mobkit.get_nearby_player(self) - - mob_core.random_sound(self, 14) - - if prty < 4 - and self.isinliquid then - animalia.hq_go_to_land(self, 4) - end - - if prty < 3 - and self.breeding then - animalia.hq_fowl_breed(self, 3) - end - - if prty == 2 - and not self.lasso_player - and (not player - or not mob_core.follow_holding(self, player)) then - mobkit.clear_queue_high(self) - end - - if prty < 2 then - if self.caught_with_lasso - and self.lasso_player then - animalia.hq_follow_player(self, 2, self.lasso_player, true) - elseif player then - if self.attention_span < 5 then - if mob_core.follow_holding(self, player) then - animalia.hq_follow_player(self, 2, player) - self.attention_span = self.attention_span + 3 - end - end - end +minetest.register_on_mods_loaded(function() + for name, def in pairs(minetest.registered_items) do + if name:match(":seed_") + or name:match("_seed") then + table.insert(follows, name) end + end +end) - if mobkit.is_queue_empty_high(self) then - animalia.hq_wander_group(self, 0, 8) - end - end -end - -animalia.register_mob("turkey", { - -- Stats - health = 15, - fleshy = 100, - view_range = 26, - lung_capacity = 10, - -- Visual - collisionbox = {-0.3, -0.2, -0.3, 0.3, 0.4, 0.3}, - visual_size = {x = 7, y = 7}, - mesh = "animalia_turkey.b3d", +creatura.register_mob("animalia:turkey", { + -- Stats + max_health = 10, + armor_groups = {fleshy = 150}, + damage = 0, + speed = 4, + tracking_range = 16, + despawn_after = 1500, + -- Entity Physics + stepheight = 1.1, + max_fall = 8, + -- Visuals + mesh = "animalia_turkey.b3d", + hitbox = { + width = 0.3, + height = 0.6 + }, + visual_size = {x = 7, y = 7}, female_textures = {"animalia_turkey_hen.png"}, male_textures = {"animalia_turkey_tom.png"}, child_textures = {"animalia_turkey_chick.png"}, @@ -88,12 +40,9 @@ animalia.register_mob("turkey", { run = {range = {x = 40, y = 60}, speed = 45, frame_blend = 0.3, loop = true}, fall = {range = {x = 70, y = 90}, speed = 30, frame_blend = 0.3, loop = true}, }, - -- Physics - speed = 5, - max_fall = 6, - -- Attributes + -- Misc + catch_with_net = true, sounds = { - alter_child_pitch = true, random = { name = "animalia_turkey_idle", gain = 1.0, @@ -110,37 +59,96 @@ animalia.register_mob("turkey", { distance = 8 } }, - -- Behavior - defend_owner = false, - follow = { - "farming:seed_cotton", - "farming:seed_wheat" - }, - drops = { - {name = "animalia:feather", chance = 1, min = 1, max = 2}, - {name = "animalia:poultry_raw", chance = 1, min = 2, max = 5} - }, - -- Functions + drops = { + {name = "animalia:poultry_raw", min = 2, max = 4, chance = 1}, + {name = "animalia:feather", min = 2, max = 4, chance = 2} + }, + follow = follows, head_data = { offset = {x = 0, y = 0.15, z = 0}, pitch_correction = 45, pivot_h = 0.45, pivot_v = 0.65 }, - physics = animalia.lightweight_physics, - logic = turkey_logic, - get_staticdata = mobkit.statfunc, - on_step = animalia.on_step, - on_activate = animalia.on_activate, - on_rightclick = function(self, clicker) - if animalia.feed_tame(self, clicker, 1, false, true) then return end - mob_core.protect(self, clicker, true) - mob_core.nametag(self, clicker, true) - end, - on_punch = function(self, puncher, _, tool_capabilities, dir) - mob_core.on_punch_basic(self, puncher, tool_capabilities, dir) - animalia.hq_sporadic_flee(self, 10) + -- Function + utility_stack = { + [1] = { + utility = "animalia:wander", + get_score = function(self) + return 0.1, {self, true} + end + }, + [2] = { + utility = "animalia:resist_fall", + get_score = function(self) + if not self.touching_ground then + return 0.11, {self} + end + return 0 + end + }, + [3] = { + utility = "animalia:swim_to_land", + get_score = function(self) + if self.in_liquid then + return 1, {self} + end + return 0 + end + }, + [4] = { + utility = "animalia:follow_player", + get_score = function(self) + if self.lasso_origin + and type(self.lasso_origin) == "userdata" then + return 0.8, {self, self.lasso_origin, true} + end + local player = creatura.get_nearby_player(self) + if player + and self:follow_wielded_item(player) then + return 0.8, {self, player} + end + return 0 + end + }, + [5] = { + utility = "animalia:bird_breed", + get_score = function(self) + if self.breeding then + return 0.9, {self} + end + return 0 + end + } + }, + activate_func = function(self) + animalia.initialize_api(self) + animalia.initialize_lasso(self) + self.attention_span = 8 + self._path = {} + end, + step_func = function(self) + animalia.step_timers(self) + animalia.head_tracking(self, 0.75, 0.75) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + end, + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) + end + end, + on_rightclick = function(self, clicker) + if animalia.feed(self, clicker, false, true) then + return + end + animalia.add_libri_page(self, clicker, {name = "turkey", form = "pg_turkey;Turkeys"}) end, + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + self:initiate_utility("animalia:flee_from_player", self, puncher) + self:set_utility_score(1) + end }) -mob_core.register_spawn_egg("animalia:turkey", "352b22", "2f2721") \ No newline at end of file +creatura.register_spawn_egg("animalia:turkey", "352b22", "2f2721") \ No newline at end of file diff --git a/mobs/wolf.lua b/mobs/wolf.lua index 82514af..85c4ffb 100644 --- a/mobs/wolf.lua +++ b/mobs/wolf.lua @@ -2,9 +2,7 @@ -- Wolf -- ---------- -local clamp_bone_rot = animalia.clamp_bone_rot - -local interp = animalia.interp +local vec_dist = vector.distance local follow = { "animalia:mutton_raw", @@ -23,203 +21,239 @@ if minetest.registered_items["bonemeal:bone"] then } end -function animalia.bh_attack(self, prty, target) - if mobkit.is_alive(target) then - if target:is_player() then - if not self.tamed - or target:get_player_name() ~= self.owner then - animalia.hq_attack(self, prty, target) - end - elseif target:get_luaentity() then - if not self.tamed - or not mob_core.shared_owner(self, target) then - animalia.hq_attack(self, prty, target) - end - end - end +local function is_value_in_table(tbl, val) + for _, v in pairs(tbl) do + if v == val then + return true + end + end + return false end -local function wolf_logic(self) - - if self.hp <= 0 then - mob_core.on_die(self) - return - end - - animalia.head_tracking(self, 0.5, 0.75) - - if mobkit.timer(self, 1) then - - local prty = mobkit.get_queue_priority(self) - local player = mobkit.get_nearby_player(self) - - mob_core.random_sound(self, 22) - mob_core.growth(self) - - if self.status ~= "following" then - if self.attention_span > 1 then - self.attention_span = self.attention_span - 1 - mobkit.remember(self, "attention_span", self.attention_span) - end - else - self.attention_span = self.attention_span + 1 - mobkit.remember(self, "attention_span", self.attention_span) - end - - if prty < 22 - and self.order == "sit" then - if not mobkit.is_queue_empty_high(self) then - mobkit.clear_queue_high(self) - end - mobkit.animate(self, "sit") - return - end - - if prty < 21 - and self.owner_target then - if not mob_core.shared_owner(self, self.owner_target) then - animalia.hq_attack(self, 21, self.owner_target) - end - end - - if prty < 20 - and self.order == "follow" - and self.owner - and minetest.get_player_by_name(self.owner) then - local owner = minetest.get_player_by_name(self.owner) - animalia.hq_follow_player(self, 20, owner, true) - end - - if prty < 5 - and self.isinliquid then - animalia.hq_go_to_land(self, 5) - end - - if prty < 4 - and self.breeding then - animalia.hq_breed(self, 4) - end - - if prty == 3 - and not self.lasso_player - and (not player - or not mob_core.follow_holding(self, player)) then - mobkit.clear_queue_high(self) - end - - if prty < 3 then - if self.caught_with_lasso - and self.lasso_player then - animalia.hq_follow_player(self, 3, self.lasso_player, true) - elseif player then - if self.attention_span < 5 then - if mob_core.follow_holding(self, player) then - animalia.hq_follow_player(self, 3, player) - self.attention_span = self.attention_span + 3 - end - end - end - end - - if prty < 2 then - local target = mobkit.get_closest_entity(self, "animalia:sheep") - if target then - animalia.bh_attack(self, 2, target) - end - end - - if mobkit.is_queue_empty_high(self) then - animalia.hq_wander_group(self, 0, 8) - end - end -end - -animalia.register_mob("wolf", { +creatura.register_mob("animalia:wolf", { -- Stats - health = 25, - fleshy = 100, - view_range = 32, - lung_capacity = 10, - -- Visual - collisionbox = {-0.35, -0.375, -0.35, 0.35, 0.4, 0.35}, - visual_size = {x = 9, y = 9}, - scale_stage1 = 0.5, - scale_stage2 = 0.65, - scale_stage3 = 0.80, - mesh = "animalia_wolf.b3d", + max_health = 15, + armor_groups = {fleshy = 100}, + damage = 4, + speed = 5, + tracking_range = 32, + despawn_after = 2000, + -- Entity Physics + stepheight = 1.1, + -- Visuals + mesh = "animalia_wolf.b3d", + hitbox = { + width = 0.35, + height = 0.7 + }, + visual_size = {x = 9, y = 9}, textures = {"animalia_wolf.png"}, animations = { stand = {range = {x = 30, y = 49}, speed = 10, frame_blend = 0.3, loop = true}, sit = {range = {x = 60, y = 90}, speed = 20, frame_blend = 0.3, loop = true}, walk = {range = {x = 1, y = 20}, speed = 30, frame_blend = 0.3, loop = true}, run = {range = {x = 1, y = 20}, speed = 45, frame_blend = 0.3, loop = true}, + leap = {range = {x = 100, y = 100}, speed = 1, frame_blend = 0.15, loop = false} }, - -- Physics - speed = 8, - max_fall = 4, - -- Attributes - sounds = { - alter_child_pitch = true, - random = { - name = "animalia_wolf_idle", - gain = 1.0, - distance = 8 - }, - hurt = { - name = "animalia_wolf_hurt", - gain = 1.0, - pitch = 0.5, - distance = 8 - }, - death = { - name = "animalia_wolf_death", - gain = 1.0, - distance = 8 - } - }, - reach = 2, - damage = 3, - knockback = 2, - punch_cooldown = 1, - -- Behavior - defend_owner = true, - follow = { - "bonemeal:bone", - "animalia:beef_raw", - "animalia:porkchop_raw", - "animalia:mutton_raw", - "animalia:poultry_raw" - }, - -- Functions + -- Misc + catch_with_net = true, + assist_owner = true, + follow = follow, head_data = { offset = {x = 0, y = 0.22, z = 0}, - pitch_correction = -20, + pitch_correction = -25, pivot_h = 0.65, pivot_v = 0.65 }, - logic = wolf_logic, - get_staticdata = mobkit.statfunc, - on_step = animalia.on_step, - on_activate = animalia.on_activate, - on_rightclick = function(self, clicker) - if animalia.feed_tame(self, clicker, math.random(3, 5), true, true) then return end - mob_core.protect(self, clicker, false) - mob_core.nametag(self, clicker, true) - if not self.owner - or clicker:get_player_name() ~= self.owner then return end - if self.order == "wander" then - self.order = "follow" - elseif self.order == "follow" then - self.order = "sit" - else - self.order = "wander" + -- Function + utility_stack = { + [1] = { + utility = "animalia:wander", + get_score = function(self) + return 0.1, {self, true} + end + }, + [2] = { + utility = "animalia:swim_to_land", + get_score = function(self) + if self.in_liquid then + return 0.9, {self} + end + return 0 + end + }, + [3] = { + utility = "animalia:attack", + get_score = function(self) + local target = creatura.get_nearby_entity(self, "animalia:sheep") + local player = self._nearby_player + local is_attacking = self:get_utility() == "animalia:attack" + if player then + if is_value_in_table(self.enemies, player:get_player_name()) then + local nearby_players = creatura.get_nearby_players(self) + local nearby_allies = creatura.get_nearby_entities(self, self.name) + if #nearby_players < #nearby_allies then + target = player + end + end + end + if target then + if is_attacking + and self._utility_data.args[2] == target then + return 0 + end + return 0.85, {self, target} + end + return 0 + end + }, + [4] = { + utility = "animalia:flee_from_player", + get_score = function(self) + local player = self._nearby_player + if player then + if is_value_in_table(self.enemies, player:get_player_name()) then + local nearby_players = creatura.get_nearby_players(self) + local nearby_allies = creatura.get_nearby_entities(self, self.name) + if #nearby_players >= #nearby_allies then + return 0.86, {self, player} + end + end + end + return 0 + end + }, + [5] = { + utility = "animalia:sit", + get_score = function(self) + if self.order == "sit" then + return 0.8, {self} + end + return 0 + end + }, + [6] = { + utility = "animalia:follow_player", + get_score = function(self) + local trust = 0 + local player = self._nearby_player + if self.lasso_origin + and type(self.lasso_origin) == "userdata" then + return 0.7, {self, self.lasso_origin, true} + elseif player + and self:follow_wielded_item(player) then + return 0.7, {self, player} + end + if self.order == "follow" + and self.owner + and minetest.get_player_by_name(self.owner) then + return 1, {self, minetest.get_player_by_name(self.owner), true} + end + return 0 + end + }, + [7] = { + utility = "animalia:mammal_breed", + get_score = function(self) + if self.breeding then + return 0.7, {self} + end + return 0 + end + } + }, + activate_func = function(self) + animalia.initialize_api(self) + animalia.initialize_lasso(self) + self._path = {} + self.order = self:recall("order") or "wander" + self.owner = self:recall("owner") or nil + self.enemies = self:recall("enemies") or {} + if self.owner + and minetest.get_player_by_name(self.owner) then + if not is_value_in_table(animalia.pets[self.owner], self.object) then + table.insert(animalia.pets[self.owner], self.object) + end end - mobkit.remember(self, "order", self.order) + end, + step_func = function(self) + animalia.step_timers(self) + animalia.head_tracking(self, 0.5, 0.75) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + end, + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) + end + end, + on_rightclick = function(self, clicker) + if not clicker:is_player() then return end + local passive = true + if is_value_in_table(self.enemies, clicker:get_player_name()) then + passive = false + end + if animalia.feed(self, clicker, passive, passive) then + return + end + if self.owner + and clicker:get_player_name() == self.owner + and clicker:get_player_control().sneak then + local order = self.order + if order == "wander" then + minetest.chat_send_player(clicker:get_player_name(), "Wolf is following") + self.order = "follow" + self:initiate_utility("animalia:follow_player", self, clicker, true) + self:set_utility_score(1) + elseif order == "follow" then + minetest.chat_send_player(clicker:get_player_name(), "Wolf is sitting") + self.order = "sit" + self:initiate_utility("animalia:sit", self) + self:set_utility_score(0.8) + else + minetest.chat_send_player(clicker:get_player_name(), "Wolf is wandering") + self.order = "wander" + self:set_utility_score(0) + end + self:memorize("order", self.order) + end + animalia.add_libri_page(self, clicker, {name = "wolf", form = "pg_wolf;Wolves"}) end, - on_punch = function(self, puncher, _, tool_capabilities, dir) - mob_core.on_punch_basic(self, puncher, tool_capabilities, dir) - animalia.bh_attack(self, 10, puncher) + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) + if puncher:is_player() then + if self.owner + and puncher:get_player_name() == self.owner then + return + elseif not is_value_in_table(self.enemies, puncher:get_player_name()) then + table.insert(self.enemies, puncher:get_player_name()) + if #self.enemies > 15 then + table.remove(self.enemies, 1) + end + self.enemies = self:memorize("enemies", self.enemies) + else + table.remove(self.enemies, 1) + table.insert(self.enemies, puncher:get_player_name()) + self.enemies = self:memorize("enemies", self.enemies) + end + end + self:initiate_utility("animalia:attack", self, puncher, true) + self:set_utility_score(1) + end, + deactivate_func = function(self) + if self.owner then + for i = 1, #animalia.pets[self.owner] do + if animalia.pets[self.owner][i] == self.object then + animalia.pets[self.owner][i] = nil + end + end + end + if self.enemies + and self.enemies[1] then + self.enemies[1] = nil + self.enemies = self:memorize("enemies", self.enemies) + end end }) -mob_core.register_spawn_egg("animalia:wolf", "a19678" ,"231b13") \ No newline at end of file +creatura.register_spawn_egg("animalia:wolf", "a19678" ,"231b13") \ No newline at end of file diff --git a/mod.conf b/mod.conf index d78d50b..d8bf1ea 100644 --- a/mod.conf +++ b/mod.conf @@ -1,4 +1,4 @@ name = animalia -depends = mobkit, mob_core -optional_depends = default -description = Adds useful Animals for food, ambiance, and more +depends = creatura +optional_depends = default, mcl_player +description = Adds unique and consistantly designed Animals diff --git a/settingtypes.txt b/settingtypes.txt index ced9f1e..be637a3 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -1,17 +1,17 @@ # How many chunks can generate before adding another to spawn queue -chunk_spawn_add_int (Chunk Spawning Addition Interval) float 64 +chunk_spawn_add_int (Chunk Spawning Addition Interval) float 6 # How often (in seconds) the spawn queue is executed and cleared chunk_spawn_queue_int (Chunk Spawning Queue Execution Interval) float 16 -# How often (in seconds) mob spawns are attempted (higher number means less spawning) -animalia_spawn_int (Spawn Interval) float 60 - # If true, mobs will spawn in the wild spawn_mobs (Spawn Mobs) bool true # If true, items from mobs_redo and mobs_animal will be converted to Animalia items -convert_redo_items (Convert Mobs Redo Items) bool false +convert_redo_items(Convert Mobs Redo Items) bool false -# If true, mobs will do a small hop to step up blocks -animalia_fancy_step (Fancy Stepping) bool true \ No newline at end of file +# If true, Guano will accumulate under resting bats +guano_accumulation (Guano Accumulation) bool true + +# If true, Guano can be used as fertilizer +guano_fertilization (Guano Fertilization) bool true \ No newline at end of file