diff --git a/api/api.lua b/api/api.lua index 8565fa2..daa4235 100644 --- a/api/api.lua +++ b/api/api.lua @@ -257,6 +257,7 @@ function animalia.get_nearby_mate(self) local obj_pos = object and object:get_pos() local ent = obj_pos and object:get_luaentity() if obj_pos + and ent.growth_scale == 1 and ent.gender ~= self.gender and ent.breeding then return object @@ -678,7 +679,8 @@ end function animalia.mount(self, player, params) if not creatura.is_alive(player) - or player:get_attach() then + or (player:get_attach() + and player:get_attach() ~= self.object) then return end local plyr_name = player:get_player_name() diff --git a/api/mob_ai.lua b/api/mob_ai.lua index 8e874ba..1bcfec3 100644 --- a/api/mob_ai.lua +++ b/api/mob_ai.lua @@ -98,7 +98,7 @@ local function calc_altitude(self, pos2) return ((calc_pos.y + altitude) - center_y) / range * 2 end -local function calc_steering_and_lift(self, pos, pos2, dir, steer_method) +--[[local function calc_steering_and_lift(self, pos, pos2, dir, steer_method) local steer_to = creatura.calc_steering(self, pos2, steer_method or creatura.get_context_small) pos2 = vec_add(pos, steer_to) local lift = creatura.get_avoidance_lift(self, pos2, 2) @@ -111,7 +111,7 @@ local function calc_steering_and_lift_aquatic(self, pos, pos2, dir, steer_method local lift = creatura.get_avoidance_lift_aquatic(self, vec_add(pos, steer_to), 2) steer_to.y = (lift ~= 0 and lift) or dir.y return steer_to -end +end]] local function get_obstacle(pos, water) local pos2 = {x = pos.x, y = pos.y, z = pos.z} @@ -482,6 +482,60 @@ end -- if self.animations["latch_ceiling"] then latch to ceiling end -- if self.animations["latch_wall"] then latch to wall end +local latch_ceil_offset = {x = 0, y = 1, z = 0} +local latch_wall_offset = { + {x = 1, y = 0, z = 0}, + {x = 0, y = 0, z = 1}, + {x = -1, y = 0, z = 0}, + {x = 0, y = 0, z = -1} +} + + +function animalia.action_latch(self) + local pos = self.object:get_pos() + if not pos then return end + + local ceiling + if self.animations["latch_ceiling"] then + ceiling = vec_add(pos, latch_ceil_offset) + + if not creatura.get_node_def(ceiling).walkable then + ceiling = nil + end + end + + local wall + if self.animations["latch_wall"] then + for n = 1, 4 do + wall = vec_add(self.stand_pos, latch_wall_offset[n]) + + if creatura.get_node_def(wall).walkable then + break + else + wall = nil + end + end + end + local function func(mob) + mob:set_gravity(0) + + if ceiling then + mob:animate("latch_ceiling") + mob:set_vertical_velocity(1) + mob:set_forward_velocity(0) + return + end + + if wall then + mob:animate("latch_wall") + mob.object:set_yaw(minetest.dir_to_yaw(vec_dir(pos, wall))) + mob:set_vertical_velocity(0) + mob:set_forward_velocity(1) + 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 @@ -559,6 +613,46 @@ function animalia.action_melee(self, target) self:set_action(func) end +function animalia.action_play(self, target) + local stage = 1 + local is_animated = self.animations["play"] ~= nil + local timeout = 1 + + local function func(mob) + local target_pos = target and target:get_pos() + if not target_pos then return true end + + local pos = mob.stand_pos + local dist = vec_dist(pos, target_pos) + local dir = vec_dir(pos, target_pos) + + local anim = is_animated and mob:animate("play", "stand") + + if stage == 1 then + mob.object:add_velocity({x = dir.x * 3, y = 2, z = dir.z * 3}) + + stage = 2 + end + + if stage == 2 + and dist < mob.width + 1 then + animalia.add_trust(mob, target, 1) + + stage = 3 + end + + if stage == 3 + and (not is_animated + or anim == "stand") then + return true + end + + timeout = timeout - mob.dtime + if timeout <= 0 then return true end + end + self:set_action(func) +end + function animalia.action_float(self, time, anim) local timer = time local function func(_self) @@ -613,6 +707,48 @@ end -- Behaviors +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) + -- Basic -- creatura.register_utility("animalia:basic_idle", function(self, timeout, anim) @@ -832,12 +968,11 @@ creatura.register_utility("animalia:basic_attack", function(self, target) end) creatura.register_utility("animalia:basic_breed", function(self) - local mate + local mate = animalia.get_nearby_mate(self, self.name) local timer = 0 local function func(mob) - mate = mate or animalia.get_nearby_mate(mob, mob.name) - if not mate then return true end + if not mob.breeding then return true end local pos, target_pos = mob.object:get_pos(), mate and mate:get_pos() if not pos or not target_pos then return true end @@ -850,8 +985,8 @@ creatura.register_utility("animalia:basic_breed", function(self) mob.breeding = mob:memorize("breeding", false) mob.breeding_cooldown = mob:memorize("breeding_cooldown", 300) - mate_entity.breeding = mate:memorize("breeding", false) - mate_entity.breeding_cooldown = mate:memorize("breeding_cooldown", 300) + mate_entity.breeding = mate_entity:memorize("breeding", false) + mate_entity.breeding_cooldown = mate_entity:memorize("breeding_cooldown", 300) animalia.particle_spawner(pos, "heart.png", "float") @@ -866,6 +1001,7 @@ creatura.register_utility("animalia:basic_breed", function(self) animalia.protect_from_despawn(ent) end end + return true, 60 end if not mob:get_action() then @@ -1120,7 +1256,13 @@ creatura.register_utility("animalia:horse_ride", function(self, player) local tyaw = player:get_look_horizontal() local control = player:get_player_control() local vel = _self.object:get_velocity() - if not tyaw then return end + if not tyaw then return true end + + if control.sneak + or not _self.rider then + animalia.mount(_self, player) + return true + end animate_player(player, "sit", 30) @@ -1176,12 +1318,6 @@ creatura.register_utility("animalia:horse_ride", function(self, player) _self:set_forward_velocity(_self.speed * speed_x) _self:animate(anim) - - if control.sneak - or not _self.rider then - animalia.mount(_self, player) - return true - end end self:set_utility(func) end) @@ -1295,6 +1431,44 @@ creatura.register_utility("animalia:cat_follow_owner", function(self, player) self:set_utility(func) end) +creatura.register_utility("animalia:cat_play_with_owner", function(self) + local timeout = 6 + --local attack_chance = 6 + + local has_played = false + + local function func(mob) + local owner = mob.owner and minetest.get_player_by_name(mob.owner) + if not owner then return true end + + local item = owner:get_wielded_item() + local item_name = item and item:get_name() + + if item_name ~= "animalia:cat_toy" then return true, 5 end + + local pos, target_pos = mob.object:get_pos(), owner:get_pos() + if not pos or not target_pos then return true end + + if not mob:get_action() then + if has_played then return true, 20 end + local dist = vec_dist(pos, target_pos) + + if dist > mob.width + 0.5 then + animalia.action_pursue(mob, owner) + else + animalia.action_play(mob, owner) + has_played = true + end + end + + timeout = timeout - mob.dtime + if timeout <= 0 then + return true + end + end + self:set_utility(func) +end) + -- Frog -- local function get_bug_pos(self) @@ -1347,6 +1521,51 @@ creatura.register_utility("animalia:frog_seek_bug", function(self) self:set_utility(func) end) +-- Opossum + +local function grow_crop(crop) + local crop_name = minetest.get_node(crop).name + local growth_stage = tonumber(crop_name:sub(-1)) or 1 + local new_name = crop_name:sub(1, #crop_name - 1) .. (growth_stage + 1) + local new_def = minetest.registered_nodes[new_name] + + if new_def then + local p2 = new_def.place_param2 or 1 + minetest.set_node(crop, {name = new_name, param2 = p2}) + end +end + +creatura.register_utility("animalia:opossum_seek_crop", function(self) + local timeout = 12 + + local crop = animalia.find_crop(self) + local crop_reached = false + local function func(mob) + local pos = mob.object:get_pos() + if not pos or not crop then return true, 30 end + + local dist = vec_dist(pos, crop) + if dist < mob.width + 0.5 + and not crop_reached then + crop_reached = true + + creatura.action_idle(mob, 1, "clean_crop") + grow_crop(crop) + end + + if not mob:get_action() then + if crop_reached then return true, 10 end + animalia.action_walk(mob, 2, 0.5, "walk", crop) + end + + timeout = timeout - mob.dtime + if timeout <= 0 then + return true + end + end + self:set_utility(func) +end) + -- Rat -- local function find_chest(self) @@ -1481,7 +1700,6 @@ animalia.mob_ai.basic_flee = { animalia.mob_ai.basic_breed = { utility = "animalia:basic_breed", - step_delay = 0.25, get_score = function(self) if self.breeding and animalia.get_nearby_mate(self, self.name) then @@ -1689,6 +1907,20 @@ animalia.mob_ai.cat_stay = { end } +animalia.mob_ai.cat_play_with_owner = { + utility = "animalia:cat_play_with_owner", + get_score = function(self) + local trust = (self.owner and self.trust[self.owner]) or 0 + + if trust + and trust > 1 + and random(4) < 2 then + return 0.3, {self} + end + return 0 + end +} + -- Eagle animalia.mob_ai.eagle_attack = { @@ -1808,6 +2040,17 @@ animalia.mob_ai.opossum_feint = { end } +animalia.mob_ai.opossum_seek_crop = { + utility = "animalia:opossum_seek_crop", + step_delay = 0.25, + get_score = function(self) + if random(8) < 2 then + return 0.4, {self} + end + return 0 + end +} + -- Rat animalia.mob_ai.rat_seek_chest = { diff --git a/api/spawning.lua b/api/spawning.lua index ae0d4d9..e9f816a 100644 --- a/api/spawning.lua +++ b/api/spawning.lua @@ -44,6 +44,16 @@ minetest.register_on_mods_loaded(function() insert_all(frog_biomes, animalia.registered_biome_groups["tropical"].biomes) end) +creatura.register_abm_spawn("animalia:grizzly_bear", { + chance = predator_spawn_chance, + min_height = -1, + max_height = 1024, + min_group = 1, + max_group = 1, + biomes = animalia.registered_biome_groups["boreal"].biomes, + nodes = {"group:sand"}, +}) + creatura.register_abm_spawn("animalia:chicken", { chance = common_spawn_chance, chance_on_load = 64, diff --git a/craftitems.lua b/craftitems.lua index e34fd67..11f784c 100644 --- a/craftitems.lua +++ b/craftitems.lua @@ -507,6 +507,42 @@ minetest.register_craftitem("animalia:net", { end }) +----------- +-- Armor -- +----------- + +if minetest.get_modpath("3d_armor") then + table.insert(armor.attributes, "heavy_pelt") + + armor:register_armor("animalia:coat_bear_pelt", { + description = "Bear Pelt Coat", + inventory_image = "animalia_inv_coat_bear_pelt.png", + groups = {armor_torso = 1, armor_heal = 0, armor_heavy_pelt = 1, armor_use = 1000}, + armor_groups = {fleshy = 5} + }) + + minetest.register_on_punchplayer(function(player, hitter, _, _, _, damage) + local name = player:get_player_name() + if name + and (armor.def[name].heavy_pelt or 0) > 0 then + local hit_ip = hitter:is_player() + if hit_ip and minetest.is_protected(player:get_pos(), "") then + return + else + local player_pos = player:get_pos() + if not player_pos then return end + + local biome_data = minetest.get_biome_data(player_pos) + + if biome_data.heat < 50 then + player:set_hp(player:get_hp() - (damage / 1.5)) + return true + end + end + end + end) +end + ----------- -- Nodes -- ----------- diff --git a/init.lua b/init.lua index 8adf9f1..3f8de9c 100644 --- a/init.lua +++ b/init.lua @@ -110,6 +110,7 @@ animalia.animals = { "animalia:cow", "animalia:fox", "animalia:frog", + "animalia:grizzly_bear", "animalia:horse", "animalia:opossum", "animalia:owl", @@ -125,6 +126,7 @@ animalia.animals = { dofile(path.."/api/api.lua") load_file(path .. "/mobs", "bat.lua") +load_file(path .. "/mobs", "bear.lua") load_file(path .. "/mobs", "cat.lua") load_file(path .. "/mobs", "chicken.lua") load_file(path .. "/mobs", "cow.lua") diff --git a/mobs/bat.lua b/mobs/bat.lua index 2750dc1..9e442a6 100644 --- a/mobs/bat.lua +++ b/mobs/bat.lua @@ -65,7 +65,7 @@ creatura.register_mob("animalia:bat", { stand = {range = {x = 1, y = 40}, speed = 10, frame_blend = 0.3, loop = true}, walk = {range = {x = 51, y = 69}, speed = 30, frame_blend = 0.3, loop = true}, fly = {range = {x = 81, y = 99}, speed = 80, frame_blend = 0.3, loop = true}, - cling = {range = {x = 110, y = 110}, speed = 1, frame_blend = 0, loop = false} + latch_ceiling = {range = {x = 110, y = 110}, speed = 1, frame_blend = 0, loop = false} }, follow = { "butterflies:butterfly_red", @@ -77,7 +77,7 @@ creatura.register_mob("animalia:bat", { flee_puncher = true, catch_with_net = true, catch_with_lasso = false, - roost_action = animalia.action_cling, + roost_action = animalia.action_latch, -- Functions utility_stack = { diff --git a/mobs/bear.lua b/mobs/bear.lua new file mode 100644 index 0000000..ed15210 --- /dev/null +++ b/mobs/bear.lua @@ -0,0 +1,125 @@ +---------- +-- Bear -- +---------- + +creatura.register_mob("animalia:grizzly_bear", { + -- Engine Props + visual_size = {x = 10, y = 10}, + mesh = "animalia_bear.b3d", + textures = { + "animalia_bear_grizzly.png" + }, + makes_footstep_sound = true, + + -- Creatura Props + max_health = 20, + armor_groups = {fleshy = 100}, + damage = 6, + speed = 4, + tracking_range = 10, + despawn_after = 1000, + max_fall = 3, + stepheight = 1.1, + sounds = { + random = { + name = "animalia_bear", + gain = 0.5, + distance = 8 + }, + hurt = { + name = "animalia_bear_hurt", + gain = 0.5, + distance = 8 + }, + death = { + name = "animalia_bear_death", + gain = 0.5, + distance = 8 + } + }, + hitbox = { + width = 0.5, + height = 1 + }, + animations = { + stand = {range = {x = 1, y = 59}, speed = 10, frame_blend = 0.3, loop = true}, + walk = {range = {x = 61, y = 79}, speed = 10, frame_blend = 0.3, loop = true}, + run = {range = {x = 81, y = 99}, speed = 20, frame_blend = 0.3, loop = true}, + melee = {range = {x = 101, y = 120}, speed = 30, frame_blend = 0.3, loop = false} + }, + follow = { + "group:food_berry", + "group:food_fish" + }, + drops = { + {name = "animalia:pelt_bear", min = 1, max = 3, chance = 1} + }, + fancy_collide = false, + + -- Behavior Parameters + attacks_players = true, + + -- Animalia Parameters + catch_with_net = true, + catch_with_lasso = true, + head_data = { + offset = {x = 0, y = 0.35, z = 0.0}, + pitch_correction = -45, + pivot_h = 0.75, + pivot_v = 1 + }, + + -- Functions + utility_stack = { + animalia.mob_ai.basic_wander, + animalia.mob_ai.swim_seek_land, + animalia.mob_ai.basic_seek_food, + animalia.mob_ai.basic_attack, + animalia.mob_ai.basic_breed + }, + + on_eat_drop = function(self) + local feed_no = (self.feed_no or 0) + 1 + + if feed_no >= 5 then + feed_no = 0 + + if self.breeding then return false end + if self.breeding_cooldown <= 0 then + self.breeding = true + self.breeding_cooldown = 60 + animalia.particle_spawner(self.stand_pos, "heart.png", "float") + end + + self._despawn = self:memorize("_despawn", false) + self.despawn_after = self:memorize("despawn_after", false) + end + self.feed_no = feed_no + end, + + activate_func = function(self) + animalia.initialize_api(self) + animalia.initialize_lasso(self) + end, + + step_func = function(self) + animalia.step_timers(self) + animalia.head_tracking(self, 0.75, 0.75) + animalia.do_growth(self, 60) + animalia.update_lasso_effects(self) + animalia.random_sound(self) + end, + + death_func = function(self) + if self:get_utility() ~= "animalia:die" then + self:initiate_utility("animalia:die", self) + end + end, + + on_punch = animalia.punch +}) + +creatura.register_spawn_item("animalia:grizzly_bear", { + col1 = "64361d", + col2 = "2c0d03" +}) diff --git a/mobs/cat.lua b/mobs/cat.lua index 37745d6..644f8ab 100644 --- a/mobs/cat.lua +++ b/mobs/cat.lua @@ -99,7 +99,7 @@ creatura.register_mob("animalia:cat", { animalia.mob_ai.swim_seek_land, animalia.mob_ai.cat_seek_vessel, animalia.mob_ai.cat_stay, - --animalia.mob_ai.cat_play + animalia.mob_ai.cat_play_with_owner, animalia.mob_ai.cat_follow_owner, animalia.mob_ai.basic_attack, animalia.mob_ai.basic_breed diff --git a/mobs/horse.lua b/mobs/horse.lua index 8758175..1a854e5 100644 --- a/mobs/horse.lua +++ b/mobs/horse.lua @@ -211,7 +211,7 @@ creatura.register_mob("animalia:horse", { walk = {range = {x = 70, y = 89}, speed = 20, frame_blend = 0.3, loop = true}, run = {range = {x = 101, y = 119}, speed = 40, frame_blend = 0.3, loop = true}, punch_aoe = {range = {x = 170, y = 205}, speed = 30, frame_blend = 0.2, loop = false}, - rear = {range = {x = 130, y = 160}, speed = 20, frame_blend = 0.2, loop = false}, + rear = {range = {x = 130, y = 160}, speed = 20, frame_blend = 0.1, loop = false}, eat = {range = {x = 210, y = 240}, speed = 30, frame_blend = 0.3, loop = false} }, follow = animalia.food_wheat, @@ -371,7 +371,7 @@ creatura.register_mob("animalia:horse", { local owner = self.owner local name = clicker and clicker:get_player_name() - if owner and name ~= owner then return end + if not owner or name ~= owner then return end if animalia.set_nametag(self, clicker) then return diff --git a/mobs/opossum.lua b/mobs/opossum.lua index c2a28f8..c68ae3a 100644 --- a/mobs/opossum.lua +++ b/mobs/opossum.lua @@ -30,7 +30,8 @@ creatura.register_mob("animalia:opossum", { stand = {range = {x = 1, y = 59}, speed = 10, frame_blend = 0.3, loop = true}, walk = {range = {x = 70, y = 89}, speed = 30, frame_blend = 0.3, loop = true}, run = {range = {x = 100, y = 119}, speed = 45, frame_blend = 0.3, loop = true}, - feint = {range = {x = 130, y = 130}, speed = 45, frame_blend = 0.3, loop = false} + feint = {range = {x = 130, y = 130}, speed = 45, frame_blend = 0.3, loop = false}, + clean_crop = {range = {x = 171, y = 200}, speed = 15, frame_blend = 0.2, loop = false} }, follow = { "animalia:song_bird_egg", @@ -62,6 +63,7 @@ creatura.register_mob("animalia:opossum", { animalia.mob_ai.swim_seek_land, animalia.mob_ai.basic_attack, animalia.mob_ai.opossum_feint, + animalia.mob_ai.opossum_seek_crop, animalia.mob_ai.basic_seek_food, animalia.mob_ai.tamed_follow_owner, animalia.mob_ai.basic_breed diff --git a/models/animalia_bear.b3d b/models/animalia_bear.b3d new file mode 100644 index 0000000..4af43e3 Binary files /dev/null and b/models/animalia_bear.b3d differ diff --git a/models/animalia_opossum.b3d b/models/animalia_opossum.b3d index c472e36..0a41d89 100644 Binary files a/models/animalia_opossum.b3d and b/models/animalia_opossum.b3d differ diff --git a/textures/armor/animalia_coat_bear_pelt.png b/textures/armor/animalia_coat_bear_pelt.png new file mode 100644 index 0000000..1c05a46 Binary files /dev/null and b/textures/armor/animalia_coat_bear_pelt.png differ diff --git a/textures/armor/animalia_coat_bear_pelt_preview.png b/textures/armor/animalia_coat_bear_pelt_preview.png new file mode 100644 index 0000000..3a65b01 Binary files /dev/null and b/textures/armor/animalia_coat_bear_pelt_preview.png differ diff --git a/textures/armor/animalia_inv_coat_bear_pelt.png b/textures/armor/animalia_inv_coat_bear_pelt.png new file mode 100644 index 0000000..1ad8a8c Binary files /dev/null and b/textures/armor/animalia_inv_coat_bear_pelt.png differ diff --git a/textures/bear/animalia_bear_grizzly.png b/textures/bear/animalia_bear_grizzly.png new file mode 100644 index 0000000..f703c72 Binary files /dev/null and b/textures/bear/animalia_bear_grizzly.png differ