diff --git a/LICENSE b/LICENSE index d17803b..ed8c3a0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2022 ElCeejo - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2022 ElCeejo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/api/api.lua b/api/api.lua index 87d10f8..6415cd1 100644 --- a/api/api.lua +++ b/api/api.lua @@ -1,1262 +1,1281 @@ ---------- --- API -- ---------- - -animalia.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(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 Utilities -- ---------------------- - -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 -end - -local correct_name = animalia.correct_name - ----------------------- --- Global Utilities -- ----------------------- - -function animalia.particle_spawner(pos, texture, type, min_pos, max_pos) - type = type or "float" - min_pos = min_pos or { - x = pos.x - 2, - y = pos.y - 2, - z = pos.z - 2, - } - max_pos = max_pos or { - x = pos.x + 2, - y = pos.y + 2, - z = pos.z + 2, - } - if type == "float" then - minetest.add_particlespawner({ - amount = 16, - time = 0.25, - minpos = min_pos, - maxpos = max_pos, - minvel = {x = 0, y = 0.2, z = 0}, - maxvel = {x = 0, y = 0.25, z = 0}, - minexptime = 0.75, - maxexptime = 1, - minsize = 4, - maxsize = 4, - texture = texture, - glow = 1, - }) - elseif type == "splash" then - minetest.add_particlespawner({ - amount = 6, - 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 = {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, - texture = texture, - }) - end -end - -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 - ----------------------- --- 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 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) - 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 - -------------------- --- Mob Functions -- -------------------- - -local function activate_nametag(self) - self.nametag = self:recall("nametag") or nil - if not self.nametag then return end - self.object:set_properties({ - nametag = self.nametag, - nametag_color = "#FFFFFF" - }) -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 - activate_nametag(self) - 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 - 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 or 30) - 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 - 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 - if #self.child_textures == 1 then - self.texture_no = random(#self.textures) - end - self:set_texture(self.texture_no, self.textures) - end - end - self:memorize("growth_scale", self.growth_scale) - end - end -end - -function animalia.set_nametag(self, clicker) - local item = clicker:get_wielded_item() - if item - and item:get_name() ~= "animalia:nametag" then - return - end - local name = item:get_meta():get_string("name") - if not name - or name == "" then - return - end - self.nametag = self:memorize("nametag", name) - self.despawn_after = self:memorize("despawn_after", nil) - activate_nametag(self) - if not creative then - item:take_item() - clicker:set_wielded_item(item) - end - return true -end - ------------------------ --- Dynamic Animation -- ------------------------ - -local function clamp_bone_rot(n) -- Fixes issues with bones jittering when yaw clamps - if n < -180 then - n = n + 360 - elseif n > 180 then - n = n - 360 - end - if n < -60 then - n = -60 - elseif n > 60 then - n = 60 - end - return n -end - -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) + (deg(pi) * 2)) - elseif a > b then - return ((a + (b - a) * w) - (deg(pi) * 2)) - end - end - return a + (b - a) * w -end - -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(deg(yaw - tyaw)) - local look_pitch = 0 - if pitch then - look_pitch = clamp_bone_rot(deg(pitch)) - end - if tyaw ~= yaw then - look_yaw = look_yaw * 0.66 - end - 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 = 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 - --[[minetest.add_particle({ - pos = pos, - velocity = {x=0, y=0, z=0}, - acceleration = {x=0, y=0, z=0}, - expirationtime = 0.1, - size = 8, - collisiondetection = false, - vertical = false, - texture = "mob_core_green_particle.png", - playername = "singleplayer" - })]] - 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 - self.head_tracking = object - break - end - end - else - 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 = 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 - 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.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_" .. tostring(def.catch_with_lasso or false) .. "_icon.png]", - -- Lasso - "item_image[6,8.15;1,1;animalia:net]", - "image[6.5,8.75;0.5,0.5;animalia_libri_" .. tostring(def.catch_with_net or false) .. "_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 +--------- +-- API -- +--------- + +animalia.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(animalia.walkable_nodes, name) + end + end + end +end) + +-- Math -- + +local abs = math.abs +local atan2 = math.atan2 +local cos = math.cos +local deg = math.deg +local min = math.min +local pi = math.pi +local pi2 = pi * 2 +local rad = math.rad +local random = math.random +local sin = math.sin +local sqrt = math.sqrt + + +local function diff(a, b) -- Get difference between 2 angles + return atan2(sin(b - a), cos(b - a)) +end + +local function interp_angle(a, b, w) + local cs = (1 - w) * cos(a) + w * cos(b) + local sn = (1 - w) * sin(a) + w * sin(b) + return atan2(sn, cs) +end + +local function lerp_step(a, b, dtime, rate) + return min(dtime * rate, abs(diff(a, b)) % (pi2)) +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_dir = vector.direction +local vec_add = vector.add +local vec_sub = vector.subtract +local vec_multi = vector.multiply +local vec_normal = vector.normalize +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") + +------------ +-- Common -- +------------ + +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.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 + +local correct_name = animalia.correct_name + +--------------------- +-- Local Utilities -- +--------------------- + +local function activate_nametag(self) + self.nametag = self:recall("nametag") or nil + if not self.nametag then return end + self.object:set_properties({ + nametag = self.nametag, + nametag_color = "#FFFFFF" + }) +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 + +----------------------- +-- Dynamic Animation -- +----------------------- + +function animalia.rotate_to_pitch(self) + local rot = self.object:get_rotation() + if self._anim == "fly" then + local vel = vec_normal(self.object:get_velocity()) + local step = math.min(self.dtime * 5, abs(diff(rot.x, vel.y)) % (pi2)) + local n_rot = interp_angle(rot.x, vel.y, step) + self.object:set_rotation({ + x = clamp(n_rot, -0.75, 0.75), + y = rot.y, + z = rot.z + }) + elseif rot.x ~= 0 then + self.object:set_rotation({ + x = 0, + y = rot.y, + z = rot.z + }) + end +end + +function animalia.move_head(self, tyaw, pitch) + local data = self.head_data + if not data then return end + local yaw = self.object:get_yaw() + local pitch_offset = data.pitch_correction or 0 + local bone = data.bone or "Head.CTRL" + local _, rot = self.object:get_bone_position(bone) + if not rot then return end + local n_yaw = (tyaw ~= yaw and diff(tyaw, yaw) / 2) or 0 + if abs(deg(n_yaw)) > 45 then n_yaw = 0 end + local dir = yaw2dir(n_yaw) + dir.y = pitch or 0 + local n_pitch = (sqrt(dir.x^2 + dir.y^2) / dir.z) + if abs(deg(n_pitch)) > 45 then n_pitch = 0 end + if self.dtime then + local yaw_w = lerp_step(rad(rot.z), tyaw, self.dtime, 3) + n_yaw = interp_angle(rad(rot.z), n_yaw, yaw_w) + local rad_offset = rad(pitch_offset) + local pitch_w = lerp_step(rad(rot.x), n_pitch + rad_offset, self.dtime, 3) + n_pitch = interp_angle(rad(rot.x), n_pitch + rad_offset, pitch_w) + end + local pitch_max = pitch_offset + 45 + local pitch_min = pitch_offset - 45 + self.object:set_bone_position(bone, data.offset, + {x = clamp(deg(n_pitch), pitch_min, pitch_max), y = 0, z = clamp(deg(n_yaw), -45, 45)}) +end + +function animalia.head_tracking(self) + if not self.head_data then return end + -- Calculate Head Position + local yaw = self.object:get_yaw() + local pos = self.object:get_pos() + if not pos then return end + local y_dir = yaw2dir(yaw) + local offset_h = self.head_data.pivot_h + local offset_v = self.head_data.pivot_v + pos = { + x = pos.x + y_dir.x * offset_h, + y = pos.y + offset_v, + z = pos.z + y_dir.z * offset_h + } + local vel = self.object:get_velocity() + if vec_len(vel) > 2 then + self.head_tracking = nil + animalia.move_head(self, yaw, 0) + return + end + local player = self.head_tracking + local plyr_pos = player and player:get_pos() + if plyr_pos then + plyr_pos.y = plyr_pos.y + 1.4 + local dir = vec_dir(pos, plyr_pos) + local tyaw = dir2yaw(dir) + if abs(diff(yaw, tyaw)) > pi / 10 + and self._anim == "stand" then + self:turn_to(tyaw, 1) + end + animalia.move_head(self, tyaw, dir.y) + return + elseif self:timer(random(6, 12)) then + local players = creatura.get_nearby_players(self, 6) + self.head_tracking = #players > 0 and players[random(#players)] + end + animalia.move_head(self, yaw, 0) + +end + +--------------- +-- Utilities -- +--------------- + +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 + +------------------------ +-- Environment Access -- +------------------------ + +function animalia.set_nametag(self, clicker) + local item = clicker:get_wielded_item() + if item + and item:get_name() ~= "animalia:nametag" then + return + end + local name = item:get_meta():get_string("name") + if not name + or name == "" then + return + end + self.nametag = self:memorize("nametag", name) + self.despawn_after = self:memorize("despawn_after", nil) + activate_nametag(self) + if not creative then + item:take_item() + clicker:set_wielded_item(item) + end + return true +end + +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 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) + 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.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 + +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 + +--------------- +-- Particles -- +--------------- + +function animalia.particle_spawner(pos, texture, type, min_pos, max_pos) + type = type or "float" + min_pos = min_pos or { + x = pos.x - 2, + y = pos.y - 2, + z = pos.z - 2, + } + max_pos = max_pos or { + x = pos.x + 2, + y = pos.y + 2, + z = pos.z + 2, + } + if type == "float" then + minetest.add_particlespawner({ + amount = 16, + time = 0.25, + minpos = min_pos, + maxpos = max_pos, + minvel = {x = 0, y = 0.2, z = 0}, + maxvel = {x = 0, y = 0.25, z = 0}, + minexptime = 0.75, + maxexptime = 1, + minsize = 4, + maxsize = 4, + texture = texture, + glow = 1, + }) + elseif type == "splash" then + minetest.add_particlespawner({ + amount = 6, + 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 = {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, + texture = texture, + }) + end +end + +-------------- +-- Entities -- +-------------- + +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)]) + -- Reset Texture ID + self.texture_no = nil + 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 + activate_nametag(self) + 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) + local child_textures = self.growth_scale < 0.8 and self.child_textures + local textures = (not child_textures and self[self.gender .. "_textures"]) or self.textures + if child_textures then + if not self.texture_no + or self.texture_no > #child_textures then + self.texture_no = random(#child_textures) + end + self:set_texture(self.texture_no, child_textures) + elseif textures then + if not self.texture_no then + self.texture_no = random(#textures) + end + self:set_texture(self.texture_no, textures) + return + end +end + +function animalia.step_timers(self) + local breed_cd = self.breeding_cooldown or 30 + local trust_cd = self.trust_cooldown or 0 + self.breeding_cooldown = (breed_cd > 0 and breed_cd - self.dtime) or 0 + self.trust_cooldown = (trust_cd > 0 and trust_cd - self.dtime) or 0 + if self.breeding + and self.breeding_cooldown <= 30 then + self.breeding = false + end + self:memorize("breeding_cooldown", self.breeding_cooldown) + self:memorize("trust_cooldown", self.trust_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 + 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 + if #self.child_textures == 1 then + self.texture_no = random(#self.textures) + end + self:set_texture(self.texture_no, self.textures) + end + end + self:memorize("growth_scale", self.growth_scale) + end + end +end + +function animalia.add_trust(self, player, amount) + self.trust_cooldown = 60 + local plyr_name = player:get_player_name() + local trust = self.trust[plyr_name] or 0 + if trust > 4 then return end + self.trust[plyr_name] = trust + (amount or 1) + self:memorize("trust", self.trust) +end + +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 + +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 = 25, z = 0}, {x = 0, y = 15, z = 15}) +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 form = {} + for i = 1, #list do + local vert_multi = math.floor((i - 1) / max_horiz) + local horz_multi = (total_scale * max_horiz) * vert_multi + local pos_x = offset_x + ((total_scale * i) - horz_multi) + local pos_y = offset_y + (total_scale * vert_multi ) + table.insert(form, "item_image[" .. pos_x .. "," .. pos_y .. ";" .. size .. "," .. size .. ";" .. list[i] .. "]") + end + return table.concat(form, "") +end + +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 + str = minetest.colorize("#383329", str .. "\n") + table.insert(info_text, "label[" .. offset_x + center_offset .. "," .. offset_y + i * 0.25 .. ";" .. str .. "]") + 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_groups = animalia.registered_biome_groups + local biome_group = spawn_biomes[mob_name] + local spawn_biome = 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.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_" .. tostring(def.catch_with_lasso or false) .. "_icon.png]", + -- Lasso + "item_image[6,8.15;1,1;animalia:net]", + "image[6.5,8.75;0.5,0.5;animalia_libri_" .. tostring(def.catch_with_net or false) .. "_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 wild_biomes = animalia.registered_biome_groups[spawn_biomes[mob_name]].biomes + if biome_idx >= #wild_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_" .. mob_name:split(":")[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 = animalia.animals[i]:split(":")[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 index 026f789..24bf1bb 100644 --- a/api/behaviors.lua +++ b/api/behaviors.lua @@ -1,1627 +1,1079 @@ ---------------- --- Behaviors -- ---------------- - --- Math -- - -local abs = math.abs -local random = math.random -local ceil = math.ceil -local floor = math.floor -local sin = math.sin -local cos = math.cos -local rad = math.rad - -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_center(v) - return {x = floor(v.x + 0.5), y = floor(v.y + 0.5), z = floor(v.z + 0.5)} -end - -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 - ---------------------- --- Local Utilities -- ---------------------- - -local moveable = creatura.is_pos_moveable -local fast_ray_sight = creatura.fast_ray_sight -local get_node_def = creatura.get_node_def - -local get_ground_level = creatura.get_ground_level - -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 creatura.get_node_def(i_pos).walkable then - table.insert(output, i_pos) - end - end - return output -end - -local function get_collision(self, yaw) - local width = self.width - local height = self.height - local pos = self.object:get_pos() - if not pos then return end - pos.y = pos.y + 1 - local pos2 = vec_add(pos, vec_multi(yaw2dir(yaw), width + 5)) - for x = -width, width, width / ceil(width) do - for y = 0, height, height / ceil(height) do - local vec1 = { - x = cos(yaw) * ((pos.x + x) - pos.x) + pos.x, - y = pos.y + y, - z = sin(yaw) * ((pos.x + x) - pos.x) + pos.z - } - local vec2 = { - x = cos(yaw) * ((pos2.x + x) - pos2.x) + pos2.x, - y = vec1.y, - z = sin(yaw) * ((pos2.x + x) - pos2.x) + pos2.z - } - local ray = minetest.raycast(vec1, vec2, false, true) - for pointed_thing in ray do - if pointed_thing - and pointed_thing.type == "node" then - return true, pointed_thing.intersection_point - end - end - end - end - return false -end - -local function get_obstacle_avoidance(self, goal) - local width = self.width - local height = self.height - local pos = self.object:get_pos() - if not pos then return end - pos.y = pos.y + 1 - local yaw2goal = dir2yaw(vec_dir(pos, goal)) - local collide, col_pos = get_collision(self, yaw2goal) - if not collide then return end - local avd_pos - for i = 45, 180, 45 do - local angle = rad(i) - local dir = vec_multi(yaw2dir(yaw2goal + angle), width) - avd_pos = vec_center(vec_add(pos, dir)) - if not get_collision(self, yaw2goal) then - break - end - angle = -rad(i) - dir = vec_multi(yaw2dir(yaw2goal + angle), width) - avd_pos = vec_center(vec_add(pos, dir)) - if not get_collision(self, yaw2goal) then - break - end - end - if col_pos.y - (pos.y + height * 0.5) > 1 then - avd_pos.y = avd_pos.y - 3 - elseif (pos.y + height * 0.5) - col_pos.y > 1 then - avd_pos.y = avd_pos.y + 3 - end - return avd_pos -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 -- ----------------------- - --- Flying -- - -creatura.register_movement_method("animalia:fly_obstacle_avoidance", function(self, goal) - local waypoint - local tick = 0.15 - local box = clamp(self.width, 0.5, ceil(self.width)) - local function func(self) - local pos = self.object:get_pos() - if not pos then return end - -- Return true when goal is reached - if self:pos_in_box(goal, box) then - self:halt() - return true - end - self:animate("fly") - tick = tick - self.dtime - if tick <= 0 then - if not waypoint - or self:pos_in_box(waypoint, box) then - waypoint = get_obstacle_avoidance(self, goal) - end - tick = 0.15 - end - -- Get movement direction - local goal_dir = vec_dir(pos, goal) - if waypoint then - goal_dir = vec_dir(pos, waypoint) - end - local yaw = self.object:get_yaw() - local goal_yaw = dir2yaw(goal_dir) - if abs(yaw - goal_yaw) > 0.1 then - self:turn_to(goal_yaw, self.turn_rate or 6) - end - -- Set Velocity - self:set_forward_velocity(self.speed or 2) - self:set_vertical_velocity((self.speed or 2) * goal_dir.y) - end - return func -end) - -creatura.register_movement_method("animalia:fly_pathfind", function(self, goal) - local waypoint - local tick = 0.15 - local box = clamp(self.width, 0.5, ceil(self.width)) - local function func(self) - local pos = self.object:get_pos() - if not pos then return end - -- Return true when goal is reached - if self:pos_in_box(goal, box) then - self:halt() - return true - end - self:animate("fly") - tick = tick - self.dtime - if tick <= 0 then - if not waypoint - or self:pos_in_box(waypoint, box) then - waypoint = get_obstacle_avoidance(self, goal) - end - tick = 0.15 - end - -- Get movement direction - local goal_dir = vec_dir(pos, goal) - if waypoint then - -- There's an obstruction, time to find a path - if #path < 1 then - path = creatura.find_theta_path(self, pos, goal, self.width, self.height, 300, false, true) or {} - else - waypoint = path[2] or path[1] - end - goal_dir = vec_dir(pos, waypoint) - end - local yaw = self.object:get_yaw() - local goal_yaw = dir2yaw(goal_dir) - if abs(yaw - goal_yaw) > 0.1 then - self:turn_to(goal_yaw, self.turn_rate or 6) - end - -- Set Velocity - self:set_forward_velocity(self.speed or 2) - self:set_vertical_velocity((self.speed or 2) * goal_dir.y) - end - return func -end) - --- Swimming -- - -creatura.register_movement_method("animalia:swim_obstacle_avoidance", function(self, goal) - local waypoint - local tick = 0.15 - local box = clamp(self.width, 0.5, ceil(self.width)) - local function func(self) - if self.in_liquid then - self:set_gravity(-9.8) - return true - end - local pos = self.object:get_pos() - if not pos then return end - -- Return true when goal is reached - if vec_dist(pos, goal) < self.width * 1.33 then - self:halt() - return true - end - self:animate("swim") - tick = tick - self.dtime - if tick <= 0 then - if not waypoint - or vec_dist(pos, waypoint) < self.width * 1.33 then - waypoint = get_obstacle_avoidance(self, goal) - end - tick = 0.15 - end - -- Get movement direction - local goal_dir = vec_dir(pos, goal) - if waypoint then - goal_dir = vec_dir(pos, waypoint) - end - local yaw = self.object:get_yaw() - local goal_yaw = dir2yaw(goal_dir) - if abs(yaw - goal_yaw) > 0.1 then - self:turn_to(goal_yaw, self.turn_rate or 6) - end - -- Set Velocity - self:set_forward_velocity(self.speed or 2) - self:set_vertical_velocity((self.speed or 2) * goal_dir.y) - end - return func -end) - -------------- --- 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, speed_factor, anim) - 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, goal) - local yaw2goal = minetest.dir_to_yaw(dir2goal) - boid_angle = boid_angle + (yaw2goal - boid_angle) * 0.25 - 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.25 - else - boid_dir.y = vec_dir(pos, goal).y - end - boid_dir = vector.normalize(boid_dir) - goal = vec_add(pos, vec_multi(boid_dir, vec_dist(pos, goal))) - end - end - if timer <= 0 - or self:move_to(goal, method or "animalia:fly_obstacle_avoidance", speed_factor or 1) then - self:halt() - return true - end - self:animate(anim or "fly") - end - self:set_action(func) -end - -function animalia.action_boid_walk(self, pos2, timeout, method, speed_factor, anim) - local boids = creatura.get_boid_members(self.object:get_pos(), 12, self.name) - 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 = 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 timer <= 0 - or not self:is_pos_safe(pos2) - or self:move_to(pos2, method or "creatura:obstacle_avoidance", speed_factor or 1) then - self:halt() - return true - end - self:animate(anim or "walk") - - 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 - -function animalia.action_horse_spin(self, speed, anim) - local tyaw = random(math.pi * 2) - local function func(self) - self:set_gravity(-9.8) - self:halt() - self:animate(anim or "stand") - self:turn_to(tyaw, speed) - if abs(tyaw - self.object:get_yaw()) < 0.1 then - return true - end - end - self:set_action(func) -end - -function animalia.action_pursue(self, target, timeout, method, speed_factor, anim) - local timer = timeout or 4 - local goal - local function func(self) - local target_alive, line_of_sight, tgt_pos = self:get_target(target) - if not target_alive then - return true - end - local pos = self.object:get_pos() - if not pos then return end - timer = timer - self.dtime - if timer <= 0 then return true end - if not goal - or (line_of_sight - and vec_dist(goal, tgt_pos) > 3) then - goal = tgt_pos - end - goal.y = creatura.get_ground_level(pos, 3).y - if timer <= 0 - or self:move_to(goal, method or "creatura:obstacle_avoidance", speed_factor or 0.5) then - self:halt() - return true - end - self:animate(anim or "walk") - end - self:set_action(func) -end - - ------------------------- --- Register Utilities -- ------------------------- - --- Wander - -creatura.register_utility("animalia:wander", function(self, group) - local idle_time = 3 - local move_probability = 5 - local far_from_group = false - local group_tick = 1 - local function func(self) - local pos = self.object:get_pos() - if not pos then return end - if not self:get_action() then - local goal - local move = random(move_probability) < 2 - if self.lasso_pos - and vec_dist(pos, self.lasso_pos) > 10 then - goal = self.lasso_pos - end - if not goal - and move then - goal = self:get_wander_pos(1, 2) - end - if group - and goal - and group_tick > 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(pos, center) > range * 0.33 - or vec_dist(goal, center) > range * 0.33 then - goal = center - far_from_group = true - else - far_from_group = false - end - end - group_tick = 0 - end - if (move - and goal) - or far_from_group then - creatura.action_walk(self, goal, 2, "creatura:obstacle_avoidance") - else - creatura.action_idle(self, idle_time) - end - if group then - group_tick = group_tick + 1 - end - end - end - self:set_utility(func) -end) - -creatura.register_utility("animalia:skittish_wander", function(self) - local idle_time = 3 - local move_probability = 3 - local force_move = false - local avoid_tick = 1 - local function func(self) - local pos = self.object:get_pos() - if not pos then return end - if not self:get_action() then - local goal - local move = random(move_probability) < 2 - if avoid_tick > 3 - and move then - local range = self.tracking_range * 0.5 - 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(player_pos, pos) - goal = self:get_wander_pos(2, 3, dir) - end - end - avoid_tick = 0 - end - if self.lasso_pos - and vec_dist(pos, self.lasso_pos) > 10 then - goal = self.lasso_pos - end - if not goal - and move then - goal = self:get_wander_pos(4, 4) - end - if move - and goal then - creatura.action_walk(self, goal, 3, "creatura:obstacle_avoidance", 0.35) - else - creatura.action_idle(self, idle_time) - end - avoid_tick = avoid_tick + 1 - 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() - if not pos then return end - local goal - 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(pos, center) > range then - 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(player_pos, pos) - goal = self:get_wander_pos(2, 3, dir) - end - end - end - if not self:get_action() then - local move = random(move_probability) < 2 - if self.lasso_pos - and vec_dist(pos, self.lasso_pos) > 10 then - goal = self.lasso_pos - end - if not goal - and move then - goal = self:get_wander_pos(4, 4) - end - if move - and goal then - animalia.action_boid_walk(self, goal, 6, "creatura:obstacle_avoidance", 0.35) - 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 far_from_group = false - local group_tick = 1 - local function func(self) - local pos = self.object:get_pos() - if not pos then return end - if not self:get_action() then - local goal - local move = random(move_probability) < 2 - if self.lasso_pos - and vec_dist(pos, self.lasso_pos) > 10 then - goal = self.lasso_pos - end - if not goal - and move then - goal = self:get_wander_pos(1, 2) - end - if group - and goal - and group_tick > 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(pos, center) > range * 0.33 - or vec_dist(goal, center) > range * 0.33 then - goal = center - far_from_group = true - else - far_from_group = false - end - end - group_tick = 0 - end - if (move - or far_from_group) - and goal then - animalia.action_boid_walk(self, goal, 2, "creatura:obstacle_avoidance", 0.35) - else - creatura.action_idle(self, idle_time) - end - group_tick = group_tick + 1 - 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() - if not pos then return end - local random_goal = self:get_wander_pos_3d(1, 3) - 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 action_init = false - local function func(self) - local pos = self.object:get_pos() - if not pos then return end - local look_dir = yaw2dir(self.object:get_yaw()) - local under = vec_add(pos, vec_multi(look_dir, self.width)) - under.y = pos.y - 0.5 - if not action_init then - for i, 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) - if not self:get_action() then - creatura.action_idle(self, 1, "eat") - action_init = true - end - break - elseif i == #self.consumable_nodes then - return true - end - end - elseif not self:get_action() then - return true - 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() - if not pos then return end - 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() - if not pos then return end - 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() - if not pos then return end - 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() - if not pos then return end - 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 - if escape_pos then - animalia.action_boid_walk(self, escape_pos, 6, "creatura:obstacle_avoidance", 1) - end - 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() - if not pos then return end - 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() - if not pos then return end - 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 - animalia.action_pursue(self, player, 6, "creatura:pathfind", 1, "walk") - 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() - if not pos then return end - 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() - if not pos then return end - 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() - if not pos then return end - 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() - if not pos then return end - 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() - if not pos then return end - 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 = get_boid_members(self.object:get_pos(), 6, self.name, self.texture_no) - 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() - if not pos then return end - local pos2 = self:get_wander_pos_3d(1, 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, "animalia:fly_obstacle_avoidance", 1) - 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() - if not pos then return end - 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, "animalia:fly_obstacle_avoidance", 1) - end - end - self:set_utility(func) -end) - -creatura.register_utility("animalia:land", function(self, scale) - scale = scale or 1 - local function func(self) - if self.touching_ground then return true end - local _, node = creatura.sensor_floor(self, 3, true) - if node and get_node_def(node.name).drawtype == "liquid" then self.is_landed = false return true end - if not self:get_action() then - local pos = self.object:get_pos() - if not pos then return end - local offset = random(2 * scale, 3 * scale) - if random(2) < 2 then - offset = offset * -1 - end - local pos2 = { - x = pos.x + offset, - y = pos.y, - z = pos.z + offset - } - pos2.y = pos2.y - (3 * scale) - self:animate("fly") - animalia.action_boid_move(self, pos2, 2, "creatura:fly_path", 1) - end - end - self:set_utility(func) -end) - -creatura.register_utility("animalia:return_to_nest", function(self) - local function func(self) - if not self.home_position then return true end - local pos = self.object:get_pos() - if not pos then return end - local pos2 = self.home_position - local dist = vec_dist(pos, {x = pos2.x, y = pos.y, z = pos2.z}) - if dist < 4 - and abs(pos.y - pos2.y) < 2 then - if self.touching_ground then - creatura.action_idle(self, 1) - end - end - if not self:get_action() then - creatura.action_walk(self, pos2, 6, "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() - if not pos then return end - 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", 1, "swim") - 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() - if not pos then return end - 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() - if not pos then return end - 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() - if not pos then return end - 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() - if not pos then return end - local pos2 = self.home_position - local dist = vec_dist(pos, pos2) - if dist < 2 then - if creatura.get_node_def(vec_raise(pos, 1)).walkable then - creatura.action_idle(self, 1, "latch") - self:set_gravity(9.8) - self.object:set_velocity({x = 0, y = 0, z = 0}) - end - end - if not self:get_action() then - creatura.action_walk(self, vec_raise(pos2, -1), 6, "animalia:fly_path", 1) - 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() - if not pos then return end - 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 = self:get_wander_pos_3d(1, 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, "animalia:fly_obstacle_avoidance", 1) - 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) - --- Horse Exclusive Behaviors - -creatura.register_utility("animalia:horse_breaking", function(self) - local timer = 18 - self:clear_action() - local function func(self) - if not self:get_action() then - animalia.action_horse_spin(self, random(4, 6), "stand") - end - timer = timer - self.dtime - if timer <= 0 then - return true - 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 control.aux1 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) +--------------- +-- Behaviors -- +--------------- + +-- Math -- + +local abs = math.abs +local atan2 = math.atan2 +local cos = math.cos +local floor = math.floor +local pi = math.pi +local sin = math.sin +local rad = math.rad +local random = math.random + +local function interp_rad(a, b, w) + local cs = (1 - w) * cos(a) + w * cos(b) + local sn = (1 - w) * sin(a) + w * sin(b) + return atan2(sn, cs) +end + +local function diff(a, b) -- Get difference between 2 angles + return atan2(sin(b - a), cos(b - a)) +end + +local function clamp(val, min, max) + if val < min then + val = min + elseif max < val then + val = max + end + return val +end + +-- Vector -- + +local vec_dir = vector.direction +local vec_dist = vector.distance +local vec_len = vector.length +local vec_normal = vector.normalize +local vec_round = vector.round +local vec_sub = vector.subtract +local vec_add = vector.add +local vec_multi = vector.multiply + +local dir2yaw = minetest.dir_to_yaw +local yaw2dir = minetest.yaw_to_dir + +----------------- +-- Local Tools -- +----------------- + +local get_collision = creatura.get_collision + +local function get_avoidance_dir(self) + local pos = self.object:get_pos() + if not pos then return end + local collide, col_pos = get_collision(self) + if collide then + local vel = self.object:get_velocity() + local ahead = vec_add(pos, vec_normal(self.object:get_velocity())) + local avoidance_force = vector.subtract(ahead, col_pos) + avoidance_force.y = 0 + local vel_len = vec_len(vel) + avoidance_force = vec_multi(vec_normal(avoidance_force), (vel_len > 1 and vel_len) or 1) + return vec_dir(pos, vec_add(ahead, avoidance_force)) + end +end + +local function add_break_particle(pos) + pos = vec_round(pos) + local def = creatura.get_node_def(pos) + local texture = (def.tiles and def.tiles[1]) or def.inventory_image + texture = texture .. "^[resize:8x8" + minetest.add_particlespawner({ + amount = 6, + time = 0.1, + minpos = { + x = pos.x, + y = pos.y - 0.49, + z = pos.z + }, + maxpos = { + x = pos.x, + y = pos.y - 0.49, + z = pos.z + }, + 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.5, + minsize = 1, + maxsize = 2, + collisiondetection = true, + vertical = false, + texture = texture, + }) +end + +local function get_group_positions(self) + local objects = creatura.get_nearby_objects(self, self.name) + local group = {} + for _, object in ipairs(objects) do + local obj_pos = object and object:get_pos() + if obj_pos then table.insert(group, obj_pos) end + end + return group +end + +-------------- +-- Movement -- +-------------- + +creatura.register_movement_method("animalia:fly_simple", function(self) + local box = clamp(self.width, 0.5, 1.5) + local steer_to + local steer_timer = 0.25 + self:set_gravity(0) + local function func(_self, goal, speed_factor) + local pos = _self.object:get_pos() + if not pos then return end + -- Return true when goal is reached + if vec_dist(pos, goal) < box * 1.33 then + _self:halt() + return true + end + steer_timer = steer_timer - self.dtime + if steer_timer <= 0 then + steer_to = get_avoidance_dir(_self) + end + -- Get movement direction + local goal_dir = vec_dir(pos, goal) + if steer_to then + steer_to.y = goal_dir.y + goal_dir = steer_to + end + local yaw = _self.object:get_yaw() + local goal_yaw = dir2yaw(goal_dir) + local speed = abs(_self.speed or 2) * speed_factor or 0.5 + local turn_rate = abs(_self.turn_rate or 5) + -- Movement + local yaw_diff = abs(diff(yaw, goal_yaw)) + if yaw_diff < pi * 0.25 + or steer_to then + _self:set_forward_velocity(speed) + else + _self:set_forward_velocity(speed * 0.33) + end + self:set_vertical_velocity(speed * goal_dir.y) + _self:turn_to(goal_yaw, turn_rate) + if _self.touching_ground + or _self.in_liquid then + _self.object:add_velocity({x = 0, y = 2, z = 0}) + end + end + return func +end) + +creatura.register_movement_method("animalia:swim_simple", function(self) + local box = clamp(self.width, 0.5, 1.5) + local steer_to + local steer_timer = 0.25 + self:set_gravity(0) + local function func(_self, goal, speed_factor) + local pos = _self.object:get_pos() + if not pos then return end + -- Return true when goal is reached + if vec_dist(pos, goal) < box * 1.33 then + _self:halt() + return true + end + steer_timer = steer_timer - self.dtime + if steer_timer <= 0 then + steer_to = get_avoidance_dir(_self) + end + -- Get movement direction + local goal_dir = vec_dir(pos, goal) + if steer_to then + steer_to.y = goal_dir.y + goal_dir = steer_to + end + local yaw = _self.object:get_yaw() + local goal_yaw = dir2yaw(goal_dir) + local speed = abs(_self.speed or 2) * speed_factor or 0.5 + local turn_rate = abs(_self.turn_rate or 5) + -- Movement + local yaw_diff = abs(diff(yaw, goal_yaw)) + if yaw_diff < pi * 0.25 + or steer_to then + _self:set_forward_velocity(speed) + else + _self:set_forward_velocity(speed * 0.33) + end + self:set_vertical_velocity(speed * goal_dir.y) + _self:turn_to(goal_yaw, turn_rate) + end + return func +end) + +------------- +-- Actions -- +------------- + +function animalia.action_pursue(self, target, timeout, method, speed_factor, anim) + local timer = timeout or 4 + local goal + local function func(_self) + local target_alive, line_of_sight, tgt_pos = self:get_target(target) + if not target_alive then + return true + end + goal = goal or tgt_pos + timer = timer - _self.dtime + self:animate(anim or "walk") + local safe = true + if _self.max_fall + and _self.max_fall > 0 then + local pos = self.object:get_pos() + if not pos then return end + safe = _self:is_pos_safe(goal) + end + if line_of_sight + and vec_dist(goal, tgt_pos) > 3 then + goal = tgt_pos + end + if timer <= 0 + or not safe + or _self:move_to(goal, method or "creatura:obstacle_avoidance", speed_factor or 0.5) then + return true + end + end + self:set_action(func) +end + +function animalia.action_move_flock(self, pos2, timeout, method, speed_factor, anim) + local old_boids = (self._movement_data and self._movement_data.boids) or {} + local boids = (#old_boids > 2 and old_boids) or creatura.get_boid_members(self.object:get_pos(), 12, self.name) + local timer = timeout or 4 + local boid_pos2 + local function func(_self) + local pos = self.object:get_pos() + if not pos then return end + -- Tick down timer + timer = timer - _self.dtime + -- Check if goal is safe + local safe = true + local max_fall = (_self.max_fall or 0) > 0 and _self.max_fall + if max_fall then + safe = _self:is_pos_safe(pos2) + end + -- Boid calculation + if #boids > 2 then + local boid_yaw, boid_pitch = creatura.get_boid_angle(self, boids, 12) + if boid_yaw then + local dir2pos = vec_dir(pos, pos2) + local yaw2pos = minetest.dir_to_yaw(dir2pos) + boid_yaw = interp_rad(boid_yaw, yaw2pos, 0.3) + local boid_dir = minetest.yaw_to_dir(boid_yaw) + boid_dir.y = boid_pitch + boid_pos2 = vec_add(pos, vec_multi(boid_dir, 4)) + if max_fall then + boid_pos2 = creatura.get_ground_level(boid_pos2, 2) + end + end + end + -- Main movement + if timer <= 0 + or not safe + or _self:move_to(boid_pos2 or pos2, method or "creatura:obstacle_avoidance", speed_factor or 0.5) then + return true + end + self:animate(anim or "walk") + end + self:set_action(func) +end + +function animalia.action_float(self, time, anim) + local timer = time + local function func(_self) + _self:set_gravity(-0.14) + _self:halt() + _self:animate(anim or "foat") + timer = timer - _self.dtime + if timer <= 0 then + return true + end + end + self:set_action(func) +end + +function animalia.action_cling(self, time) + local timer = time + local function func(_self) + _self:set_gravity(0) + _self:halt() + _self:set_vertical_velocity(1) + _self:set_forward_velocity(0) + _self:animate("cling") + timer = timer - _self.dtime + if timer <= 0 then + return true + end + end + self:set_action(func) +end + +function animalia.action_punch(self, target) + local jump_init = false + local function func(_self) + local tgt_alive, _, tgt_pos = _self:get_target(target) + if not tgt_alive then return true end + local pos = _self.object:get_pos() + if not pos then return end + local dir = vec_dir(pos, tgt_pos) + if not jump_init then + local vel = { + x = dir.x * 3, + y = 3, + z = dir.z * 3 + } + _self.object:add_velocity(vel) + jump_init = true + elseif _self.touching_ground then + return true + end + local dist = vec_dist(pos, tgt_pos) + if dist < _self.width + 1 then + _self:punch_target(target) + return true + end + end + self:set_action(func) +end + +function animalia.action_punch_aoe(self, target) + local punch_init = false + local anim = self.animations["punch_aoe"] + local anim_len = (anim.range.y - anim.range.x) / anim.speed + local timeout = anim_len + local function func(_self) + local tgt_alive, _, tgt_pos = _self:get_target(target) + if not tgt_alive then return true end + local pos = _self.object:get_pos() + if not pos then return end + _self:halt() + _self:animate("punch_aoe") + local dist = vec_dist(pos, tgt_pos) + timeout = timeout - _self.dtime + if not punch_init + and dist < _self.width + 1 + and timeout < anim_len * 0.5 then + _self:punch_target(target) + punch_init = true + end + if timeout <= 0 then _self:animate("stand") return true end + end + self:set_action(func) +end + +--------------- +-- Utilities -- +--------------- + +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() + if not pos then return end + 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) + +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() + if not pos then return end + 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) + +-- Wandering + +creatura.register_utility("animalia:wander", function(self) + local move_chance = 5 + local idle_duration = 4 + local center = self.object:get_pos() + if not center then return end + local move = self.wander_action or creatura.action_move + local function func(_self) + if not _self:get_action() then + local pos2 = _self:get_wander_pos(2, 3) + if random(move_chance) < 2 + and vec_dist(pos2, center) < _self.tracking_range * 0.5 then + move(_self, pos2, 2) + else + creatura.action_idle(_self, random(idle_duration), "stand") + end + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:wander_group", function(self) + local move_chance = 3 + local idle_duration = 3 + local center = self.object:get_pos() + if not center then return end + local group_tick = 500 + local move = self.wander_action or animalia.action_move_flock + local function func(_self) + group_tick = group_tick - 1 + if group_tick <= 0 then + local pos = _self.object:get_pos() + if not pos then return end + local grp_pos = get_group_positions(_self) + center = animalia.get_average_pos(grp_pos) or pos + group_tick = 500 + end + if not _self:get_action() then + local pos2 = _self:get_wander_pos(2, 3) + if random(move_chance) < 2 + and vec_dist(pos2, center) < _self.tracking_range * 0.5 then + move(_self, pos2, 2) + else + creatura.action_idle(_self, random(idle_duration)) + end + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:wander_skittish", function(self) + local move_chance = 3 + local idle_duration = 3 + local center = self.object:get_pos() + if not center then return end + local plyr_tick = 500 + local move_dir + local move = self.wander_action or creatura.action_move + local function func(_self) + plyr_tick = plyr_tick - 1 + if plyr_tick <= 0 then + local pos = _self.object:get_pos() + if not pos then return true end + local plyr = creatura.get_nearby_player(_self) + local plyr_alive, los, plyr_pos = _self:get_target(plyr) + if plyr_alive + and los then + move_dir = vec_dir(plyr_pos, pos) + end + plyr_tick = 500 + end + if not _self:get_action() then + local pos2 = _self:get_wander_pos(2, 3, move_dir) + if random(move_chance) < 2 + and vec_dist(pos2, center) < _self.tracking_range * 0.5 then + move(_self, pos2, 2) + move_dir = nil + else + creatura.action_idle(_self, random(idle_duration)) + end + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:aerial_wander", function(self) + local center = self.object:get_pos() + if not center then return end + local height_tick = 0 + local function func(_self) + local pos = self.object:get_pos() + if not pos then return end + height_tick = height_tick - 1 + if height_tick <= 0 then + local dist2floor = creatura.sensor_floor(self, 2, true) + center.y = center.y + (2 - dist2floor) + height_tick = 30 + end + if not _self:get_action() then + local move_dir = (vec_dist(pos, center) > 8 and vec_dir(pos, center)) or nil + local pos2 = _self:get_wander_pos_3d(2, 5, move_dir) + animalia.action_move_flock(_self, pos2, 3, "animalia:fly_simple", 1, "fly") + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:fly_to_roost", function(self) + local home = self.home_position + local roost = self.roost_action or creatura.action_idle + local function func(_self) + local pos = self.object:get_pos() + if not pos then return end + if not home then return true end + if not _self:get_action() then + if abs(pos.x - home.x) < 0.5 + and abs(pos.y - home.y) < 0.7 + and abs(pos.z - home.z) < 0.5 then + roost(_self, 1, "stand") + return + end + creatura.action_move(_self, home, 3, "animalia:fly_simple", 1, "fly") + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:fly_to_land", function(self) + local landed = false + local function func(_self) + if not _self:get_action() then + if landed then return true end + if _self.touching_ground then + creatura.action_idle(_self, 0.5, "stand") + landed = true + else + local pos2 = _self:get_wander_pos_3d(3, 6) + if pos2 then + local dist2floor = creatura.sensor_floor(_self, 10, true) + pos2.y = pos2.y - dist2floor + creatura.action_move(_self, pos2, 3, "animalia:fly_simple", 0.6, "fly") + end + end + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:aquatic_wander_school", function(self) + local center = self.object:get_pos() + if not center then return end + local center_tick = 0 + local water_nodes = minetest.find_nodes_in_area(vec_sub(center, 4), vec_add(center, 4), {"group:water"}) + local function func(_self) + if #water_nodes < 1 then return true end + if #water_nodes < 10 then + center_tick = center_tick - 1 + if center_tick <= 0 then + center_tick = 30 + end + center = self.object:get_pos() + if not center then return end + water_nodes = minetest.find_nodes_in_area(vec_sub(center, 4), vec_add(center, 4), {"group:water"}) + end + if not _self:get_action() then + animalia.action_move_flock(_self, water_nodes[random(#water_nodes)], 3, "animalia:swim_simple", 1, "swim") + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:aquatic_wander", function(self) + local center = self.object:get_pos() + if not center then return end + local center_tick = 0 + local move_chance = 3 + local idle_duration = 3 + local water_nodes = minetest.find_nodes_in_area(vec_sub(center, 4), vec_add(center, 4), {"group:water"}) + local function func(_self) + if #water_nodes < 1 then return true end + if #water_nodes < 10 then + center_tick = center_tick - 1 + if center_tick <= 0 then + center_tick = 30 + end + center = self.object:get_pos() + if not center then return end + water_nodes = minetest.find_nodes_in_area(vec_sub(center, 4), vec_add(center, 4), {"group:water"}) + end + if not _self:get_action() then + if random(move_chance) < 2 then + creatura.action_move(_self, water_nodes[random(#water_nodes)], 3, "animalia:swim_simple", 0.5, "swim") + else + animalia.action_float(_self, random(idle_duration), "float") + end + end + end + self:set_utility(func) +end) + +-- Environment Interaction + +creatura.register_utility("animalia:eat_turf", function(self) + local action_init = false + local function func(_self) + local pos = _self.object:get_pos() + if not pos then return end + local yaw = _self.object:get_yaw() + local dir = vec_normal(yaw2dir(yaw)) + local turf_pos = { + x = pos.x + dir.x * _self.width, + y = pos.y - 0.5, + z = pos.z + dir.z * _self.width + } + if not _self:get_action() then + if action_init then return true end + for name, sub_name in pairs(_self.consumable_nodes) do + if minetest.get_node(turf_pos).name == name then + add_break_particle(turf_pos) + minetest.set_node(turf_pos, {name = sub_name}) + _self.collected = _self:memorize("collected", false) + creatura.action_idle(_self, 1, "eat") + action_init = true + break + end + end + if not action_init then return true end + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:eat_bug", function(self, bug) + local timer = 0.2 + local action_init = false + local function func(_self) + local pos = _self.object:get_pos() + if not pos then return end + if not bug then return true end + local dist = vec_dist(pos, bug) + local dir = vec_dir(pos, bug) + local frame = floor(dist * 10) + if not _self:get_action() then + if dist > 1 then + local pos2 = vec_add(bug, vec_multi(vec_normal(vec_dir(bug, pos)), 0.25)) + creatura.action_move(_self, pos2, 1) + else + animalia.move_head(_self, dir2yaw(dir), dir.y) + creatura.action_idle(_self, 0.1, "tongue_" .. frame) + action_init = true + end + end + if action_init then + timer = timer - _self.dtime + if timer <= 0 then + minetest.remove_node(bug) + return true + end + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:run_to_pos", function(self, pos2, timeout) + timeout = timeout or 3 + local function func(_self) + local pos = _self.object:get_pos() + if not pos then return end + if not pos2 then return true end + if not _self:get_action() then + local anim = (_self.animations["run"] and "run") or "walk" + creatura.action_move(_self, pos2, 2, "creatura:obstacle_avoidance", 1, anim) + end + timeout = timeout - _self.dtime + if timeout <= 0 then + return true + end + end + self:set_utility(func) +end) + +-- Object Interaction + +creatura.register_utility("animalia:follow_player", function(self, player, force) + local width = self.width + local function func(_self) + local pos = _self.object:get_pos() + if not pos then return end + local plyr_alive, _, plyr_pos = _self:get_target(player) + if not plyr_alive + or (not _self:follow_wielded_item(player) + and not force) then return true end + local dist = vec_dist(pos, plyr_pos) + if not _self:get_action() then + if dist > width + 1 then + animalia.action_pursue(_self, player, 3, "creatura:obstacle_avoidance", 0.75) + else + creatura.action_idle(_self, 1) + end + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:flee_from_target", function(self, target) + local los_timeout = 3 + local function func(_self) + local pos = _self.object:get_pos() + if not pos then return end + local tgt_alive, los, tgt_pos = _self:get_target(target) + if not tgt_alive then self._puncher = nil return true end + if not los then + los_timeout = los_timeout - _self.dtime + else + los_timeout = 3 + end + if los_timeout <= 0 then self._puncher = nil return true end + local dist = vec_dist(pos, tgt_pos) + if dist > _self.tracking_range then self._puncher = nil return true end + if not _self:get_action() then + local flee_dir = vec_dir(tgt_pos, pos) + local pos2 = _self:get_wander_pos(2, 3, flee_dir) + local anim = (_self.animations["run"] and "run") or "walk" + creatura.action_move(_self, pos2, 2, "creatura:obstacle_avoidance", 1, anim) + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:flee_from_target_defend", function(self, target) + local los_timeout = 3 + local function func(_self) + local pos = _self.object:get_pos() + if not pos then return end + local tgt_alive, los, tgt_pos = _self:get_target(target) + if not tgt_alive then self._puncher = nil return true end + if not los then + los_timeout = los_timeout - _self.dtime + else + los_timeout = 3 + end + if los_timeout <= 0 then self._puncher = nil return true end + local dist = vec_dist(pos, tgt_pos) + if dist > _self.tracking_range then self._puncher = nil return true end + if not _self:get_action() then + local flee_dir = vec_dir(tgt_pos, pos) + local pos2 = _self:get_wander_pos(2, 3, flee_dir) + local anim = (_self.animations["run"] and "run") or "walk" + if dist > _self.width + 0.5 then + creatura.action_move(_self, pos2, 2, "creatura:obstacle_avoidance", 1, anim) + else + animalia.action_punch_aoe(self, target) + end + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:tame_horse", function(self) + local center = self.object:get_pos() + local trust = 5 + local function func(_self) + local pos = _self.object:get_pos() + if not pos then return end + if not _self.rider + or not creatura.is_alive(_self.rider) then return true end + local player = _self.rider + if not _self:get_action() then + if random(6) < 2 then + creatura.action_idle(_self, 0.5, "punch_aoe") + else + local dir = vec_dist(pos, center) > 8 and vec_dir(pos, center) + local pos2 = _self:get_wander_pos(2, 4, dir) + creatura.action_move(_self, pos2, 3, "creatura:obstacle_avoidance", 1, "run") + end + end + local yaw = _self.object:get_yaw() + local plyr_yaw = player:get_look_horizontal() + if abs(diff(yaw, plyr_yaw)) < pi * 0.25 then + trust = trust + _self.dtime + else + trust = trust - _self.dtime * 0.5 + end + local min_pos = {x = pos.x, y = pos.y + 2, z = pos.z} + local max_pos = {x = pos.x, y = pos.y + 2, z = pos.z} + if trust <= 0 then + animalia.mount(_self, player) + animalia.particle_spawner(pos, "creatura_particle_red.png", "float", min_pos, max_pos) + return true + end + if trust >= 10 then + _self.owner = self:memorize("owner", player:get_player_name()) + animalia.protect_from_despawn(_self) + animalia.mount(_self, player) + animalia.particle_spawner(pos, "creatura_particle_green.png", "float", min_pos, max_pos) + return true + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:attack_target", function(self, target) + local width = self.width + local punch_init = false + local function func(_self) + local pos = _self.object:get_pos() + if not pos then return end + local tgt_alive, _, tgt_pos = _self:get_target(target) + if not tgt_alive then return true end + local dist = vec_dist(pos, tgt_pos) + if dist < width + 1 + and not punch_init then + punch_init = true + animalia.action_punch(_self, target) + end + if not _self:get_action() then + if punch_init then return true end + animalia.action_pursue(_self, target, 3, "creatura:pathfind", 0.75) + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia: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 _self.breeding then return true end + local pos = _self.object:get_pos() + if not pos then return end + local tgt_pos = mate:get_pos() + if not tgt_pos then return end + local dist = vec_dist(pos, tgt_pos) + if dist < _self.width + 0.5 then + breeding_time = breeding_time + _self.dtime + end + if breeding_time > 2 then + local mate_ent = mate:get_luaentity() + _self.breeding = self:memorize("breeding", false) + _self.breeding_cooldown = _self:memorize("breeding_cooldown", 300) + mate_ent.breeding = mate_ent:memorize("breeding", false) + mate_ent.breeding_cooldown = mate_ent:memorize("breeding_cooldown", 300) + local minp = vector.subtract(pos, 1) + local maxp = vec_add(pos, 1) + animalia.particle_spawner(pos, "heart.png", "float", minp, maxp) + for _ = 1, _self.birth_count or 1 do + if _self.add_child then + _self:add_child() + else + 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 + return true + end + if not _self:get_action() then + creatura.action_move(_self, tgt_pos) + end + end + self:set_utility(func) +end) + +-- Domesticated Behavior + +creatura.register_utility("animalia:stay", function(self) + local function func(_self) + local order = _self.order or "wander" + if order ~= "sit" then return true end + if not _self:get_action() then + creatura.action_idle(_self, 1, "sit") + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:destroy_nearby_vessel", function(self) + local width = self.width + local timeout = 8 + local nodes + local glass_vessels = {"vessels:glass_bottle", "vessels:drinking_glass"} + local pos2 + local function func(_self) + local pos = _self.object:get_pos() + if not pos then return end + nodes = nodes or minetest.find_nodes_in_area(vec_sub(pos, 6), vec_add(pos, 6), glass_vessels) or {} + if #nodes < 1 then return true end + if not _self:get_action() then + pos2 = pos2 or nodes[random(#nodes)] + local dist = vec_dist(pos, pos2) + if dist < width + 0.5 then + creatura.action_idle(_self, 0.7, "smack") + minetest.remove_node(pos2) + minetest.add_item(pos2, "vessels:glass_fragments") + return true + else + creatura.action_move(_self, pos2, 4, "creatura:pathfind") + end + end + timeout = timeout - self.dtime + if timeout <= 0 then return true end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:play_with_player", function(self, player) + local play_init = false + local width = self.width + local function func(_self) + local pos = _self.object:get_pos() + if not pos then return end + local plyr_alive, _, plyr_pos = _self:get_target(player) + if not plyr_alive + or _self.trust_cooldown > 0 then return true end + local dist = vec_dist(pos, plyr_pos) + if dist < width + 0.5 + and not play_init then + creatura.action_idle(_self, 0.5, "play") + _self.object:add_velocity({x = 0, y = 2, z = 0}) + animalia.particle_spawner(pos, "heart.png", "float") + animalia.add_trust(_self, player, 1) + play_init = true + end + if not _self:get_action() then + if play_init then return true end + animalia.action_pursue(_self, player, 1, "creatura:obstacle_avoidance") + end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:bother_player", function(self, player) + local width = self.width + local play_init = false + local timeout = 5 + local function func(_self) + local pos = _self.object:get_pos() + if not pos then return end + local plyr_alive, _, plyr_pos = _self:get_target(player) + if not plyr_alive then return true end + local dist = vec_dist(pos, plyr_pos) + if not _self:get_action() then + if play_init then return true end + if dist > width then + animalia.action_pursue(_self, player, 3, "creatura:pathfind", 0.75) + else + creatura.action_idle(_self, 0.5, "play") + play_init = true + end + end + timeout = timeout - _self.dtime + if timeout <= 0 then return true end + end + self:set_utility(func) +end) + +creatura.register_utility("animalia:mount_horse", 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 control.aux1 then + anim = "run" + end + if not _self.touching_ground + and not _self.in_liquid + and vel.y > 0 then + anim = "rear_constant" + end + end + local yaw = self.object:get_yaw() + local tyaw = player:get_look_horizontal() + if abs(yaw - tyaw) > 0.1 then + _self:turn_to() + end + _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) + +-- Misc + +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) \ No newline at end of file diff --git a/api/lasso.lua b/api/lasso.lua index a69dd3f..b2a6dd6 100644 --- a/api/lasso.lua +++ b/api/lasso.lua @@ -1,361 +1,361 @@ ------------ --- 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.catch_with_lasso then return end - 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 +----------- +-- 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) + 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) + 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(_, 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.catch_with_lasso then return end + 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/legacy_convert.lua b/api/legacy_convert.lua index 22133aa..bc2fe62 100644 --- a/api/legacy_convert.lua +++ b/api/legacy_convert.lua @@ -1,81 +1,82 @@ --------------------------------------- --- Convert Better Fauna to Animalia -- --------------------------------------- - -for i = 1, #animalia.mobs do - local new_mob = animalia.mobs[i] - local old_mob = "better_fauna:" .. new_mob:split(":")[2] - minetest.register_entity(":" .. old_mob, { - on_activate = mob_core.on_activate - }) - minetest.register_alias_force("better_fauna:spawn_" .. new_mob:split(":")[2], "animalia:spawn_" .. new_mob:split(":")[2]) -end - -minetest.register_globalstep(function(dtime) - local mobs = minetest.luaentities - for _, mob in pairs(mobs) do - if mob - and mob.name:match("better_fauna:") then - if mob.name:find("lasso_fence_ent") then - local pos = mob.object:get_pos() - if pos then - minetest.add_entity(pos, "animalia:lasso_fence_ent") - end - mob.object:remove() - elseif mob.name:find("lasso_visual") then - if pos then - minetest.add_entity(pos, "animalia:lasso_visual") - end - mob.object:remove() - end - for i = 1, #animalia.mobs do - local ent = animalia.mobs[i] - local new_name = ent:split(":")[2] - local old_name = mob.name:split(":")[2] - if new_name == old_name then - local pos = mob.object:get_pos() - if pos then - local new_mob = minetest.add_entity(pos, ent) - local mem = nil - if mob.memory then - mem = mob.memory - end - minetest.after(0.1, function() - if mem then - new_mob:get_luaentity().memory = mem - new_mob:get_luaentity():on_activate(new_mob, nil, dtime) - end - end) - end - mob.object:remove() - end - end - end - end -end) - - --- Tools - -minetest.register_alias_force("better_fauna:net", "animalia:net") -minetest.register_alias_force("better_fauna:lasso", "animalia:lasso") -minetest.register_alias_force("better_fauna:cat_toy", "animalia:cat_toy") -minetest.register_alias_force("better_fauna:saddle", "animalia:saddle") -minetest.register_alias_force("better_fauna:shears", "animalia:shears") - --- Drops - -minetest.register_alias_force("better_fauna:beef_raw", "animalia:beef_raw") -minetest.register_alias_force("better_fauna:beef_cooked", "animalia:beef_cooked") -minetest.register_alias_force("better_fauna:bucket_milk", "animalia:bucket_milk") -minetest.register_alias_force("better_fauna:leather", "animalia:leather") -minetest.register_alias_force("better_fauna:chicken_egg", "animalia:chicken_egg") -minetest.register_alias_force("better_fauna:chicken_raw", "animalia:poultry_raw") -minetest.register_alias_force("better_fauna:chicken_cooked", "animalia:poultry_cooked") -minetest.register_alias_force("better_fauna:feather", "animalia:feather") -minetest.register_alias_force("better_fauna:mutton_raw", "animalia:mutton_raw") -minetest.register_alias_force("better_fauna:mutton_cooked", "animalia:mutton_cooked") -minetest.register_alias_force("better_fauna:porkchop_raw", "animalia:porkchop_raw") -minetest.register_alias_force("better_fauna:porkchop_cooked", "animalia:porkchop_cooked") -minetest.register_alias_force("better_fauna:turkey_raw", "animalia:poultry_raw") +-------------------------------------- +-- Convert Better Fauna to Animalia -- +-------------------------------------- + +for i = 1, #animalia.mobs do + local new_mob = animalia.mobs[i] + local old_mob = "better_fauna:" .. new_mob:split(":")[2] + minetest.register_entity(":" .. old_mob, { + on_activate = mob_core.on_activate + }) + minetest.register_alias_force("better_fauna:spawn_" .. new_mob:split(":")[2], + "animalia:spawn_" .. new_mob:split(":")[2]) +end + +minetest.register_globalstep(function(dtime) + local mobs = minetest.luaentities + for _, mob in pairs(mobs) do + if mob + and mob.name:match("better_fauna:") then + local pos = mob.object:get_pos() + if not pos then return end + if mob.name:find("lasso_fence_ent") then + if pos then + minetest.add_entity(pos, "animalia:lasso_fence_ent") + end + mob.object:remove() + elseif mob.name:find("lasso_visual") then + if pos then + minetest.add_entity(pos, "animalia:lasso_visual") + end + mob.object:remove() + end + for i = 1, #animalia.mobs do + local ent = animalia.mobs[i] + local new_name = ent:split(":")[2] + local old_name = mob.name:split(":")[2] + if new_name == old_name then + if pos then + local new_mob = minetest.add_entity(pos, ent) + local mem = nil + if mob.memory then + mem = mob.memory + end + minetest.after(0.1, function() + if mem then + new_mob:get_luaentity().memory = mem + new_mob:get_luaentity():on_activate(new_mob, nil, dtime) + end + end) + end + mob.object:remove() + end + end + end + end +end) + + +-- Tools + +minetest.register_alias_force("better_fauna:net", "animalia:net") +minetest.register_alias_force("better_fauna:lasso", "animalia:lasso") +minetest.register_alias_force("better_fauna:cat_toy", "animalia:cat_toy") +minetest.register_alias_force("better_fauna:saddle", "animalia:saddle") +minetest.register_alias_force("better_fauna:shears", "animalia:shears") + +-- Drops + +minetest.register_alias_force("better_fauna:beef_raw", "animalia:beef_raw") +minetest.register_alias_force("better_fauna:beef_cooked", "animalia:beef_cooked") +minetest.register_alias_force("better_fauna:bucket_milk", "animalia:bucket_milk") +minetest.register_alias_force("better_fauna:leather", "animalia:leather") +minetest.register_alias_force("better_fauna:chicken_egg", "animalia:chicken_egg") +minetest.register_alias_force("better_fauna:chicken_raw", "animalia:poultry_raw") +minetest.register_alias_force("better_fauna:chicken_cooked", "animalia:poultry_cooked") +minetest.register_alias_force("better_fauna:feather", "animalia:feather") +minetest.register_alias_force("better_fauna:mutton_raw", "animalia:mutton_raw") +minetest.register_alias_force("better_fauna:mutton_cooked", "animalia:mutton_cooked") +minetest.register_alias_force("better_fauna:porkchop_raw", "animalia:porkchop_raw") +minetest.register_alias_force("better_fauna:porkchop_cooked", "animalia:porkchop_cooked") +minetest.register_alias_force("better_fauna:turkey_raw", "animalia:poultry_raw") minetest.register_alias_force("better_fauna:turkey_cooked", "animalia:poultry_cooked") \ No newline at end of file diff --git a/api/spawning.lua b/api/spawning.lua index 890e8c7..cdebc84 100644 --- a/api/spawning.lua +++ b/api/spawning.lua @@ -1,364 +1,350 @@ --------------- --- 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"}, -}) - -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 = 1, - min_light = 0, - min_group = 12, - max_group = 16, - biomes = animalia.registered_biome_groups["common"].biomes, - spawn_cluster = true, - nodes = {"group:leaves"} - -}) - -creatura.register_on_spawn("animalia:bird", function(self, pos) - local node = minetest.get_node(pos) - if node.name == "air" then - minetest.set_node(pos, {name = "animalia:nest_song_bird"}) - self.home_position = self:memorize("home_position", pos) - self.despawn_after = self:memorize("despawn_after", nil) - else - local nodes = minetest.find_nodes_in_area_under_air({x = pos.x - 3, y = pos.y - 3, z = pos.z - 3}, {x = pos.x + 3, y = pos.y + 7, z = pos.z + 3}, "group:leaves") - if nodes[1] then - pos = nodes[1] - minetest.set_node({x = pos.x, y = pos.y + 1, z = pos.z}, {name = "animalia:nest_song_bird"}) - self.home_position = self:memorize("home_position", nodes[1]) - self.despawn_after = self:memorize("despawn_after", nil) - end - end -end) - -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 = {} - -local c_air = minetest.get_content_id("air") - -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 max_y = maxp.y - local min_x = minp.x - local max_x = maxp.x - local min_z = minp.z - local max_z = maxp.z - - local vm, emin, emax = minetest.get_mapgen_object("voxelmanip") - local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax} - local data = vm:get_data() - - local spawn_added = false - - for xcen = min_x + 8, max_x - 7, 8 do - if spawn_added then break end - for zcen = min_z + 8, max_z - 7, 8 do - local surface = false -- y of above surface node - for y = max_y, 2, -1 do - local vi = area:index(xcen, y, zcen) - local c_node = data[vi] - if not c_node then break end - local c_name = minetest.get_name_from_content_id(c_node) - local c_def = minetest.registered_nodes[c_name] - if y == max_y and c_node ~= c_air then -- if top node solid - break - elseif minetest.get_item_group(c_name, "leaves") > 0 then - break - elseif c_def.walkable then - surface = y + 1 - break - end - end - if animalia.chunks_since_last_spawn > chunk_spawn_add_int - and surface then - local center = { - x = xcen, - y = surface, - z = zcen, - } - local spawnable_mobs = get_spawnable_mobs(center) - if spawnable_mobs - and #spawnable_mobs > 0 then - local mob = spawnable_mobs[random(#spawnable_mobs)] - local spawn_def = creatura.registered_mob_spawns[mob] - table.insert(animalia.spawn_queue, {pos = center, mob = mob, group = random(spawn_def.min_group, spawn_def.max_group)}) - table.insert(animalia.spawn_points, center) - end - spawn_added = true - animalia.chunks_since_last_spawn = 0 - end - end - 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 - and #spawnable_mobs > 0 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 - local spawn_def = creatura.registered_mob_spawns[mob] - table.insert(animalia.spawn_queue, {pos = point, mob = mob, group = random(spawn_def.min_group, spawn_def.max_group)}) - 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 - if queue[i].group > 4 - or creatura.registered_mob_spawns[queue[i].mob].spawn_cluster then - 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) - meta:set_string("cluster", queue[i].group) - else - 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 - end - table.remove(animalia.spawn_queue, i) - end - end - minetest.after(chunk_spawn_queue_int, spawn_queued) -end +-------------- +-- 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 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"}, +}) + +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 = 1, + min_light = 0, + min_group = 12, + max_group = 16, + biomes = animalia.registered_biome_groups["common"].biomes, + spawn_cluster = true, + nodes = {"group:leaves"} +}) + +creatura.register_on_spawn("animalia:bird", function(self, pos) + local node = minetest.get_node(pos) + if node.name == "air" then + minetest.set_node(pos, {name = "animalia:nest_song_bird"}) + self.home_position = self:memorize("home_position", pos) + self.despawn_after = self:memorize("despawn_after", nil) + else + local nodes = minetest.find_nodes_in_area_under_air( + {x = pos.x - 3, y = pos.y - 3, z = pos.z - 3}, + {x = pos.x + 3, y = pos.y + 7, z = pos.z + 3}, + "group:leaves" + ) + if nodes[1] then + pos = nodes[1] + minetest.set_node({x = pos.x, y = pos.y + 1, z = pos.z}, {name = "animalia:nest_song_bird"}) + self.home_position = self:memorize("home_position", {x = pos.x, y = pos.y + 1, z = pos.z}) + self.despawn_after = self:memorize("despawn_after", nil) + end + end +end) + +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 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 get_biome_name(pos) + if not pos then return end + return minetest.get_biome_name(minetest.get_biome_data(pos).biome) +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 = {} + +local c_air = minetest.get_content_id("air") + +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 max_y = maxp.y + local min_x = minp.x + local max_x = maxp.x + local min_z = minp.z + local max_z = maxp.z + + local vm, emin, emax = minetest.get_mapgen_object("voxelmanip") + local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax} + local data = vm:get_data() + + local spawn_added = false + + for xcen = min_x + 8, max_x - 7, 8 do + if spawn_added then break end + for zcen = min_z + 8, max_z - 7, 8 do + local surface = false -- y of above surface node + for y = max_y, 2, -1 do + local vi = area:index(xcen, y, zcen) + local c_node = data[vi] + if not c_node then break end + local c_name = minetest.get_name_from_content_id(c_node) + local c_def = minetest.registered_nodes[c_name] + if y == max_y and c_node ~= c_air then -- if top node solid + break + elseif minetest.get_item_group(c_name, "leaves") > 0 then + break + elseif c_def.walkable then + surface = y + 1 + break + end + end + if animalia.chunks_since_last_spawn > chunk_spawn_add_int + and surface then + local center = { + x = xcen, + y = surface, + z = zcen, + } + local spawnable_mobs = get_spawnable_mobs(center) + if spawnable_mobs + and #spawnable_mobs > 0 then + local mob = spawnable_mobs[random(#spawnable_mobs)] + local spawn_def = creatura.registered_mob_spawns[mob] + table.insert(animalia.spawn_queue, + {pos = center, mob = mob, group = random(spawn_def.min_group, spawn_def.max_group)}) + table.insert(animalia.spawn_points, center) + end + spawn_added = true + animalia.chunks_since_last_spawn = 0 + end + end + 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 + and #spawnable_mobs > 0 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 _, object in ipairs(objects) do + local ent = object and object:get_luaentity() + if ent + and ent.name:find("animalia:") then + spawn = false + break + end + end + end + if spawn then + local spawn_def = creatura.registered_mob_spawns[mob] + table.insert(animalia.spawn_queue, + {pos = point, mob = mob, group = random(spawn_def.min_group, spawn_def.max_group)}) + 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 + if queue[i].group > 4 + or creatura.registered_mob_spawns[queue[i].mob].spawn_cluster then + pos = creatura.get_ground_level(pos, 4) + minetest.add_node(pos, {name = "creatura:spawn_node"}) + local meta = minetest.get_meta(pos) + meta:set_string("mob", queue[i].mob) + meta:set_string("cluster", queue[i].group) + else + for _ = 1, queue[i].group do + pos = { + x = pos.x + random(-3, 3), + y = pos.y, + z = pos.z + random(-3, 3) + } + pos = creatura.get_ground_level(pos, 4) + minetest.add_node(pos, {name = "creatura:spawn_node"}) + local meta = minetest.get_meta(pos) + meta:set_string("mob", queue[i].mob) + end + 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 index eb20542..802c948 100644 --- a/api/storage.lua +++ b/api/storage.lua @@ -1,20 +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) - +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 aece53f..18ec865 100644 --- a/craftitems.lua +++ b/craftitems.lua @@ -1,756 +1,776 @@ ----------------- --- Craftitems -- ----------------- - -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 -}) - -minetest.register_node("animalia:nest_song_bird", { - description = "Song Bird Nest", - paramtype = "light", - drawtype = "mesh", - mesh = "animalia_nest.obj", - tiles = {"animalia_nest.png"}, - sunlight_propagates = true, - stack_max = 1, - groups = {snappy = 3, flammable = 3}, - selection_box = { - type = "fixed", - fixed = {-5 / 16, -0.5, -5 / 16, 5 / 16, -0.31, 5 / 16}, - }, - node_box = { - type = "fixed", - fixed = {-5 / 16, -0.5, -5 / 16, 5 / 16, -0.31, 5 / 16}, - }, - drops = "default:stick" -}) - ------------ --- Tools -- ------------ - -minetest.register_craftitem("animalia:cat_toy", { - description = "Cat Toy", - inventory_image = "animalia_cat_toy.png", - wield_image = "animalia_cat_toy.png^[transformFYR90", -}) - -local nametag = {} - -local function get_rename_formspec(meta) - local tag = meta:get_string("name") or "" - local form = { - "size[8,4]", - "field[0.5,1;7.5,0;name;" .. minetest.formspec_escape("Enter name:") .. ";" .. tag .. "]", - "button_exit[2.5,3.5;3,1;set_name;" .. minetest.formspec_escape("Set Name") .. "]" - } - return table.concat(form, "") -end - -minetest.register_on_player_receive_fields(function(player, formname, fields) - if formname == "animalia:set_name" and fields.name then - local name = player:get_player_name() - if not nametag[name] then - return - end - local itemstack = nametag[name] - if string.len(fields.name) > 64 then - fields.name = string.sub(fields.name, 1, 64) - end - local meta = itemstack:get_meta() - meta:set_string("name", fields.name) - meta:set_string("description", fields.name) - player:set_wielded_item(itemstack) - if fields.quit or fields.key_enter then - nametag[name] = nil - end - end -end) - -local function nametag_rightclick(itemstack, player, pointed_thing) - if pointed_thing - and pointed_thing.type == "object" then - return - end - local name = player:get_player_name() - nametag[name] = itemstack - local meta = itemstack:get_meta() - minetest.show_formspec(name, "animalia:set_name", get_rename_formspec(meta)) -end - -minetest.register_craftitem("animalia:nametag", { - description = "Nametag", - inventory_image = "animalia_nametag.png", - on_rightclick = nametag_rightclick, - on_secondary_use = nametag_rightclick -}) - -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", - inventory_image = "animalia_net.png", - stack_max = 1, - 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 - 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 - if placer:get_inventory():room_for_item("main", {name = "animalia:net"}) then - itemstack:take_item(1) - placer:get_inventory():add_item("main", "animalia:net") - return itemstack - else - return - end - end - meta:set_string("mob", ent.name) - meta:set_string("staticdata", ent:get_staticdata()) - local desc = "Animal Net \n" .. minetest.colorize("#a9a9a9", ent_name) .. "\n" .. minetest.colorize("#a9a9a9", ent_gender) - if ent.name == "animalia:cat" - and ent.trust - and ent.trust[placer:get_player_name()] then - desc = desc .. "\n" .. minetest.colorize("#a9a9a9", ent.trust[placer:get_player_name()]) - 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 " .. - correct_name( - meta:get_string("mob"))) - return - end - end - end, - 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 - local mob = itemstack:get_meta():get_string("mob") - local staticdata = itemstack:get_meta():get_string("staticdata") - if mob ~= "" then - pos.y = pos.y + - math.abs( - minetest.registered_entities[mob] - .collisionbox[2]) - minetest.add_entity(pos, mob, staticdata) - itemstack:get_meta():set_string("mob", nil) - itemstack:get_meta():set_string("staticdata", nil) - itemstack:get_meta():set_string("description", "Animal Net") - end - end - end - return itemstack - end -}) - ------------ --- 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 -}) - ------------ --- Libri -- ------------ - -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.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_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 -}) - --------------- --- Crafting -- --------------- - -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 -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 = { - {"", "group:string", "group:string"}, - {"", "group:leather", "group:string"}, - {"group:string", "", ""} - } -}) - -minetest.register_craft({ - output = "animalia:net", - recipe = { - {"group:string", "", "group:string"}, - {"group:string", "", "group:string"}, - {"group:stick", "group:string", ""} - } -}) - - -minetest.register_craft({ - output = "animalia:saddle", - recipe = { - {"group:leather", "group:leather", "group:leather"}, - {"group:leather", "group:steel_ingot", "group:leather"}, - {"group:string", "", "group:string"} - } -}) - -minetest.register_craft({ - output = "animalia:shears", - recipe = { - {"", "group:steel_ingot", ""}, - {"", "group:leather", "group:steel_ingot"} - } -}) - -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 +---------------- +-- Craftitems -- +---------------- + +local random = math.random + +local walkable_nodes = {} + +local color = minetest.colorize + +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 + +local 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) + 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) + 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", +}) + +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_raw_cooked", { + description = "Venison Steak", + inventory_image = "animalia_venison_cooked.png", + on_use = minetest.item_eat(10), + groups = {flammable = 2, meat = 1, food_meat = 1}, +}) + +minetest.register_craft({ + type = "cooking", + recipe = "animalia:venison_raw", + output = "animalia:venison_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}, +}) + +local 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 +}) + +minetest.register_node("animalia:nest_song_bird", { + description = "Song Bird Nest", + paramtype = "light", + drawtype = "mesh", + mesh = "animalia_nest.obj", + tiles = {"animalia_nest.png"}, + sunlight_propagates = true, + stack_max = 1, + groups = {snappy = 3, flammable = 3}, + selection_box = { + type = "fixed", + fixed = {-5 / 16, -0.5, -5 / 16, 5 / 16, -0.31, 5 / 16}, + }, + node_box = { + type = "fixed", + fixed = {-5 / 16, -0.5, -5 / 16, 5 / 16, -0.31, 5 / 16}, + }, + drops = "default:stick" +}) + +----------- +-- Tools -- +----------- + +minetest.register_craftitem("animalia:cat_toy", { + description = "Cat Toy", + inventory_image = "animalia_cat_toy.png", + wield_image = "animalia_cat_toy.png^[transformFYR90", + stack_max = 1 +}) + +local nametag = {} + +local function get_rename_formspec(meta) + local tag = meta:get_string("name") or "" + local form = { + "size[8,4]", + "field[0.5,1;7.5,0;name;" .. minetest.formspec_escape("Enter name:") .. ";" .. tag .. "]", + "button_exit[2.5,3.5;3,1;set_name;" .. minetest.formspec_escape("Set Name") .. "]" + } + return table.concat(form, "") +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname == "animalia:set_name" and fields.name then + local name = player:get_player_name() + if not nametag[name] then + return + end + local itemstack = nametag[name] + if string.len(fields.name) > 64 then + fields.name = string.sub(fields.name, 1, 64) + end + local meta = itemstack:get_meta() + meta:set_string("name", fields.name) + meta:set_string("description", fields.name) + player:set_wielded_item(itemstack) + if fields.quit or fields.key_enter then + nametag[name] = nil + end + end +end) + +local function nametag_rightclick(itemstack, player, pointed_thing) + if pointed_thing + and pointed_thing.type == "object" then + return + end + local name = player:get_player_name() + nametag[name] = itemstack + local meta = itemstack:get_meta() + minetest.show_formspec(name, "animalia:set_name", get_rename_formspec(meta)) +end + +minetest.register_craftitem("animalia:nametag", { + description = "Nametag", + inventory_image = "animalia_nametag.png", + on_rightclick = nametag_rightclick, + on_secondary_use = nametag_rightclick +}) + +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", + inventory_image = "animalia_net.png", + stack_max = 1, + 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 + 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 + if placer:get_inventory():room_for_item("main", {name = "animalia:net"}) then + itemstack:take_item(1) + placer:get_inventory():add_item("main", "animalia:net") + return itemstack + else + return + end + end + meta:set_string("mob", ent.name) + meta:set_string("staticdata", ent:get_staticdata()) + local desc = "Animal Net \n" .. color("#a9a9a9", ent_name) .. "\n" .. color("#a9a9a9", ent_gender) + if ent.name == "animalia:cat" + and ent.trust + and ent.trust[placer:get_player_name()] then + desc = desc .. "\n" .. color("#a9a9a9", ent.trust[placer:get_player_name()]) + 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 " .. + correct_name( + meta:get_string("mob"))) + return + end + end + end, + 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 + local mob = itemstack:get_meta():get_string("mob") + local staticdata = itemstack:get_meta():get_string("staticdata") + if mob ~= "" then + pos.y = pos.y + + math.abs( + minetest.registered_entities[mob] + .collisionbox[2]) + minetest.add_entity(pos, mob, staticdata) + itemstack:get_meta():set_string("mob", nil) + itemstack:get_meta():set_string("staticdata", nil) + itemstack:get_meta():set_string("description", "Animal Net") + end + end + end + return itemstack + end +}) + +----------- +-- 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, not_in_creative_inventory = 1}, + on_punch = function(pos, _, player) + 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 +}) + +----------- +-- Libri -- +----------- + +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.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_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")) + 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")) + if not pages + or #pages < 1 then return end + animalia.show_libri_main_form(player, pages) + end +}) + +-------------- +-- Crafting -- +-------------- + +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 +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 = { + {"", "group:string", "group:string"}, + {"", "group:leather", "group:string"}, + {"group:string", "", ""} + } +}) + +minetest.register_craft({ + output = "animalia:net", + recipe = { + {"group:string", "", "group:string"}, + {"group:string", "", "group:string"}, + {"group:stick", "group:string", ""} + } +}) + + +minetest.register_craft({ + output = "animalia:saddle", + recipe = { + {"group:leather", "group:leather", "group:leather"}, + {"group:leather", "group:steel_ingot", "group:leather"}, + {"group:string", "", "group:string"} + } +}) + +minetest.register_craft({ + output = "animalia:shears", + recipe = { + {"", "group:steel_ingot", ""}, + {"", "group:leather", "group:steel_ingot"} + } +}) + +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, _, 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 2fa067f..a4d4049 100644 --- a/init.lua +++ b/init.lua @@ -1,16 +1,15 @@ animalia = {} -better_fauna = animalia animalia.pets = {} minetest.register_on_joinplayer(function(player) - local name = player:get_player_name() - animalia.pets[name] = {} + local name = player:get_player_name() + animalia.pets[name] = {} end) minetest.register_on_leaveplayer(function(player) - local name = player:get_player_name() - animalia.pets[name] = nil + local name = player:get_player_name() + animalia.pets[name] = nil end) -- Daytime Tracking @@ -18,9 +17,9 @@ end) animalia.is_day = true local function is_day() - local time = (minetest.get_timeofday() or 0) * 24000 - animalia.is_day = time < 19500 and time > 4500 - minetest.after(10, is_day) + local time = (minetest.get_timeofday() or 0) * 24000 + animalia.is_day = time < 19500 and time > 4500 + minetest.after(10, is_day) end is_day() @@ -33,61 +32,61 @@ dofile(path.."/api/lasso.lua") dofile(path.."/craftitems.lua") 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", + "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", } for i = 1, #animalia.animals do - local name = string.split(animalia.animals[i], ":")[2] - dofile(path.."/mobs/" .. name .. ".lua") + local name = animalia.animals[i]:split(":")[2] + dofile(path.."/mobs/" .. name .. ".lua") end if minetest.settings:get_bool("spawn_mobs", true) then - dofile(path.."/api/spawning.lua") + 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, damage) - old_punch(self, puncher, time_from_last_punch, tool_capabilities, dir, damage) - 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 + 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, damage) + old_punch(self, puncher, time_from_last_punch, tool_capabilities, dir, damage) + 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) diff --git a/mobs/bat.lua b/mobs/bat.lua index 12ef032..4866ba0 100644 --- a/mobs/bat.lua +++ b/mobs/bat.lua @@ -2,30 +2,6 @@ -- 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 -- @@ -51,6 +27,33 @@ local function vec_raise(v, n) return {x = v.x, y = v.y + n, z = v.z} end +--------------- +-- Utilities -- +--------------- + +local function get_roost(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 roosts = {} + for i = 1, #walkable do + local i_pos = walkable[i] + local n_pos = { + x = i_pos.x, + y = i_pos.y - 1, + z = i_pos.z + } + if creatura.get_node_def(n_pos).name == "air" + and minetest.line_of_sight(pos, n_pos) then + table.insert(roosts, n_pos) + end + end + return roosts[random(#roosts)] +end + local function is_node_walkable(name) local def = minetest.registered_nodes[name] return def and def.walkable @@ -84,7 +87,7 @@ creatura.register_mob("animalia:bat", { 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} + cling = {range = {x = 150, y = 150}, speed = 1, frame_blend = 0, loop = false} }, -- Misc sounds = { @@ -103,80 +106,63 @@ creatura.register_mob("animalia:bat", { "butterflies:butterfly_violet" }, -- Function + step_delay = 0.25, + roost_action = animalia.action_cling, utility_stack = { - [1] = { + { utility = "animalia:wander", + step_delay = 0.25, get_score = function(self) - if self.is_landed then - return 0.1, {self} - end - return 0 + return 0.1, {self} end }, - [2] = { - utility = "animalia:aerial_swarm", + { + utility = "animalia:aerial_wander", + step_delay = 0.25, 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 player:get_pos() - 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 + local pos = self.object:get_pos() + if not pos then return end + local player = creatura.get_nearby_player(self) + local plyr_pos = player and not player:get_player_control().sneak and player:get_pos() + if plyr_pos then + local trust = self.trust[player:get_player_name() or ""] or 0 + local dist = vec_dist(pos, plyr_pos) + self._target = player + self.is_landed = false + return (12 - (dist + trust)) * 0.1, {self} end if self.in_liquid or not self.is_landed then - return 0.11, {self, 1} + return 0.2, {self} end return 0 end }, - [3] = { - utility = "animalia:land", + { + utility = "animalia:fly_to_land", get_score = function(self) - if not self.is_landed - and not self.touching_ground then - return 0.12, {self} + if self.is_landed + and not self.touching_ground + and not self.in_liquid + and creatura.sensor_floor(self, 3, true) > 2 then + return 0.3, {self} end return 0 end }, [4] = { - utility = "animalia:return_to_home", + utility = "animalia:fly_to_roost", get_score = function(self) - if not self.home_position then return 0 end - local player = self._nearby_player - if player - and player:get_pos() 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 + local pos = self.object:get_pos() + if not pos then return end + local home = animalia.is_day and self.home_position + if home + and home.x + and vec_dist(pos, home) < 8 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) @@ -184,32 +170,40 @@ creatura.register_mob("animalia:bat", { animalia.initialize_lasso(self) 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 + self.trust = self:recall("trust") or {} + if not self.home_position then + local roost = get_roost(self.object:get_pos(), 8) + if roost then + self.home_position = self:memorize("home_position", roost) + end + end 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) + animalia.rotate_to_pitch(self) + local pos = self.object:get_pos() + if not pos then return end + if self:timer(random(10,15)) then + if random(4) < 2 then + self.is_landed = not self.is_landed end - if self.stamina > 25 - and self.is_landed then - self.is_landed = self:memorize("is_landed", false) + if not self.home_position + or creatura.get_node_def(self.home_position).walkable then + local roost = get_roost(pos, 8) + if roost then + self.home_position = self:memorize("home_position", roost) + end 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 vel_y = vector.normalize(self.object:get_velocity()).y local rot = self.object:get_rotation() + local n_rot = rot.x + (vel_y - rot.x) * 0.2 self.object:set_rotation({ - x = clamp(vel_y * 0.25, -0.75, 0.75), + x = clamp(n_rot, -0.75, 0.75), y = rot.y, z = rot.z }) @@ -218,8 +212,7 @@ creatura.register_mob("animalia:bat", { 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() + and self:get_utility() == "animalia:fly_to_roost" then pos = { x = floor(pos.x + 0.5), y = floor(pos.y + 0.5), @@ -229,7 +222,7 @@ creatura.register_mob("animalia:bat", { return end local fail_safe = 1 - while not is_node_walkable(minetest.get_node(floor_pos).name) + while not is_node_walkable(minetest.get_node(pos).name) and fail_safe < 16 do pos.y = pos.y - 1 end @@ -260,6 +253,7 @@ creatura.register_mob("animalia:bat", { end, on_rightclick = function(self, clicker) if animalia.feed(self, clicker, false, false) then + animalia.add_trust(self, clicker, 1) return end if animalia.set_nametag(self, clicker) then diff --git a/mobs/bird.lua b/mobs/bird.lua index 8c3b774..e937c2b 100644 --- a/mobs/bird.lua +++ b/mobs/bird.lua @@ -5,45 +5,38 @@ local follows = {} minetest.register_on_mods_loaded(function() - for name, def in pairs(minetest.registered_items) do - if name:match(":seed_") + for name in pairs(minetest.registered_items) do + if name:match(":seed_") or name:match("_seed") then table.insert(follows, name) - end - end + end + end end) 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 +local vec_dist = vector.distance creatura.register_mob("animalia:bird", { - -- Stats - max_health = 5, - armor_groups = {fleshy = 200}, - damage = 0, - speed = 4, + -- Stats + max_health = 5, + armor_groups = {fleshy = 200}, + damage = 0, + speed = 4, tracking_range = 16, - despawn_after = 100, + despawn_after = 100, -- Entity Physics stepheight = 1.1, - max_fall = 100, + max_fall = 0, turn_rate = 6, boid_seperation = 0.4, - -- Visuals - mesh = "animalia_bird.b3d", - hitbox = { + -- Visuals + mesh = "animalia_bird.b3d", + hitbox = { width = 0.15, height = 0.3 }, - visual_size = {x = 7, y = 7}, + visual_size = {x = 7, y = 7}, textures = { "animalia_bird_cardinal.png", "animalia_bird_eastern_blue.png", @@ -52,97 +45,89 @@ creatura.register_mob("animalia:bird", { 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} + fly = {range = {x = 120, y = 140}, speed = 80, frame_blend = 0.3, loop = true} }, - -- Misc + -- Misc + step_delay = 0.25, catch_with_net = true, catch_with_lasso = false, sounds = { cardinal = { - name = "animalia_cardinal", - gain = 0.5, - distance = 63, + name = "animalia_cardinal", + gain = 0.5, + distance = 63, variations = 3 - }, + }, eastern_blue = { - name = "animalia_eastern_blue", - gain = 0.5, - distance = 63, + name = "animalia_eastern_blue", + gain = 0.5, + distance = 63, variations = 3 - }, - goldfinch = { - name = "animalia_goldfinch", - gain = 0.5, - distance = 63, + }, + goldfinch = { + name = "animalia_goldfinch", + gain = 0.5, + distance = 63, variations = 3 - }, - }, - follow = follows, - -- Function + }, + }, + follow = follows, + -- Function + wander_action = animalia.action_move_flock, utility_stack = { { - utility = "animalia:boid_wander", + utility = "animalia:wander_group", + step_delay = 0.25, get_score = function(self) return 0.1, {self, true} end }, { - utility = "animalia:aerial_flock", + utility = "animalia:aerial_wander", + step_delay = 0.25, 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, 0.5} - end - local player = creatura.get_nearby_player(self) - if player - and player:get_pos() then - local dist = vector.distance(pos, player:get_pos()) - self.is_landed = false - return (16 - dist) * 0.1, {self, 0.5} - end + if not self.is_landed + or self.in_liquid then + return 0.2, {self} end return 0 end }, { - utility = "animalia:land", + utility = "animalia:fly_to_land", get_score = function(self) if self.is_landed and not self.touching_ground - and not self.in_liquid then - return 0.12, {self} + and not self.in_liquid + and creatura.sensor_floor(self, 3, true) > 2 then + return 0.3, {self} end return 0 end }, { - utility = "animalia:return_to_nest", + utility = "animalia:fly_to_roost", get_score = function(self) - if not self.home_position then - return 0 - end - local player = self._nearby_player + local pos = self.object:get_pos() + if not pos then return end + local player = creatura.get_nearby_player(self) if player and player:get_pos() then - local pos = self.object:get_pos() local dist = vector.distance(pos, player:get_pos()) if dist < 3 then return 0 end end - if not animalia.is_day then + local home = not animalia.is_day and self.home_position + if home + and vec_dist(pos, home) < 8 then return 0.6, {self} end return 0 end } }, - activate_func = function(self) + activate_func = function(self) animalia.initialize_api(self) animalia.initialize_lasso(self) self._tp2home = self:recall("_tp2home") or nil @@ -152,52 +137,49 @@ creatura.register_mob("animalia:bird", { self.object:set_pos(self.home_position) end self.is_landed = self:recall("is_landed") or false - self.stamina = self:recall("stamina") or 40 if not self.home_position then local pos = self.object:get_pos() - local nests = minetest.find_nodes_in_area_under_air(vector.add(pos, 4), vector.subtract(pos, 4), {"animalia:nest_song_bird"}) + local nests = minetest.find_nodes_in_area_under_air( + vector.add(pos, 4), + vector.subtract(pos, 4), + {"animalia:nest_song_bird"} + ) if nests[1] and minetest.get_natural_light(nests[1]) > 0 then self.home_position = self:memorize("home_position", nests[1]) end end - end, - step_func = function(self) + end, + step_func = function(self) animalia.step_timers(self) animalia.do_growth(self, 60) animalia.update_lasso_effects(self) - if animalia.is_day - and 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") + animalia.rotate_to_pitch(self) + if self:timer(random(10,15)) then + if animalia.is_day 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 - 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) + if random(4) < 2 then + self.is_landed = not self.is_landed end - if self.stamina > 25 - and self.is_landed then - self.is_landed = self:memorize("is_landed", false) + local home = self.home_position + if home + and creatura.get_node_def(home).name ~= "animalia:nest_song_bird" then + local nodes = minetest.find_nodes_in_area_under_air( + {x = home.x, y = home.y - 12, z = home.z}, + {x = home.x, y = home.y + 12, z = home.z}, + {"animalia:nest_song_bird"} + ) + if nodes[1] then + self.home_position = self:memorize("home_position", nodes[1]) + end 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 @@ -205,12 +187,12 @@ creatura.register_mob("animalia:bird", { else self.speed = 1 end - end, - death_func = function(self) + end, + death_func = function(self) if self:get_utility() ~= "animalia:die" then self:initiate_utility("animalia:die", self) end - end, + end, deactivate_func = function(self) if self:get_utility() and self:get_utility() == "animalia:return_to_nest" then @@ -238,3 +220,12 @@ creatura.register_mob("animalia:bird", { }) creatura.register_spawn_egg("animalia:bird", "ae2f2f", "f3ac1c") + +minetest.register_abm({ + label = "animalia:nest_cleanup", + nodenames = "animalia:nest_song_bird", + interval = 900, + action = function(pos) + minetest.remove_node(pos) + end +}) \ No newline at end of file diff --git a/mobs/cat.lua b/mobs/cat.lua index c77c6ba..2737f27 100644 --- a/mobs/cat.lua +++ b/mobs/cat.lua @@ -2,6 +2,10 @@ -- Cat -- --------- +local random = math.random + +local vec_dist = vector.distance + local follow = { "animalia:poultry_raw" } @@ -20,6 +24,7 @@ creatura.register_mob("animalia:cat", { damage = 1, speed = 5, tracking_range = 24, + turn_rate = 9, despawn_after = 2000, -- Entity Physics stepheight = 1.1, @@ -34,16 +39,25 @@ creatura.register_mob("animalia:cat", { "animalia_cat_1.png", "animalia_cat_2.png", "animalia_cat_3.png", - "animalia_cat_4.png" + "animalia_cat_4.png", + "animalia_cat_5.png", + "animalia_cat_6.png", + "animalia_cat_7.png", + "animalia_cat_8.png", + "animalia_cat_9.png", + "animalia_cat_ash.png", + "animalia_cat_birch.png", }, animations = { stand = {range = {x = 1, y = 39}, speed = 10, frame_blend = 0.3, loop = true}, - walk = {range = {x = 50, y = 90}, speed = 45, frame_blend = 0.3, loop = true}, - run = {range = {x = 100, y = 130}, speed = 50, frame_blend = 0.3, loop = true}, - 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}, + walk = {range = {x = 41, y = 59}, speed = 20, frame_blend = 0.3, loop = true}, + run = {range = {x = 42, y = 59}, speed = 30, frame_blend = 0.3, loop = true}, + play = {range = {x = 61, y = 79}, speed = 30, frame_blend = 0.3, loop = false}, + sit = {range = {x = 81, y = 99}, speed = 10, frame_blend = 0.3, loop = true}, + smack = {range = {x = 101, y = 119}, speed = 40, frame_blend = 0.1, loop = true}, }, -- Misc + step_delay = 0.25, catch_with_net = true, catch_with_lasso = true, sounds = { @@ -70,7 +84,7 @@ creatura.register_mob("animalia:cat", { }, follow = follow, head_data = { - offset = {x = 0, y = 0.22, z = 0}, + offset = {x = 0, y = 0.18, z = 0}, pitch_correction = -20, pivot_h = 0.65, pivot_v = 0.65 @@ -79,7 +93,6 @@ creatura.register_mob("animalia:cat", { activate_func = function(self) animalia.initialize_api(self) animalia.initialize_lasso(self) - self._path = {} self.interact_sound_cooldown = 0 self.trust_cooldown = self:recall("trust_cooldown") or 0 self.order = self:recall("order") or "wander" @@ -93,94 +106,94 @@ creatura.register_mob("animalia:cat", { end end, utility_stack = { - [1] = { - utility = "animalia:skittish_wander", + { + utility = "animalia:wander_skittish", + step_delay = 0.25, get_score = function(self) return 0.1, {self} end }, - [2] = { + { utility = "animalia:swim_to_land", + step_delay = 0.25, get_score = function(self) if self.in_liquid then - return 0.9, {self} + return 0.3, {self} end return 0 end }, - [3] = { - utility = "animalia:find_and_break_glass_vessels", + { + utility = "animalia:destroy_nearby_vessel", + step_delay = 0.25, 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 - and player:get_player_name() 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 + if random(24) < 2 then + return 0.2, {self} end return 0 end }, - [5] = { - utility = "animalia:sit", + { + utility = "animalia:bother_player", + step_delay = 0.25, get_score = function(self) - if self.order == "sit" - and self.trust[self.owner] > 7 then - return 0.8, {self} + if random(24) > 1 then return 0 end + local owner = self.owner and minetest.get_player_by_name(self.owner) + local pos = self.object:get_pos() + if not pos then return end + local trust = self.trust[self.owner] or 0 + if trust > 3 + and owner + and vec_dist(pos, owner:get_pos()) < self.tracking_range then + return 0.2, {self, owner} end return 0 end }, - [6] = { + { + utility = "animalia:stay", + step_delay = 0.25, + get_score = function(self) + local trust = (self.owner and self.trust[self.owner]) or 0 + if trust < 5 then return 0 end + local order = self.order or "wander" + if order == "sit" then + return 0.5, {self} + end + return 0 + end + }, + { + utility = "animalia:play_with_player", + step_delay = 0.25, + get_score = function(self) + if self.trust_cooldown > 0 then return 0 end + local owner = self.owner and minetest.get_player_by_name(self.owner) + if owner + and owner:get_wielded_item():get_name() == "animalia:cat_toy" then + return 0.6, {self, owner} + end + return 0 + end + }, + { 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 + local lasso = type(self.lasso_origin or {}) == "userdata" and self.lasso_origin + local trust = (self.owner and self.trust[self.owner]) or 0 + local owner = self.owner and self.order == "follow" and trust > 4 and minetest.get_player_by_name(self.owner) + local force = (lasso and lasso ~= false) or (owner and owner ~= false) + local player = (force and (owner or lasso)) or creatura.get_nearby_player(self) if player - and player:get_player_name() 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} + and self:follow_wielded_item(player) then + return 0.6, {self, player, force} end return 0 end }, - [7] = { - utility = "animalia:mammal_breed", + { + utility = "animalia:breed", + step_delay = 0.25, get_score = function(self) if self.breeding and animalia.get_nearby_mate(self, self.name) then @@ -196,9 +209,6 @@ creatura.register_mob("animalia:cat", { 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 @@ -234,20 +244,6 @@ creatura.register_mob("animalia:cat", { 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 - 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 - 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 @@ -261,7 +257,7 @@ creatura.register_mob("animalia:cat", { or clicker:get_player_name() ~= self.owner then return end - if trust <= 7 then + if trust <= 5 then if self.interact_sound_cooldown <= 0 then self.interact_sound_cooldown = 3 self:play_sound("random") @@ -276,11 +272,19 @@ creatura.register_mob("animalia:cat", { end 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(0.7) elseif order == "follow" then + minetest.chat_send_player(clicker:get_player_name(), "Wolf is sitting") self.order = "sit" + self:initiate_utility("animalia:stay", self) + self:set_utility_score(0.5) 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 diff --git a/mobs/chicken.lua b/mobs/chicken.lua index d33ac2b..e7eb142 100644 --- a/mobs/chicken.lua +++ b/mobs/chicken.lua @@ -5,7 +5,7 @@ local follows = {} minetest.register_on_mods_loaded(function() - for name, def in pairs(minetest.registered_items) do + for name in pairs(minetest.registered_items) do if name:match(":seed_") or name:match("_seed") then table.insert(follows, name) @@ -19,7 +19,7 @@ creatura.register_mob("animalia:chicken", { armor_groups = {fleshy = 150}, damage = 0, speed = 4, - tracking_range = 16, + tracking_range = 4, despawn_after = 1500, -- Entity Physics stepheight = 1.1, @@ -42,12 +42,13 @@ creatura.register_mob("animalia:chicken", { "animalia_rooster_2.png", "animalia_rooster_3.png" }, - child_textures = {"animalia_chick.png"}, + child_textures = {"animalia_chicken_child.png"}, animations = { - 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 = 70, frame_blend = 0.3, loop = true}, + stand = {range = {x = 1, y = 39}, speed = 20, frame_blend = 0.3, loop = true}, + walk = {range = {x = 41, y = 59}, speed = 30, frame_blend = 0.3, loop = true}, + run = {range = {x = 41, y = 59}, speed = 45, frame_blend = 0.3, loop = true}, + eat = {range = {x = 61, y = 89}, speed = 45, frame_blend = 0.3, loop = true}, + fall = {range = {x = 91, y = 99}, speed = 70, frame_blend = 0.3, loop = true} }, -- Misc catch_with_net = true, @@ -75,61 +76,86 @@ creatura.register_mob("animalia:chicken", { }, follow = follows, head_data = { - offset = {x = 0, y = 0.15, z = 0}, - pitch_correction = 55, + offset = {x = 0, y = 0.45, z = 0}, + pitch_correction = 40, pivot_h = 0.25, pivot_v = 0.55 }, -- Function + add_child = function(self) + local pos = self.object:get_pos() + if not pos then return end + 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", + }) + 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, utility_stack = { - [1] = { - utility = "animalia:wander", + { + utility = "animalia:wander_group", + step_delay = 0.25, get_score = function(self) - return 0.1, {self, true} + return 0.1, {self} 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", + step_delay = 0.25, get_score = function(self) if self.in_liquid then - return 1, {self} + return 0.5, {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) + local lasso = type(self.lasso_origin or {}) == "userdata" and self.lasso_origin + local force = lasso and lasso ~= false + local player = (force and lasso) or creatura.get_nearby_player(self) if player and self:follow_wielded_item(player) then - return 0.8, {self, player} + return 0.3, {self, player} end return 0 end }, - [5] = { - utility = "animalia:bird_breed", + { + utility = "animalia:breed", + step_delay = 0.25, get_score = function(self) if self.breeding and animalia.get_nearby_mate(self, self.name) then - return 0.9, {self} + return 0.4, {self} end return 0 end + }, + { + utility = "animalia:flee_from_target", + get_score = function(self) + local puncher = self._target + if puncher + and puncher:get_pos() then + return 0.6, {self, puncher} + end + self._target = nil + return 0 + end } }, activate_func = function(self) @@ -141,6 +167,10 @@ creatura.register_mob("animalia:chicken", { animalia.head_tracking(self, 0.75, 0.75) animalia.do_growth(self, 60) animalia.update_lasso_effects(self) + if self.fall_start then + self:set_gravity(-4.9) + self:animate("fall") + end end, death_func = function(self) if self:get_utility() ~= "animalia:die" then @@ -158,8 +188,7 @@ creatura.register_mob("animalia:chicken", { 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) + self._target = puncher end }) diff --git a/mobs/cow.lua b/mobs/cow.lua index 6e37094..5805c20 100644 --- a/mobs/cow.lua +++ b/mobs/cow.lua @@ -2,10 +2,12 @@ -- Cow -- --------- +local random = math.random + local follows = {} minetest.register_on_mods_loaded(function() - for name, def in pairs(minetest.registered_items) do + for name 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 @@ -51,11 +53,12 @@ creatura.register_mob("animalia:cow", { "animalia_cow_4.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 = 60, frame_blend = 0.3, loop = true}, + stand = {range = {x = 1, y = 59}, speed = 10, frame_blend = 0.3, loop = true}, + walk = {range = {x = 61, y = 79}, speed = 20, frame_blend = 0.3, loop = true}, + run = {range = {x = 61, y = 79}, speed = 30, frame_blend = 0.3, loop = true}, }, -- Misc + step_delay = 0.25, catch_with_net = true, catch_with_lasso = true, sounds = { @@ -82,77 +85,85 @@ creatura.register_mob("animalia:cow", { }, follow = follows, consumable_nodes = { - { - name = "default:dirt_with_grass", - replacement = "default:dirt" - }, - { - name = "default:dry_dirt_with_dry_grass", - replacement = "default:dry_dirt" - } + ["default:dirt_with_grass"] = "default:dirt", + ["default:dry_dirt_with_dry_grass"] = "default:dry_dirt" }, head_data = { - offset = {x = 0, y = 0.5, z = 0}, - pitch_correction = -45, + offset = {x = 0, y = 0.7, z = 0.0}, + pitch_correction = -65, pivot_h = 0.75, pivot_v = 1 }, -- Function utility_stack = { - [1] = { + { utility = "animalia:wander", + step_delay = 0.25, get_score = function(self) - return 0.1, {self, true} + return 0.1, {self} end }, - [2] = { - utility = "animalia:eat_from_turf", + { + utility = "animalia:eat_turf", + step_delay = 0.25, get_score = function(self) - if math.random(25) < 2 then - return 0.1, {self} + if random(64) < 2 then + return 0.2, {self} end return 0 end }, - [3] = { + { utility = "animalia:swim_to_land", + step_delay = 0.25, get_score = function(self) if self.in_liquid then - return 1, {self} + return 0.3, {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) + local lasso = type(self.lasso_origin or {}) == "userdata" and self.lasso_origin + local force = lasso and lasso ~= false + local player = (force and lasso) or creatura.get_nearby_player(self) if player and self:follow_wielded_item(player) then - return 0.8, {self, player} + return 0.4, {self, player} end return 0 end }, - [5] = { - utility = "animalia:mammal_breed", + { + utility = "animalia:breed", + step_delay = 0.25, get_score = function(self) if self.breeding and animalia.get_nearby_mate(self, self.name) then - return 0.9, {self} + return 0.5, {self} end return 0 end + }, + { + utility = "animalia:flee_from_target", + get_score = function(self) + local puncher = self._target + if puncher + and puncher:get_pos() then + return 0.6, {self, puncher} + end + self._target = nil + return 0 + end } }, activate_func = function(self) animalia.initialize_api(self) animalia.initialize_lasso(self) - self.gotten = self:recall("gotten") or false + self.collected = self:recall("collected") or false end, step_func = function(self) animalia.step_timers(self) @@ -181,7 +192,7 @@ creatura.register_mob("animalia:cow", { return end - if self.gotten then + if self.collected then minetest.chat_send_player(name, "This Cow has already been milked.") return end @@ -199,15 +210,14 @@ creatura.register_mob("animalia:cow", { minetest.add_item(pos, {name = "animalia:bucket_milk"}) end - self.gotten = self:memorize("gotten", true) + self.collected = self:memorize("collected", true) return end animalia.add_libri_page(self, clicker, {name = "cow", form = "pg_cow;Cows"}) 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) + self._target = puncher end }) diff --git a/mobs/frog.lua b/mobs/frog.lua index 85b825d..f1fc61e 100644 --- a/mobs/frog.lua +++ b/mobs/frog.lua @@ -4,7 +4,9 @@ local random = math.random +local vec_add = vector.add local vec_dist = vector.distance +local vec_sub = vector.subtract creatura.register_mob("animalia:frog", { -- Stats @@ -16,9 +18,10 @@ creatura.register_mob("animalia:frog", { despawn_after = 2500, -- Entity Physics stepheight = 1.1, - max_fall = 100, + max_fall = 0, turn_rate = 10, bouyancy_multiplier = 0, + hydrodynamics_multiplier = 0.3, -- Visuals mesh = "animalia_frog.b3d", hitbox = { @@ -37,9 +40,11 @@ creatura.register_mob("animalia:frog", { 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} + walk = {range = {x = 50, y = 80}, speed = 50, frame_blend = 0.3, loop = true}, + run = {range = {x = 50, y = 80}, speed = 60, frame_blend = 0.3, loop = true} }, -- Misc + step_delay = 0.25, makes_footstep_sound = true, catch_with_net = true, catch_with_lasso = true, @@ -64,81 +69,91 @@ creatura.register_mob("animalia:frog", { }, -- Function utility_stack = { - [1] = { + { utility = "animalia:wander", + step_delay = 0.25, get_score = function(self) return 0.1, {self} end }, - [2] = { - utility = "animalia:wander_water_surface", + { + utility = "animalia:aquatic_wander", + step_delay = 0.25, get_score = function(self) if self.in_liquid then - return 0.11, {self} + return 0.2, {self} end return 0 end }, - [3] = { - utility = "animalia:eat_bug_nodes", + { + utility = "animalia:eat_bug", 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 not pos then return end + if random(12) < 2 then + local food = minetest.find_nodes_in_area(vec_sub(pos, 1.5), vec_add(pos, 1.5), self.follow) if food[1] then - return 0.2, {self} + return 0.3, {self, food[1]} 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", + { + utility = "animalia:breed", + step_delay = 0.25, get_score = function(self) if self.breeding and animalia.get_nearby_mate(self, self.name) and self.in_liquid then - return 1 + return 1, {self} end return 0 end }, - [6] = { - utility = "animalia:flee_from_player", + { + utility = "animalia:flop", + step_delay = 0.25, get_score = function(self) - if self.in_liquid then return 0 end - local player = creatura.get_nearby_player(self) - if player - and player:get_player_name() 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} + if not self.in_liquid + and self.growth_scale <= 0.6 then + return 1, {self} end return 0 end }, - [7] = { - utility = "animalia:flee_to_water", + { + utility = "animalia:flee_from_target", 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 pos then return end + local target = self._target or creatura.get_nearby_player(self) + local tgt_pos = target and target:get_pos() + local plyr_name = (target and target:is_player() and target:get_player_name()) or "" + if tgt_pos then + local trust = self.trust[plyr_name] or 0 + self._target = target -- stored to memory to avoid calling get_nearby_player again + return (10 - (vec_dist(pos, tgt_pos) + trust)) * 0.1, {self, target} + end + return 0 + end + }, + { + utility = "animalia:run_to_pos", + get_score = function(self) + if self.in_liquid then return 0 end + local pos = self.object:get_pos() + if not pos then return end + local water = minetest.find_nodes_in_area(vec_sub(pos, 1.5), vec_add(pos, 1.5), {"group:water"}) if not water[1] then return 0 end - local player = self._nearby_player - if player - and player:get_player_name() 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} + local player = self._target + local plyr_name = player and player:is_player() and player:get_player_name() + if plyr_name then + local plyr_pos = player and player:get_pos() + local trust = self.trust[plyr_name] or 0 + return (10 - (vec_dist(pos, plyr_pos) + trust)) * 0.1, {self, water[1]} end return 0 end @@ -177,14 +192,7 @@ creatura.register_mob("animalia:frog", { 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) + animalia.add_trust(self, clicker, 1) return end if animalia.set_nametag(self, clicker) then diff --git a/mobs/horse.lua b/mobs/horse.lua index b7d53a8..98f02bd 100644 --- a/mobs/horse.lua +++ b/mobs/horse.lua @@ -7,7 +7,7 @@ local random = math.random local follows = {} minetest.register_on_mods_loaded(function() - for name, def in pairs(minetest.registered_items) do + for name 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 @@ -16,42 +16,59 @@ minetest.register_on_mods_loaded(function() end end) -local function set_pattern(self) - local types = { - "spots", - "patches" +local patterns = { + "animalia_horse_pattern_1.png", + "animalia_horse_pattern_2.png", + "animalia_horse_pattern_3.png" +} + +local avlbl_colors = { + [1] = { + "animalia_horse_2.png", + "animalia_horse_3.png", + "animalia_horse_6.png" + }, + [2] = { + "animalia_horse_1.png", + "animalia_horse_6.png" + }, + [3] = { + "animalia_horse_2.png", + "animalia_horse_1.png" + }, + [4] = { + "animalia_horse_2.png", + "animalia_horse_1.png" + }, + [5] = { + "animalia_horse_2.png", + "animalia_horse_1.png" + }, + [6] = { + "animalia_horse_2.png", + "animalia_horse_1.png" } - 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} - }) - else - local type = types[random(#types)] - local overlay = "(animalia_horse_".. type ..".png)" - if type == "patches" then - local colors = { - "brown", - "white" - } - if self.texture_no < 1 then - table.insert(colors, "black") - else - table.remove(colors, 1) - end - overlay = "(animalia_horse_".. colors[random(#colors)] .."_patches.png)" +} + +local function set_pattern(self) + local pattern_no = self:recall("pattern_no") + if pattern_no and pattern_no < 1 then return end + if not pattern_no then + if random(3) < 2 then + pattern_no = self:memorize("pattern_no", random(#patterns)) + else + self:memorize("pattern_no", 0) + return end - if random(100) > 50 then - overlay = "transparency.png" - end - local texture = self.object:get_properties().textures[1] - self.object:set_properties({ - textures = {texture .. "^" .. overlay} - }) - self:memorize("pattern", overlay) - end + end + local colors = avlbl_colors[self.texture_no] + local color_no = self:recall("color_no") or self:memorize("color_no", random(#colors)) + if not colors[color_no] then return end + local pattern = "(" .. patterns[pattern_no] .. "^[mask:" .. colors[color_no] .. ")" + local texture = self.object:get_properties().textures[1] + self.object:set_properties({ + textures = {texture .. "^" .. pattern} + }) end creatura.register_mob("animalia:horse", { @@ -82,14 +99,16 @@ creatura.register_mob("animalia:horse", { "animalia_horse_6.png" }, animations = { - stand = {range = {x = 1, y = 60}, speed = 10, frame_blend = 0.3, loop = true}, - walk = {range = {x = 70, y = 110}, speed = 30, frame_blend = 0.3, loop = true}, - run = {range = {x = 120, y = 140}, speed = 30, frame_blend = 0.3, loop = true}, - rear = {range = {x = 150, y = 180}, speed = 27, frame_blend = 0.2, loop = false}, - rear_constant = {range = {x = 160, y = 170}, speed = 20, frame_blend = 0.3, loop = true}, - eat = {range = {x = 190, y = 220}, speed = 20, frame_blend = 0.3, loop = false} + stand = {range = {x = 1, y = 59}, speed = 10, frame_blend = 0.3, loop = true}, + walk = {range = {x = 61, y = 79}, speed = 20, frame_blend = 0.3, loop = true}, + run = {range = {x = 81, y = 99}, speed = 30, frame_blend = 0.3, loop = true}, + punch_aoe = {range = {x = 101, y = 119}, speed = 30, frame_blend = 0.2, loop = false}, + rear = {range = {x = 121, y = 140}, speed = 20, frame_blend = 0.2, loop = false}, + rear_constant = {range = {x = 121, y = 140}, speed = 20, frame_blend = 0.3, loop = false}, + eat = {range = {x = 141, y = 160}, speed = 20, frame_blend = 0.3, loop = false} }, -- Misc + step_delay = 0.25, catch_with_net = true, catch_with_lasso = true, sounds = { @@ -116,71 +135,101 @@ creatura.register_mob("animalia:horse", { }, follow = follows, consumable_nodes = { - { - name = "default:dirt_with_grass", - replacement = "default:dirt" - }, - { - name = "default:dry_dirt_with_dry_grass", - replacement = "default:dry_dirt" - } + ["default:dirt_with_grass"] = "default:dirt", + ["default:dry_dirt_with_dry_grass"] = "default:dry_dirt" }, head_data = { bone = "Neck.CTRL", - offset = {x = 0, y = 1.2, z = 0.15}, - pitch_correction = 45, + offset = {x = 0, y = 1.45, z = 0.0}, + pitch_correction = 25, pivot_h = 1, pivot_v = 1.5 }, -- Function + wander_action = animalia.action_move_flock, utility_stack = { - [1] = { - utility = "animalia:boid_wander", + { + utility = "animalia:wander_group", + step_delay = 0.25, get_score = function(self) - return 0.1, {self, true} + return 0.1, {self} end }, - [2] = { - utility = "animalia:eat_from_turf", + { + utility = "animalia:eat_turf", + step_delay = 0.25, get_score = function(self) - return math.random(11) * 0.01, {self} + if random(64) < 2 then + return 0.2, {self} + end + return 0 end }, - [3] = { + { utility = "animalia:swim_to_land", + step_delay = 0.25, get_score = function(self) if self.in_liquid then - return 0.95, {self} + return 0.3, {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} + local lasso = type(self.lasso_origin or {}) == "userdata" and self.lasso_origin + local force = lasso and lasso ~= false + local player = (force and lasso) or creatura.get_nearby_player(self) + if player + and self:follow_wielded_item(player) then + return 0.4, {self, player} end return 0 end }, - [5] = { - utility = "animalia:horse_breed", + { + utility = "animalia:breed", + step_delay = 0.25, get_score = function(self) if self.breeding and animalia.get_nearby_mate(self, self.name) then - return 0.9, {self} + return 0.5, {self} end return 0 end }, - [6] = { - utility = "animalia:mount", + { + utility = "animalia:flee_from_target_defend", get_score = function(self) - if self.rider - and self.saddled then - return 1, {self, self.rider} + local puncher = self._puncher + if puncher + and puncher:get_pos() then + return 0.6, {self, puncher} + end + self._puncher = nil + return 0 + end + }, + { + utility = "animalia:tame_horse", + get_score = function(self) + local rider = not self.owner and self.rider + if rider + and rider:get_pos() then + return 0.7, {self} + end + return 0 + end + }, + { + utility = "animalia:mount_horse", + get_score = function(self) + local owner = self.owner and minetest.get_player_by_name(self.owner) + local rider = owner == self.rider and self.rider + if rider + and rider:get_pos() then + return 0.8, {self, rider} end return 0 end @@ -219,43 +268,6 @@ creatura.register_mob("animalia:horse", { 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:horse_breaking", self) - 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) - end - end - end end, death_func = function(self) if self:get_utility() ~= "animalia:die" then @@ -275,7 +287,7 @@ creatura.register_mob("animalia:horse", { and self.owner == clicker:get_player_name() then if self.saddled and tool_name == "" then - animalia.mount(self, clicker, {rot = {x = -60, y = 180, z = 0}, pos = {x = 0, y = 1.1, z = 0.5}}) + animalia.mount(self, clicker, {rot = {x = -75, y = 180, z = 0}, pos = {x = 0, y = 0.6, z = 0.5}}) self:initiate_utility("animalia:mount", self, clicker) elseif tool_name == "animalia:saddle" then self.saddled = self:memorize("saddled", true) @@ -293,16 +305,14 @@ creatura.register_mob("animalia:horse", { 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, 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) + on_punch = function(self, puncher, ...) + if self.rider and puncher == self.rider then return end + creatura.basic_punch_func(self, puncher, ...) + if self.hp < 0 then return end + self._puncher = puncher end }) diff --git a/mobs/pig.lua b/mobs/pig.lua index d5d25e3..7429d8d 100644 --- a/mobs/pig.lua +++ b/mobs/pig.lua @@ -5,7 +5,7 @@ local follows = {} minetest.register_on_mods_loaded(function() - for name, def in pairs(minetest.registered_items) do + for name 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 @@ -64,6 +64,7 @@ creatura.register_mob("animalia:pig", { run = {range = {x = 1, y = 20}, speed = 45, frame_blend = 0.3, loop = true}, }, -- Misc + step_delay = 0.25, consumable_nodes = destroyable_crops, birth_count = 2, catch_with_net = true, @@ -93,24 +94,27 @@ creatura.register_mob("animalia:pig", { utility_stack = { [1] = { utility = "animalia:wander", + step_delay = 0.25, get_score = function(self) return 0.1, {self, true} end }, [2] = { utility = "animalia:eat_from_turf", + step_delay = 0.25, get_score = function(self) if math.random(25) < 2 then - return 0.1, {self} + return 0.2, {self} end return 0 end }, [3] = { utility = "animalia:swim_to_land", + step_delay = 0.25, get_score = function(self) if self.in_liquid then - return 1, {self} + return 0.3, {self} end return 0 end @@ -118,24 +122,23 @@ creatura.register_mob("animalia:pig", { [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) + local lasso = type(self.lasso_origin or {}) == "userdata" and self.lasso_origin + local force = lasso and lasso ~= false + local player = (force and lasso) or creatura.get_nearby_player(self) if player and self:follow_wielded_item(player) then - return 0.8, {self, player} + return 0.4, {self, player} end return 0 end }, [5] = { - utility = "animalia:mammal_breed", + utility = "animalia:breed", + step_delay = 0.25, get_score = function(self) if self.breeding and animalia.get_nearby_mate(self, self.name) then - return 0.9, {self} + return 0.5, {self} end return 0 end @@ -166,8 +169,7 @@ creatura.register_mob("animalia:pig", { 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) + self._target = puncher end }) diff --git a/mobs/reindeer.lua b/mobs/reindeer.lua index 2fc256a..b0caa22 100644 --- a/mobs/reindeer.lua +++ b/mobs/reindeer.lua @@ -5,7 +5,7 @@ local follows = {} minetest.register_on_mods_loaded(function() - for name, def in pairs(minetest.registered_items) do + for name 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 @@ -43,6 +43,7 @@ creatura.register_mob("animalia:reindeer", { run = {range = {x = 70, y = 110}, speed = 50, frame_blend = 0.3, loop = true}, }, -- Misc + step_delay = 0.25, catch_with_net = true, catch_with_lasso = true, drops = { @@ -68,54 +69,68 @@ creatura.register_mob("animalia:reindeer", { }, -- Function utility_stack = { - [1] = { - utility = "animalia:boid_wander", + { + utility = "animalia:wander_group", + step_delay = 0.25, get_score = function(self) - return 0.1, {self, true} + return 0.1, {self} end }, - [2] = { - utility = "animalia:eat_from_turf", + { + utility = "animalia:eat_turf", + step_delay = 0.25, get_score = function(self) - if math.random(25) < 2 then - return 0.1, {self} + if random(64) < 2 then + return 0.2, {self} end return 0 end }, - [3] = { + { utility = "animalia:swim_to_land", + step_delay = 0.25, get_score = function(self) if self.in_liquid then - return 1, {self} + return 0.3, {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) + local lasso = type(self.lasso_origin or {}) == "userdata" and self.lasso_origin + local force = lasso and lasso ~= false + local player = (force and lasso) or creatura.get_nearby_player(self) if player and self:follow_wielded_item(player) then - return 0.8, {self, player} + return 0.4, {self, player} end return 0 end }, - [5] = { - utility = "animalia:mammal_breed", + { + utility = "animalia:breed", + step_delay = 0.25, get_score = function(self) if self.breeding and animalia.get_nearby_mate(self, self.name) then - return 0.9, {self} + return 0.5, {self} end return 0 end + }, + { + utility = "animalia:flee_from_target", + get_score = function(self) + local puncher = self._target + if puncher + and puncher:get_pos() then + return 0.6, {self, puncher} + end + self._target = nil + return 0 + end } }, activate_func = function(self) @@ -144,8 +159,7 @@ creatura.register_mob("animalia:reindeer", { 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:boid_flee_from_player", self, puncher, true) - self:set_utility_score(1) + self._target = puncher end }) diff --git a/mobs/sheep.lua b/mobs/sheep.lua index 3965f01..67ae3bd 100644 --- a/mobs/sheep.lua +++ b/mobs/sheep.lua @@ -2,10 +2,12 @@ -- Sheep -- ----------- +local random = math.random + local follows = {} minetest.register_on_mods_loaded(function() - for name, def in pairs(minetest.registered_items) do + for name 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 @@ -69,6 +71,7 @@ creatura.register_mob("animalia:sheep", { run = {range = {x = 70, y = 110}, speed = 50, frame_blend = 0.3, loop = true}, }, -- Misc + step_delay = 0.25, catch_with_net = true, catch_with_lasso = true, sounds = { @@ -94,14 +97,8 @@ creatura.register_mob("animalia:sheep", { }, follow = follows, consumable_nodes = { - { - name = "default:dirt_with_grass", - replacement = "default:dirt" - }, - { - name = "default:dry_dirt_with_dry_grass", - replacement = "default:dry_dirt" - } + ["default:dirt_with_grass"] = "default:dirt", + ["default:dry_dirt_with_dry_grass"] = "default:dry_dirt" }, head_data = { offset = {x = 0, y = 0.41, z = 0}, @@ -111,67 +108,83 @@ creatura.register_mob("animalia:sheep", { }, -- Function utility_stack = { - [1] = { - utility = "animalia:wander", + { + utility = "animalia:wander_group", + step_delay = 0.25, get_score = function(self) - return 0.1, {self, true} + return 0.1, {self} end }, - [2] = { - utility = "animalia:eat_from_turf", + { + utility = "animalia:eat_turf", + step_delay = 0.25, get_score = function(self) - if math.random(25) < 2 then - return 0.1, {self} + if random(64) < 2 then + return 0.2, {self} end return 0 end }, - [3] = { + { utility = "animalia:swim_to_land", + step_delay = 0.25, get_score = function(self) if self.in_liquid then - return 1, {self} + return 0.3, {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} + return 0.4, {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} + return 0.4, {self, player} end return 0 end }, - [5] = { - utility = "animalia:mammal_breed", + { + utility = "animalia:breed", + step_delay = 0.25, get_score = function(self) if self.breeding and animalia.get_nearby_mate(self, self.name) then - return 0.9, {self} + return 0.5, {self} end return 0 end + }, + { + utility = "animalia:flee_from_target", + get_score = function(self) + local puncher = self._target + if puncher + and puncher:get_pos() then + return 0.6, {self, puncher} + end + self._target = nil + return 0 + end } }, activate_func = function(self) - self.gotten = self:recall("gotten") or false + self.collected = self:recall("collected") 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 + and not self.collected then self.object:set_properties({ textures = {"animalia_sheep.png^(animalia_sheep_wool.png^[colorize:" .. self.dye_hex .. ")"}, }) end - if self.gotten then + if self.collected then self.object:set_properties({ textures = {"animalia_sheep.png"}, }) @@ -202,7 +215,7 @@ creatura.register_mob("animalia:sheep", { local tool = clicker:get_wielded_item() local tool_name = tool:get_name() if tool_name == "animalia:shears" - and not self.gotten + and not self.collected and self.growth_scale > 0.9 then if not minetest.get_modpath("wool") then return @@ -213,7 +226,7 @@ creatura.register_mob("animalia:sheep", { ItemStack( "wool:" .. self.dye_color .. " " .. math.random(1, 3) ) ) - self.gotten = self:memorize("gotten", true) + self.collected = self:memorize("collected", true) self.dye_color = self:memorize("dye_color", "white") self.dye_hex = self:memorize("dye_hex", "#abababc000") @@ -227,9 +240,9 @@ creatura.register_mob("animalia:sheep", { end for _, color in ipairs(palette) do if tool_name:find("dye:") - and not self.gotten + and not self.collected and self.growth_scale > 0.9 then - local dye = string.split(tool_name, ":")[2] + local dye = tool_name:split(":")[2] if color[1] == dye then self.dye_color = self:memorize("dye_color", color[1]) @@ -256,9 +269,8 @@ creatura.register_mob("animalia:sheep", { 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:boid_flee_from_player", self, puncher, true) - self:set_utility_score(1) + self._target = puncher end }) -creatura.register_spawn_egg("animalia:sheep", "f4e6cf", "e1ca9b") +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 index 22f2c6e..bc28b98 100644 --- a/mobs/tropical_fish.lua +++ b/mobs/tropical_fish.lua @@ -12,7 +12,7 @@ creatura.register_mob("animalia:tropical_fish", { despawn_after = 2500, -- Entity Physics stepheight = 0.1, - max_fall = 8, + max_fall = 0, turn_rate = 8, boid_seperation = 0.3, bouyancy_multiplier = 0, @@ -33,19 +33,22 @@ creatura.register_mob("animalia:tropical_fish", { flop = {range = {x = 30, y = 40}, speed = 20, frame_blend = 0.3, loop = true}, }, -- Misc + step_delay = 0.25, catch_with_net = true, catch_with_lasso = false, makes_footstep_sound = false, -- Function utility_stack = { { - utility = "animalia:schooling", + utility = "animalia:aquatic_wander_school", + step_delay = 0.25, get_score = function(self) return 0.1, {self} end }, { utility = "animalia:flop", + step_delay = 0.25, get_score = function(self) if not self.in_liquid then self:hurt(1) diff --git a/mobs/turkey.lua b/mobs/turkey.lua index 97c31de..a4542e2 100644 --- a/mobs/turkey.lua +++ b/mobs/turkey.lua @@ -5,7 +5,7 @@ local follows = {} minetest.register_on_mods_loaded(function() - for name, def in pairs(minetest.registered_items) do + for name in pairs(minetest.registered_items) do if name:match(":seed_") or name:match("_seed") then table.insert(follows, name) @@ -41,6 +41,7 @@ creatura.register_mob("animalia:turkey", { fall = {range = {x = 70, y = 90}, speed = 30, frame_blend = 0.3, loop = true}, }, -- Misc + step_delay = 0.25, catch_with_net = true, catch_with_lasso = true, sounds = { @@ -72,55 +73,78 @@ creatura.register_mob("animalia:turkey", { pivot_v = 0.65 }, -- Function + add_child = function(self) + local pos = self.object:get_pos() + if not pos then return end + 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", + }) + 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, + wander_action = creatura.action_move, utility_stack = { - [1] = { - utility = "animalia:wander", + { + utility = "animalia:wander_group", get_score = function(self) - return 0.1, {self, true} + return 0.1, {self} 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", + { + utility = "animalia:swim_to_Land", get_score = function(self) if self.in_liquid then - return 1, {self} + return 0.5, {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) + local lasso = type(self.lasso_origin or {}) == "userdata" and self.lasso_origin + local force = lasso and lasso ~= false + local player = (force and lasso) or creatura.get_nearby_player(self) if player and self:follow_wielded_item(player) then - return 0.8, {self, player} + return 0.3, {self, player} end return 0 end }, - [5] = { - utility = "animalia:bird_breed", + { + utility = "animalia:breed", get_score = function(self) if self.breeding and animalia.get_nearby_mate(self, self.name) then - return 0.9, {self} + return 0.4, {self} end return 0 end + }, + { + utility = "animalia:flee_from_target", + get_score = function(self) + local puncher = self._target + if puncher + and puncher:get_pos() then + return 0.6, {self, puncher} + end + self._target = nil + return 0 + end } }, activate_func = function(self) @@ -132,6 +156,10 @@ creatura.register_mob("animalia:turkey", { animalia.head_tracking(self, 0.75, 0.75) animalia.do_growth(self, 60) animalia.update_lasso_effects(self) + if self.fall_start then + self:set_gravity(-4.9) + self:animate("fall") + end end, death_func = function(self) if self:get_utility() ~= "animalia:die" then @@ -149,8 +177,7 @@ creatura.register_mob("animalia:turkey", { 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) + self._target = puncher end }) diff --git a/mobs/wolf.lua b/mobs/wolf.lua index f1d9151..3ace9c6 100644 --- a/mobs/wolf.lua +++ b/mobs/wolf.lua @@ -2,7 +2,20 @@ -- Wolf -- ---------- -local vec_dist = vector.distance +local function shared_owner(obj1, obj2) + if not obj1 or not obj2 then return false end + obj1 = creatura.is_valid(obj1) + obj2 = creatura.is_valid(obj2) + if obj1 + and obj2 + and obj1:get_luaentity() + and obj2:get_luaentity() then + obj1 = obj1:get_luaentity() + obj2 = obj2:get_luaentity() + return obj1.owner and obj2.owner and obj1.owner == obj2.owner + end + return false +end local follow = { "animalia:mutton_raw", @@ -40,6 +53,7 @@ creatura.register_mob("animalia:wolf", { despawn_after = 2000, -- Entity Physics stepheight = 1.1, + max_fall = 3, -- Visuals mesh = "animalia_wolf.b3d", hitbox = { @@ -47,117 +61,91 @@ creatura.register_mob("animalia:wolf", { height = 0.7 }, visual_size = {x = 9, y = 9}, - textures = {"animalia_wolf.png"}, + textures = { + "animalia_wolf_1.png", + "animalia_wolf_2.png", + "animalia_wolf_3.png", + "animalia_wolf_4.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} + stand = {range = {x = 1, y = 39}, speed = 10, frame_blend = 0.3, loop = true}, + walk = {range = {x = 41, y = 59}, speed = 30, frame_blend = 0.3, loop = true}, + run = {range = {x = 41, y = 59}, speed = 45, frame_blend = 0.3, loop = true}, + sit = {range = {x = 61, y = 79}, speed = 20, frame_blend = 0.3, loop = true}, }, -- Misc + step_delay = 0.25, catch_with_net = true, catch_with_lasso = true, assist_owner = true, follow = follow, head_data = { - offset = {x = 0, y = 0.22, z = 0}, - pitch_correction = -25, + offset = {x = 0, y = 0.33, z = 0}, + pitch_correction = -67, pivot_h = 0.65, pivot_v = 0.65 }, -- Function utility_stack = { - [1] = { - utility = "animalia:skittish_boid_wander", + { + utility = "animalia:wander_skittish", + step_delay = 0.25, get_score = function(self) - return 0.1, {self, true} + return 0.1, {self} end }, - [2] = { + { utility = "animalia:swim_to_land", + step_delay = 0.25, get_score = function(self) if self.in_liquid then - return 0.9, {self} + return 0.3, {self} end return 0 end }, - [3] = { - utility = "animalia:attack", + { + utility = "animalia:attack_target", 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 - and player:get_player_name() 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] - and self._utility_data.args[2] == target then - return 0 - end - return 0.85, {self, target} + local order = self.order or "wander" + if order ~= "wander" then return 0 end + local target = self._target or creatura.get_nearby_object(self, "animalia:sheep") + if target + and not shared_owner(self, target) then + return 0.4, {self, target} end return 0 end }, - [4] = { - utility = "animalia:flee_from_player", + { + utility = "animalia:stay", + step_delay = 0.25, get_score = function(self) - local player = self._nearby_player - if player - and player:get_player_name() 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 + local order = self.order or "wander" + if order == "sit" then + return 0.5, {self} 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} + local lasso = type(self.lasso_origin or {}) == "userdata" and self.lasso_origin + local owner = self.owner and self.order == "follow" and minetest.get_player_by_name(self.owner) + local force = (lasso and lasso ~= false) or owner + local player = (force and (owner or lasso)) or creatura.get_nearby_player(self) + if player + and (self:follow_wielded_item(player) + or force) then + return 0.6, {self, player, force} end return 0 end }, - [7] = { - utility = "animalia:mammal_breed", + { + utility = "animalia:breed", + step_delay = 0.25, get_score = function(self) if self.breeding and animalia.get_nearby_mate(self, self.name) then @@ -170,7 +158,6 @@ creatura.register_mob("animalia:wolf", { 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 {} @@ -194,10 +181,9 @@ creatura.register_mob("animalia:wolf", { end, on_rightclick = function(self, clicker) if not clicker:is_player() then return end + local name = clicker:get_player_name() local passive = true - if is_value_in_table(self.enemies, clicker:get_player_name()) then - passive = false - end + if is_value_in_table(self.enemies, name) then passive = false end if animalia.feed(self, clicker, passive, passive) then return end @@ -205,21 +191,21 @@ creatura.register_mob("animalia:wolf", { return end if self.owner - and clicker:get_player_name() == self.owner + and 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") + minetest.chat_send_player(name, "Wolf is following") self.order = "follow" self:initiate_utility("animalia:follow_player", self, clicker, true) - self:set_utility_score(1) + self:set_utility_score(0.7) elseif order == "follow" then - minetest.chat_send_player(clicker:get_player_name(), "Wolf is sitting") + minetest.chat_send_player(name, "Wolf is sitting") self.order = "sit" - self:initiate_utility("animalia:sit", self) - self:set_utility_score(0.8) + self:initiate_utility("animalia:stay", self) + self:set_utility_score(0.5) else - minetest.chat_send_player(clicker:get_player_name(), "Wolf is wandering") + minetest.chat_send_player(name, "Wolf is wandering") self.order = "wander" self:set_utility_score(0) end @@ -229,24 +215,24 @@ creatura.register_mob("animalia:wolf", { 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) - if puncher:is_player() then + local name = puncher:is_player() and puncher:get_player_name() + if name then if self.owner - and puncher:get_player_name() == self.owner then + and 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()) + elseif not is_value_in_table(self.enemies, name) then + table.insert(self.enemies, 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()) + table.insert(self.enemies, name) self.enemies = self:memorize("enemies", self.enemies) end end - self:initiate_utility("animalia:attack", self, puncher, true) - self:set_utility_score(1) + self._target = puncher end, deactivate_func = function(self) if self.owner then diff --git a/mod.conf b/mod.conf index 5a688cf..60afc8f 100644 --- a/mod.conf +++ b/mod.conf @@ -2,5 +2,6 @@ name = animalia depends = creatura optional_depends = default, mcl_player description = Adds unique and consistantly designed Animals +release = 11481 author = ElCeejo title = Animalia diff --git a/models/animalia_cat.b3d b/models/animalia_cat.b3d index de68a36..55aec52 100644 Binary files a/models/animalia_cat.b3d and b/models/animalia_cat.b3d differ diff --git a/models/animalia_chicken.b3d b/models/animalia_chicken.b3d index acf1cc2..5870d00 100644 Binary files a/models/animalia_chicken.b3d and b/models/animalia_chicken.b3d differ diff --git a/models/animalia_cow.b3d b/models/animalia_cow.b3d index 91bea08..f8e2931 100644 Binary files a/models/animalia_cow.b3d and b/models/animalia_cow.b3d differ diff --git a/models/animalia_horse.b3d b/models/animalia_horse.b3d index 8863724..46b807f 100644 Binary files a/models/animalia_horse.b3d and b/models/animalia_horse.b3d differ diff --git a/models/animalia_wolf.b3d b/models/animalia_wolf.b3d index 080b4c3..49cf21b 100644 Binary files a/models/animalia_wolf.b3d and b/models/animalia_wolf.b3d differ diff --git a/textures/bat/animalia_bat_1.png b/textures/bat/animalia_bat_1.png index aecea9b..0fb401c 100644 Binary files a/textures/bat/animalia_bat_1.png and b/textures/bat/animalia_bat_1.png differ diff --git a/textures/bat/animalia_bat_2.png b/textures/bat/animalia_bat_2.png index c71eb94..bbc9163 100644 Binary files a/textures/bat/animalia_bat_2.png and b/textures/bat/animalia_bat_2.png differ diff --git a/textures/bat/animalia_bat_3.png b/textures/bat/animalia_bat_3.png index 66dde2f..3d2825f 100644 Binary files a/textures/bat/animalia_bat_3.png and b/textures/bat/animalia_bat_3.png differ diff --git a/textures/cat/animalia_cat_1.png b/textures/cat/animalia_cat_1.png index ed44d56..89da6ec 100644 Binary files a/textures/cat/animalia_cat_1.png and b/textures/cat/animalia_cat_1.png differ diff --git a/textures/cat/animalia_cat_2.png b/textures/cat/animalia_cat_2.png index 96d3497..e60daec 100644 Binary files a/textures/cat/animalia_cat_2.png and b/textures/cat/animalia_cat_2.png differ diff --git a/textures/cat/animalia_cat_3.png b/textures/cat/animalia_cat_3.png index 0a49e1d..3fa1f82 100644 Binary files a/textures/cat/animalia_cat_3.png and b/textures/cat/animalia_cat_3.png differ diff --git a/textures/cat/animalia_cat_4.png b/textures/cat/animalia_cat_4.png index 6fbd523..a55fe13 100644 Binary files a/textures/cat/animalia_cat_4.png and b/textures/cat/animalia_cat_4.png differ diff --git a/textures/cat/animalia_cat_5.png b/textures/cat/animalia_cat_5.png new file mode 100644 index 0000000..40cd7ab Binary files /dev/null and b/textures/cat/animalia_cat_5.png differ diff --git a/textures/cat/animalia_cat_6.png b/textures/cat/animalia_cat_6.png new file mode 100644 index 0000000..3842974 Binary files /dev/null and b/textures/cat/animalia_cat_6.png differ diff --git a/textures/cat/animalia_cat_7.png b/textures/cat/animalia_cat_7.png new file mode 100644 index 0000000..55b2898 Binary files /dev/null and b/textures/cat/animalia_cat_7.png differ diff --git a/textures/cat/animalia_cat_8.png b/textures/cat/animalia_cat_8.png new file mode 100644 index 0000000..f8b366a Binary files /dev/null and b/textures/cat/animalia_cat_8.png differ diff --git a/textures/cat/animalia_cat_9.png b/textures/cat/animalia_cat_9.png new file mode 100644 index 0000000..060ad9c Binary files /dev/null and b/textures/cat/animalia_cat_9.png differ diff --git a/textures/cat/animalia_cat_ash.png b/textures/cat/animalia_cat_ash.png new file mode 100644 index 0000000..82a4f9f Binary files /dev/null and b/textures/cat/animalia_cat_ash.png differ diff --git a/textures/cat/animalia_cat_birch.png b/textures/cat/animalia_cat_birch.png new file mode 100644 index 0000000..9083786 Binary files /dev/null and b/textures/cat/animalia_cat_birch.png differ diff --git a/textures/chicken/animalia_chick.png b/textures/chicken/animalia_chick.png deleted file mode 100644 index 7cdc20c..0000000 Binary files a/textures/chicken/animalia_chick.png and /dev/null differ diff --git a/textures/chicken/animalia_chicken_1.png b/textures/chicken/animalia_chicken_1.png index 13b9ce6..c98069b 100644 Binary files a/textures/chicken/animalia_chicken_1.png and b/textures/chicken/animalia_chicken_1.png differ diff --git a/textures/chicken/animalia_chicken_2.png b/textures/chicken/animalia_chicken_2.png index 27f71e6..3bd3077 100644 Binary files a/textures/chicken/animalia_chicken_2.png and b/textures/chicken/animalia_chicken_2.png differ diff --git a/textures/chicken/animalia_chicken_3.png b/textures/chicken/animalia_chicken_3.png index 79b2d64..6e6003f 100644 Binary files a/textures/chicken/animalia_chicken_3.png and b/textures/chicken/animalia_chicken_3.png differ diff --git a/textures/chicken/animalia_chicken_child.png b/textures/chicken/animalia_chicken_child.png new file mode 100644 index 0000000..53f9f39 Binary files /dev/null and b/textures/chicken/animalia_chicken_child.png differ diff --git a/textures/chicken/animalia_rooster_1.png b/textures/chicken/animalia_rooster_1.png index b610dfa..5182905 100644 Binary files a/textures/chicken/animalia_rooster_1.png and b/textures/chicken/animalia_rooster_1.png differ diff --git a/textures/chicken/animalia_rooster_2.png b/textures/chicken/animalia_rooster_2.png index dcc6254..e02a4a3 100644 Binary files a/textures/chicken/animalia_rooster_2.png and b/textures/chicken/animalia_rooster_2.png differ diff --git a/textures/chicken/animalia_rooster_3.png b/textures/chicken/animalia_rooster_3.png index 0d04a41..6949779 100644 Binary files a/textures/chicken/animalia_rooster_3.png and b/textures/chicken/animalia_rooster_3.png differ diff --git a/textures/cow/animalia_cow_1.png b/textures/cow/animalia_cow_1.png index 7158b0e..8ed08ab 100644 Binary files a/textures/cow/animalia_cow_1.png and b/textures/cow/animalia_cow_1.png differ diff --git a/textures/cow/animalia_cow_2.png b/textures/cow/animalia_cow_2.png index 6a366ad..c54b568 100644 Binary files a/textures/cow/animalia_cow_2.png and b/textures/cow/animalia_cow_2.png differ diff --git a/textures/cow/animalia_cow_3.png b/textures/cow/animalia_cow_3.png index bf78cd3..24272c1 100644 Binary files a/textures/cow/animalia_cow_3.png and b/textures/cow/animalia_cow_3.png differ diff --git a/textures/cow/animalia_cow_4.png b/textures/cow/animalia_cow_4.png index 18306cd..75283bc 100644 Binary files a/textures/cow/animalia_cow_4.png and b/textures/cow/animalia_cow_4.png differ diff --git a/textures/cow/animalia_cow_udder.png b/textures/cow/animalia_cow_udder.png index 1274a55..414e499 100644 Binary files a/textures/cow/animalia_cow_udder.png and b/textures/cow/animalia_cow_udder.png differ diff --git a/textures/formspecs/libri/icons/animalia_libri_boreal_icon.png b/textures/formspecs/libri/icons/animalia_libri_boreal_icon.png deleted file mode 100644 index 6d9dd9b..0000000 Binary files a/textures/formspecs/libri/icons/animalia_libri_boreal_icon.png and /dev/null differ diff --git a/textures/formspecs/libri/icons/animalia_libri_grassland_icon.png b/textures/formspecs/libri/icons/animalia_libri_grassland_icon.png deleted file mode 100644 index 55fafc7..0000000 Binary files a/textures/formspecs/libri/icons/animalia_libri_grassland_icon.png and /dev/null differ diff --git a/textures/formspecs/libri/icons/animalia_libri_temperate_icon.png b/textures/formspecs/libri/icons/animalia_libri_temperate_icon.png deleted file mode 100644 index 6302810..0000000 Binary files a/textures/formspecs/libri/icons/animalia_libri_temperate_icon.png and /dev/null differ diff --git a/textures/formspecs/libri/icons/animalia_libri_tropical_icon.png b/textures/formspecs/libri/icons/animalia_libri_tropical_icon.png deleted file mode 100644 index 855cb6d..0000000 Binary files a/textures/formspecs/libri/icons/animalia_libri_tropical_icon.png and /dev/null differ diff --git a/textures/horse/animalia_horse_1.png b/textures/horse/animalia_horse_1.png index 1d959cd..972e2ae 100644 Binary files a/textures/horse/animalia_horse_1.png and b/textures/horse/animalia_horse_1.png differ diff --git a/textures/horse/animalia_horse_2.png b/textures/horse/animalia_horse_2.png index dde04b8..9c494cb 100644 Binary files a/textures/horse/animalia_horse_2.png and b/textures/horse/animalia_horse_2.png differ diff --git a/textures/horse/animalia_horse_3.png b/textures/horse/animalia_horse_3.png index 081378e..ae9a18a 100644 Binary files a/textures/horse/animalia_horse_3.png and b/textures/horse/animalia_horse_3.png differ diff --git a/textures/horse/animalia_horse_4.png b/textures/horse/animalia_horse_4.png index 883fe76..6168e67 100644 Binary files a/textures/horse/animalia_horse_4.png and b/textures/horse/animalia_horse_4.png differ diff --git a/textures/horse/animalia_horse_5.png b/textures/horse/animalia_horse_5.png index a0715d3..d24b465 100644 Binary files a/textures/horse/animalia_horse_5.png and b/textures/horse/animalia_horse_5.png differ diff --git a/textures/horse/animalia_horse_6.png b/textures/horse/animalia_horse_6.png index d2aa63f..1e3efee 100644 Binary files a/textures/horse/animalia_horse_6.png and b/textures/horse/animalia_horse_6.png differ diff --git a/textures/horse/animalia_horse_black_patches.png b/textures/horse/animalia_horse_black_patches.png deleted file mode 100644 index d8467a0..0000000 Binary files a/textures/horse/animalia_horse_black_patches.png and /dev/null differ diff --git a/textures/horse/animalia_horse_brown_patches.png b/textures/horse/animalia_horse_brown_patches.png deleted file mode 100644 index 9d22545..0000000 Binary files a/textures/horse/animalia_horse_brown_patches.png and /dev/null differ diff --git a/textures/horse/animalia_horse_cream_patches.png b/textures/horse/animalia_horse_cream_patches.png deleted file mode 100644 index 98c3b72..0000000 Binary files a/textures/horse/animalia_horse_cream_patches.png and /dev/null differ diff --git a/textures/horse/animalia_horse_patches.png b/textures/horse/animalia_horse_patches.png deleted file mode 100644 index 8275406..0000000 Binary files a/textures/horse/animalia_horse_patches.png and /dev/null differ diff --git a/textures/horse/animalia_horse_pattern_1.png b/textures/horse/animalia_horse_pattern_1.png new file mode 100644 index 0000000..6c0172a Binary files /dev/null and b/textures/horse/animalia_horse_pattern_1.png differ diff --git a/textures/horse/animalia_horse_pattern_2.png b/textures/horse/animalia_horse_pattern_2.png new file mode 100644 index 0000000..4a594ed Binary files /dev/null and b/textures/horse/animalia_horse_pattern_2.png differ diff --git a/textures/horse/animalia_horse_pattern_3.png b/textures/horse/animalia_horse_pattern_3.png new file mode 100644 index 0000000..5d7235f Binary files /dev/null and b/textures/horse/animalia_horse_pattern_3.png differ diff --git a/textures/horse/animalia_horse_saddle.png b/textures/horse/animalia_horse_saddle.png index 612b920..5c4238c 100644 Binary files a/textures/horse/animalia_horse_saddle.png and b/textures/horse/animalia_horse_saddle.png differ diff --git a/textures/horse/animalia_horse_spots.png b/textures/horse/animalia_horse_spots.png deleted file mode 100644 index defe306..0000000 Binary files a/textures/horse/animalia_horse_spots.png and /dev/null differ diff --git a/textures/horse/animalia_horse_white_patches.png b/textures/horse/animalia_horse_white_patches.png deleted file mode 100644 index d699d6c..0000000 Binary files a/textures/horse/animalia_horse_white_patches.png and /dev/null differ diff --git a/textures/items/animalia_beef_cooked.png b/textures/items/animalia_beef_cooked.png index 1c8605c..ed30f7a 100644 Binary files a/textures/items/animalia_beef_cooked.png and b/textures/items/animalia_beef_cooked.png differ diff --git a/textures/items/animalia_beef_raw.png b/textures/items/animalia_beef_raw.png index 7f9cfcf..5c10b57 100644 Binary files a/textures/items/animalia_beef_raw.png and b/textures/items/animalia_beef_raw.png differ diff --git a/textures/items/animalia_cat_toy.png b/textures/items/animalia_cat_toy.png index 3926a2a..d115c49 100644 Binary files a/textures/items/animalia_cat_toy.png and b/textures/items/animalia_cat_toy.png differ diff --git a/textures/items/animalia_egg.png b/textures/items/animalia_egg.png index e8b8148..0dee08a 100644 Binary files a/textures/items/animalia_egg.png and b/textures/items/animalia_egg.png differ diff --git a/textures/items/animalia_egg_fried.png b/textures/items/animalia_egg_fried.png index f3462d5..25a9918 100644 Binary files a/textures/items/animalia_egg_fried.png and b/textures/items/animalia_egg_fried.png differ diff --git a/textures/items/animalia_lasso.png b/textures/items/animalia_lasso.png index e5910e8..85bf814 100644 Binary files a/textures/items/animalia_lasso.png and b/textures/items/animalia_lasso.png differ diff --git a/textures/items/animalia_leather.png b/textures/items/animalia_leather.png index 7ec5bb1..0d0b073 100644 Binary files a/textures/items/animalia_leather.png and b/textures/items/animalia_leather.png differ diff --git a/textures/items/animalia_libri_animalia.png b/textures/items/animalia_libri_animalia.png index 9fe72c5..015d51e 100644 Binary files a/textures/items/animalia_libri_animalia.png and b/textures/items/animalia_libri_animalia.png differ diff --git a/textures/items/animalia_mutton_cooked.png b/textures/items/animalia_mutton_cooked.png index 9fe2e79..577a560 100644 Binary files a/textures/items/animalia_mutton_cooked.png and b/textures/items/animalia_mutton_cooked.png differ diff --git a/textures/items/animalia_mutton_raw.png b/textures/items/animalia_mutton_raw.png index 09801d9..44bcc02 100644 Binary files a/textures/items/animalia_mutton_raw.png and b/textures/items/animalia_mutton_raw.png differ diff --git a/textures/items/animalia_porkchop_cooked.png b/textures/items/animalia_porkchop_cooked.png index 8b542bb..8bc89ce 100644 Binary files a/textures/items/animalia_porkchop_cooked.png and b/textures/items/animalia_porkchop_cooked.png differ diff --git a/textures/items/animalia_porkchop_raw.png b/textures/items/animalia_porkchop_raw.png index 6510b6a..24496dc 100644 Binary files a/textures/items/animalia_porkchop_raw.png and b/textures/items/animalia_porkchop_raw.png differ diff --git a/textures/items/animalia_poultry_cooked.png b/textures/items/animalia_poultry_cooked.png index 77b31bf..da0a308 100644 Binary files a/textures/items/animalia_poultry_cooked.png and b/textures/items/animalia_poultry_cooked.png differ diff --git a/textures/items/animalia_poultry_raw.png b/textures/items/animalia_poultry_raw.png index d64f952..49a9693 100644 Binary files a/textures/items/animalia_poultry_raw.png and b/textures/items/animalia_poultry_raw.png differ diff --git a/textures/items/animalia_saddle.png b/textures/items/animalia_saddle.png index 092a55d..f6f9f97 100644 Binary files a/textures/items/animalia_saddle.png and b/textures/items/animalia_saddle.png differ diff --git a/textures/items/animalia_shears.png b/textures/items/animalia_shears.png index 6f1eade..b5af03c 100644 Binary files a/textures/items/animalia_shears.png and b/textures/items/animalia_shears.png differ diff --git a/textures/items/animalia_venison_cooked.png b/textures/items/animalia_venison_cooked.png index e699e8d..ccf30ab 100644 Binary files a/textures/items/animalia_venison_cooked.png and b/textures/items/animalia_venison_cooked.png differ diff --git a/textures/items/animalia_venison_raw.png b/textures/items/animalia_venison_raw.png index 474cc3f..0cffb33 100644 Binary files a/textures/items/animalia_venison_raw.png and b/textures/items/animalia_venison_raw.png differ diff --git a/textures/pig/animalia_pig_1.png b/textures/pig/animalia_pig_1.png index d8b6f1d..7993db8 100644 Binary files a/textures/pig/animalia_pig_1.png and b/textures/pig/animalia_pig_1.png differ diff --git a/textures/pig/animalia_pig_2.png b/textures/pig/animalia_pig_2.png index adcf341..8628f6a 100644 Binary files a/textures/pig/animalia_pig_2.png and b/textures/pig/animalia_pig_2.png differ diff --git a/textures/pig/animalia_pig_3.png b/textures/pig/animalia_pig_3.png index 591dab8..a095821 100644 Binary files a/textures/pig/animalia_pig_3.png and b/textures/pig/animalia_pig_3.png differ diff --git a/textures/reindeer/animalia_reindeer.png b/textures/reindeer/animalia_reindeer.png index 0fccb71..8c85342 100644 Binary files a/textures/reindeer/animalia_reindeer.png and b/textures/reindeer/animalia_reindeer.png differ diff --git a/textures/reindeer/animalia_reindeer_calf.png b/textures/reindeer/animalia_reindeer_calf.png index 4576119..d6acdc7 100644 Binary files a/textures/reindeer/animalia_reindeer_calf.png and b/textures/reindeer/animalia_reindeer_calf.png differ diff --git a/textures/sheep/animalia_sheep.png b/textures/sheep/animalia_sheep.png index b0cf5c1..03ffca4 100644 Binary files a/textures/sheep/animalia_sheep.png and b/textures/sheep/animalia_sheep.png differ diff --git a/textures/sheep/animalia_sheep_wool.png b/textures/sheep/animalia_sheep_wool.png index c37b3ed..2f1ffb8 100644 Binary files a/textures/sheep/animalia_sheep_wool.png and b/textures/sheep/animalia_sheep_wool.png differ diff --git a/textures/turkey/animalia_turkey_chick.png b/textures/turkey/animalia_turkey_chick.png index e88d34c..1b7718c 100644 Binary files a/textures/turkey/animalia_turkey_chick.png and b/textures/turkey/animalia_turkey_chick.png differ diff --git a/textures/turkey/animalia_turkey_hen.png b/textures/turkey/animalia_turkey_hen.png index 84b32c0..ea75f6d 100644 Binary files a/textures/turkey/animalia_turkey_hen.png and b/textures/turkey/animalia_turkey_hen.png differ diff --git a/textures/turkey/animalia_turkey_tom.png b/textures/turkey/animalia_turkey_tom.png index a6322f6..c26a59e 100644 Binary files a/textures/turkey/animalia_turkey_tom.png and b/textures/turkey/animalia_turkey_tom.png differ diff --git a/textures/wolf/animalia_wolf.png b/textures/wolf/animalia_wolf.png deleted file mode 100644 index b1327d7..0000000 Binary files a/textures/wolf/animalia_wolf.png and /dev/null differ diff --git a/textures/wolf/animalia_wolf_1.png b/textures/wolf/animalia_wolf_1.png new file mode 100644 index 0000000..ca9b40b Binary files /dev/null and b/textures/wolf/animalia_wolf_1.png differ diff --git a/textures/wolf/animalia_wolf_2.png b/textures/wolf/animalia_wolf_2.png new file mode 100644 index 0000000..be13a44 Binary files /dev/null and b/textures/wolf/animalia_wolf_2.png differ diff --git a/textures/wolf/animalia_wolf_3.png b/textures/wolf/animalia_wolf_3.png new file mode 100644 index 0000000..7899b11 Binary files /dev/null and b/textures/wolf/animalia_wolf_3.png differ diff --git a/textures/wolf/animalia_wolf_4.png b/textures/wolf/animalia_wolf_4.png new file mode 100644 index 0000000..d78381e Binary files /dev/null and b/textures/wolf/animalia_wolf_4.png differ