write something there

This commit is contained in:
N-Nachtigal 2025-05-04 16:01:41 +02:00
commit b4b6c08f4f
8546 changed files with 309825 additions and 0 deletions

882
mods/animalia/api/api.lua Normal file
View file

@ -0,0 +1,882 @@
---------
-- API --
---------
-- 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_add, vec_dir, vec_dist, vec_divide, vec_len, vec_multi, vec_normal,
vec_round, vec_sub = vector.add, vector.direction, vector.distance, vector.divide,
vector.length, vector.multiply, vector.normalize, vector.round, vector.subtract
local dir2yaw = minetest.dir_to_yaw
local yaw2dir = minetest.yaw_to_dir
------------
-- 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 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
animalia.animate_player = {}
if minetest.get_modpath("default")
and minetest.get_modpath("player_api") then
animalia.animate_player = player_api.set_animation
elseif minetest.get_modpath("mcl_player") then
animalia.animate_player = mcl_player.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 = 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(6)
and random(4) < 2 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.has_shared_owner(obj1, obj2)
local ent1 = obj1 and obj1:get_luaentity()
local ent2 = obj2 and obj2:get_luaentity()
if ent1
and ent2 then
return ent1.owner and ent2.owner and ent1.owner == ent2.owner
end
return false
end
function animalia.get_attack_score(entity, attack_list)
local pos = entity.stand_pos
if not pos then return end
local order = entity.order or "wander"
if order ~= "wander" then return 0 end
local target = entity._target or (entity.attacks_players and creatura.get_nearby_player(entity))
local tgt_pos = target and target:get_pos()
if not tgt_pos
or not entity:is_pos_safe(tgt_pos)
or (target:is_player()
and minetest.is_creative_enabled(target:get_player_name())) then
target = creatura.get_nearby_object(entity, attack_list)
tgt_pos = target and target:get_pos()
end
if not tgt_pos then entity._target = nil return 0 end
if target == entity.object then entity._target = nil return 0 end
if animalia.has_shared_owner(entity.object, target) then entity._target = nil return 0 end
local dist = vec_dist(pos, tgt_pos)
local score = (entity.tracking_range - dist) / entity.tracking_range
if entity.trust
and target:is_player()
and entity.trust[target:get_player_name()] then
local trust = entity.trust[target:get_player_name()]
local trust_score = ((entity.max_trust or 10) - trust) / (entity.max_trust or 10)
score = score - trust_score
end
entity._target = target
return score * 0.5, {entity, target}
end
function animalia.get_nearby_mate(self)
local pos = self.object:get_pos()
if not pos then return end
local objects = creatura.get_nearby_objects(self, self.name)
for _, object in ipairs(objects) do
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
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(self, item, chance)
local pos = self.object:get_pos()
if not pos then return end
if random(chance) < 2 then
local object = minetest.add_item(pos, ItemStack(item))
object:add_velocity({
x = random(-2, 2),
y = 1.5,
z = random(-2, 2)
})
end
end
---------------
-- Particles --
---------------
function animalia.particle_spawner(pos, texture, type, min_pos, max_pos)
type = type or "float"
min_pos = min_pos or vec_sub(pos, 1)
max_pos = max_pos or vec_add(pos, 1)
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.add_food_particle(self, item_name)
local pos, yaw = self.object:get_pos(), self.object:get_yaw()
if not pos then return end
local head = self.head_data
local offset_h = (head and head.pivot_h) or self.width
local offset_v = (head and head.pivot_v) or self.height
local head_pos = {
x = pos.x + sin(yaw) * -offset_h,
y = pos.y + offset_v,
z = pos.z + cos(yaw) * offset_h
}
local def = minetest.registered_items[item_name]
local image = def.inventory_image
if def.tiles then
image = def.tiles[1].name or def.tiles[1]
end
if image then
local crop = "^[sheet:4x4:" .. random(0,3) .. "," .. random(0,3)
minetest.add_particlespawner({
pos = head_pos,
time = 0.5,
amount = 12,
collisiondetection = true,
collision_removal = true,
vel = {min = {x = -1, y = 1, z = -1}, max = {x = 1, y = 2, z = 1}},
acc = {x = 0, y = -9.8, z = 0},
size = {min = 1, max = 2},
texture = image .. crop
})
end
end
function animalia.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
----------
-- Mobs --
----------
function animalia.death_func(self)
if self:get_utility() ~= "animalia:die" then
self:initiate_utility("animalia:die", self)
end
end
function animalia.get_dropped_food(self, item, radius)
local pos = self.object:get_pos()
if not pos then return end
local objects = minetest.get_objects_inside_radius(pos, radius or self.tracking_range)
for _, object in ipairs(objects) do
local ent = object:get_luaentity()
if ent
and ent.name == "__builtin:item"
and ent.itemstring
and ((item and ent.itemstring:match(item))
or self:follow_item(ItemStack(ent.itemstring))) then
return object, object:get_pos()
end
end
end
function animalia.eat_dropped_item(self, item)
local pos = self.object:get_pos()
if not pos then return end
local food = item or animalia.get_dropped_food(self, nil, self.width + 1)
local food_ent = food and food:get_luaentity()
if food_ent then
local food_pos = food:get_pos()
local stack = ItemStack(food_ent.itemstring)
if stack
and stack:get_count() > 1 then
stack:take_item()
food_ent.itemstring = stack:to_string()
else
food:remove()
end
self.object:set_yaw(dir2yaw(vec_dir(pos, food_pos)))
animalia.add_food_particle(self, stack:get_name())
if self.on_eat_drop then
self:on_eat_drop()
end
return true
end
end
function animalia.protect_from_despawn(self)
self._despawn = self:memorize("_despawn", false)
self.despawn_after = self:memorize("despawn_after", false)
end
function animalia.despawn_inactive_mob(self)
local os_time = os.time()
self._last_active = self:recall("_last_active")
if self._last_active
and self.despawn_after then
local last_active = self._last_active
if os_time - last_active > self.despawn_after then
self.object:remove()
return true
end
end
end
function animalia.set_nametag(self, clicker)
local plyr_name = clicker and clicker:get_player_name()
if not plyr_name then return end
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", false)
activate_nametag(self)
if not minetest.is_creative_enabled(plyr_name) then
item:take_item()
clicker:set_wielded_item(item)
end
return true
end
function animalia.initialize_api(self)
-- Set Gender
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
-- Taming/Breeding
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
-- Textures/Scale
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)
end
if self.growth_scale < 0.8
and self.child_mesh then
self.object:set_properties({
mesh = self.child_mesh
})
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)
self:memorize("_last_active", os.time())
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.child_mesh then self:set_mesh() end
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
if self.on_grown then
self:on_grown()
end
end
self:memorize("growth_scale", self.growth_scale)
end
end
end
function animalia.random_sound(self)
if self:timer(8)
and random(4) < 2 then
self:play_sound("random")
end
end
function animalia.add_trust(self, player, amount)
if self.trust_cooldown > 0 then return end
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, clicker, tame, breed)
local yaw = self.object:get_yaw()
local pos = self.object:get_pos()
if not pos then return end
local name = clicker:is_player() and clicker:get_player_name()
local item, item_name = self:follow_wielded_item(clicker)
if item_name then
-- Eat Animation
local head = self.head_data
local offset_h = (head and head.pivot_h) or 0.5
local offset_v = (head and head.pivot_v) or 0.5
local head_pos = {
x = pos.x + sin(yaw) * -offset_h,
y = pos.y + offset_v,
z = pos.z + cos(yaw) * offset_h
}
local def = minetest.registered_items[item_name]
if def.inventory_image then
minetest.add_particlespawner({
pos = head_pos,
time = 0.1,
amount = 3,
collisiondetection = true,
collision_removal = true,
vel = {min = {x = -1, y = 3, z = -1}, max = {x = 1, y = 4, z = 1}},
acc = {x = 0, y = -9.8, z = 0},
size = {min = 2, max = 4},
texture = def.inventory_image
})
end
-- Increase Health
local feed_no = (self.feed_no or 0) + 1
local max_hp = self.max_health
local hp = self.hp
hp = hp + (max_hp / 5)
if hp > max_hp then hp = max_hp end
self.hp = hp
-- Tame/Breed
if feed_no >= 5 then
feed_no = 0
if tame then
self.owner = self:memorize("owner", name)
minetest.add_particlespawner({
pos = {min = vec_sub(pos, self.width), max = vec_add(pos, self.width)},
time = 0.1,
amount = 12,
vel = {min = {x = 0, y = 3, z = 0}, max = {x = 0, y = 4, z = 0}},
size = {min = 4, max = 6},
glow = 16,
texture = "creatura_particle_green.png"
})
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")
end
end
self._despawn = self:memorize("_despawn", false)
self.despawn_after = self:memorize("despawn_after", false)
end
self.feed_no = feed_no
-- Take item
if not minetest.is_creative_enabled(name) then
item:take_item()
clicker:set_wielded_item(item)
end
return true
end
end
function animalia.mount(self, player, params)
if not creatura.is_alive(player)
or (player:get_attach()
and player:get_attach() ~= self.object) then
return
end
local plyr_name = player:get_player_name()
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 minetest.get_modpath("player_api") then
animalia.animate_player(player, "stand", 30)
if player_api.player_attached then
player_api.player_attached[plyr_name] = false
end
end
return
end
if minetest.get_modpath("player_api") then
player_api.player_attached[plyr_name] = true
end
self.rider = player
player:set_attach(self.object, "Torso", params.pos, params.rot)
player:set_eye_offset({x = 0, y = 20, z = 5}, {x = 0, y = 15, z = 15})
self:clear_utility()
minetest.after(0.4, function()
animalia.animate_player(player, "sit" , 30)
end)
end
function animalia.punch(self, puncher, ...)
if self.hp <= 0 then return end
creatura.basic_punch_func(self, puncher, ...)
self._puncher = puncher
if self.flee_puncher
and (self:get_utility() or "") ~= "animalia:flee_from_target" then
self:clear_utility()
end
end
function animalia.find_crop(self)
local pos = self.object:get_pos()
if not pos then return end
local nodes = minetest.find_nodes_in_area(vec_sub(pos, 6), vec_add(pos, 6), "group:crop") or {}
if #nodes < 1 then return end
return nodes[math.random(#nodes)]
end
function animalia.eat_crop(self, pos)
local node_name = minetest.get_node(pos).name
local new_name = node_name:sub(1, #node_name - 1) .. (tonumber(node_name:sub(-1)) or 2) - 1
local new_def = minetest.registered_nodes[new_name]
if not new_def then return false end
local p2 = new_def.place_param2 or 1
minetest.set_node(pos, {name = new_name, param2 = p2})
animalia.add_food_particle(self, new_name)
return true
end
function animalia.eat_turf(mob, pos)
for name, sub_name in pairs(mob.consumable_nodes) do
if minetest.get_node(pos).name == name then
--add_break_particle(turf_pos)
minetest.set_node(pos, {name = sub_name})
mob.collected = mob:memorize("collected", false)
--creatura.action_idle(mob, 1, "eat")
return true
end
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)
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
})

251
mods/animalia/api/lasso.lua Normal file
View file

@ -0,0 +1,251 @@
-----------
-- Lasso --
-----------
local abs = math.abs
local vec_add, vec_dir, vec_dist, vec_len = vector.add, vector.direction, vector.distance, vector.length
local dir2rot = vector.dir_to_rotation
-- Entities --
local using_lasso = {}
minetest.register_entity("animalia:lasso_entity", {
visual = "mesh",
mesh = "animalia_lasso_entity.b3d",
textures = {"animalia_lasso_entity.png"},
pointable = false,
on_activate = function(self)
self.object:set_armor_groups({immortal = 1})
end,
_scale = 1,
on_step = function(self)
local pos, parent = self.object:get_pos(), (self.object:get_attach() or self._attached)
local pointed_ent = self._point_to and self._point_to:get_luaentity()
local point_to = self._point_to and self._point_to:get_pos()
if not pos or not parent or not point_to then self.object:remove() return end
if type(parent) == "string" then
parent = minetest.get_player_by_name(parent)
if not parent then self.object:remove() return end
local tgt_pos = parent:get_pos()
tgt_pos.y = tgt_pos.y + 1
point_to.y = point_to.y + pointed_ent.height * 0.5
local dist = vec_dist(pos, tgt_pos)
if dist > 0.5 then
self.object:set_pos(tgt_pos)
else
self.object:move_to(tgt_pos)
end
self.object:set_rotation(dir2rot(vec_dir(tgt_pos, point_to)))
elseif parent.x then
point_to.y = point_to.y + pointed_ent.height * 0.5
self.object:set_rotation(dir2rot(vec_dir(pos, point_to)))
else
self.object:remove()
return
end
local size = vec_dist(pos, point_to)
if abs(size - self._scale) > 0.1 then
self.object:set_properties({
visual_size = {x = 1, y = 1, z = size}
})
self._scale = size
end
end
})
local function remove_from_fence(self)
local pos = self.object:get_pos()
local mob = self._mob and self._mob:get_luaentity()
if not mob then
self.object:remove()
return
end
mob._lassod_to = nil
mob:forget("_lassod_to")
mob._lasso_ent:remove()
local dirs = {
{x = 0.5, y = 0, z = 0},
{x = -0.5, y = 0, z = 0},
{x = 0, y = 0.5, z = 0},
{x = 0, y = -0.5, z = 0},
{x = 0, y = 0, z = 0.5},
{x = 0, y = 0, z = -0.5}
}
for i = 1, 6 do
local i_pos = vec_add(pos, dirs[i])
if not creatura.get_node_def(i_pos).walkable then
minetest.add_item(i_pos, "animalia:lasso")
break
end
end
self.object:remove()
end
minetest.register_entity("animalia:tied_lasso_entity", {
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_tied_lasso_entity.png",
"animalia_tied_lasso_entity.png",
"animalia_tied_lasso_entity.png",
"animalia_tied_lasso_entity.png",
"animalia_tied_lasso_entity.png",
"animalia_tied_lasso_entity.png",
},
on_activate = function(self)
self.object:set_armor_groups({immortal = 1})
end,
on_step = function(self)
local mob = self._mob and self._mob:get_luaentity()
if not mob then remove_from_fence(self) return end
end,
on_rightclick = remove_from_fence,
on_punch = remove_from_fence
})
-- API --
local function add_lasso(self, origin)
local pos = self.object:get_pos()
if not pos then return end
local object = minetest.add_entity(pos, "animalia:lasso_entity")
local ent = object and object:get_luaentity()
if not ent then return end
-- Attachment point of entity
ent._attached = origin
if type(origin) ~= "string" then
--local player = minetest.get_player_by_name(origin)
--object:set_attach(player)
--else
object:set_pos(origin)
end
self._lassod_to = origin
ent._point_to = self.object
self:memorize("_lassod_to", origin)
return object
end
local function get_rope_velocity(pos1, pos2, dist)
local force = dist / 10
local vel = vector.new((pos2.x - pos1.x) * force, ((pos2.y - pos1.y) / (24 + dist)), (pos2.z - pos1.z) * force)
return vel
end
function animalia.initialize_lasso(self)
self._lassod_to = self:recall("_lassod_to") or self:recall("lasso_origin")
if self._lassod_to then
local origin = self._lassod_to
if type(origin) == "table"
and minetest.get_item_group(minetest.get_node(origin).name, "fence") > 0 then
local object = minetest.add_entity(origin, "animalia:tied_lasso_entity")
object:get_luaentity()._mob = self.object
self._lasso_ent = add_lasso(self, origin)
elseif type(origin) == "string" then
self._lassod_to = origin
self._lasso_ent = add_lasso(self, origin)
else
self:forget("_lassod_to")
end
end
end
function animalia.update_lasso_effects(self)
local pos = self.object:get_pos()
if not creatura.is_alive(self) then return end
if self._lassod_to then
local lasso = self._lassod_to
self._lasso_ent = self._lasso_ent or add_lasso(self, lasso)
if type(lasso) == "string" then
using_lasso[lasso] = self
local name = lasso
lasso = minetest.get_player_by_name(lasso)
if lasso then
if lasso:get_wielded_item():get_name() ~= "animalia:lasso" then
using_lasso[name] = nil
self._lasso_ent:remove()
self._lasso_ent = nil
self._lassod_to = nil
self:forget("_lassod_to")
return
end
local lasso_pos = lasso:get_pos()
local dist = vec_dist(pos, lasso_pos)
local vel = self.object:get_velocity()
if not vel or dist < 8 and self.touching_ground then return end
if vec_len(vel) < 8 then
self.object:add_velocity(get_rope_velocity(pos, lasso_pos, dist))
end
return
end
elseif type(lasso) == "table" then
local dist = vec_dist(pos, lasso)
local vel = self.object:get_velocity()
if not vel or dist < 8 and self.touching_ground then return end
if vec_len(vel) < 8 then
self.object:add_velocity(get_rope_velocity(pos, lasso, dist))
end
return
end
end
if self._lasso_ent then
self._lasso_ent:remove()
self._lasso_ent = nil
self._lassod_to = nil
self:forget("_lassod_to")
end
end
-- Item
minetest.register_craftitem("animalia:lasso", {
description = "Lasso",
inventory_image = "animalia_lasso.png",
on_secondary_use = function(_, placer, pointed)
local ent = pointed.ref and pointed.ref:get_luaentity()
if ent
and (ent.name:match("^animalia:")
or ent.name:match("^monstrum:")) then
if not ent.catch_with_lasso then return end
local name = placer:get_player_name()
if not ent._lassod_to
and not using_lasso[name] then
using_lasso[name] = ent
ent._lassod_to = name
ent:memorize("_lassod_to", name)
elseif ent._lassod_to
and ent._lassod_to == name then
using_lasso[name] = nil
ent._lassod_to = nil
ent:forget("_lassod_to")
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 name = placer:get_player_name()
local ent = using_lasso[name]
if ent
and ent._lassod_to
and ent._lassod_to == name then
using_lasso[name] = nil
ent._lasso_ent:set_detach()
ent._lasso_ent:set_pos(pos)
ent._lasso_ent:get_luaentity()._attached = pos
ent._lassod_to = pos
ent:memorize("_lassod_to", pos)
local fence_obj = minetest.add_entity(pos, "animalia:tied_lasso_entity")
fence_obj:get_luaentity()._mob = ent.object
fence_obj:get_luaentity()._lasso_obj = ent._lasso_ent
itemstack:take_item(1)
end
end
end
return itemstack
end
})

View file

@ -0,0 +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
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")

528
mods/animalia/api/libri.lua Normal file
View file

@ -0,0 +1,528 @@
-----------
-- Libri --
-----------
local libri = {}
local path = minetest.get_modpath(minetest.get_current_modname())
local color = minetest.colorize
local libri_bg = {
"formspec_version[3]",
"size[16,10]",
"background[-0.7,-0.5;17.5,11.5;animalia_libri_bg.png]"
}
local libri_btn_next = "image_button[15,9;1,1;animalia_libri_icon_next.png;btn_next;;true;false]"
local libri_btn_last = "image_button[1,9;1,1;animalia_libri_icon_last.png;btn_last;;true;false]"
local libri_drp_font_scale = "dropdown[17,0;0.75,0.5;drp_font_scale;0.25,0.5,0.75,1;1]"
local function correct_string(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 pages = {}
local generate_mobs = {
["animalia:bat"] = "Bat",
["animalia:cat"] = "Cat",
["animalia:chicken"] = "Chicken",
["animalia:cow"] = "Cow",
["animalia:opossum"] = "Opossum",
["animalia:owl"] = "Owl",
["animalia:tropical_fish"] = "Tropical Fish",
["animalia:fox"] = "Fox",
["animalia:frog"] = "Frog",
["animalia:grizzly_bear"] = "Grizzly Bear",
["animalia:horse"] = "Horse",
["animalia:pig"] = "Pig",
["animalia:rat"] = "Rat",
["animalia:reindeer"] = "Reindeer",
["animalia:sheep"] = "Sheep",
["animalia:song_bird"] = "Song Bird",
["animalia:turkey"] = "Turkey",
["animalia:wolf"] = "Wolf",
}
local spawn_biomes = {
["animalia:bat"] = "cave",
["animalia:cat"] = "urban",
["animalia:chicken"] = "tropical",
["animalia:cow"] = "grassland",
["animalia:opossum"] = "temperate",
["animalia:owl"] = "temperate",
["animalia:tropical_fish"] = "ocean",
["animalia:fox"] = "boreal",
["animalia:frog"] = "swamp",
["animalia:grizzly_bear"] = "boreal",
["animalia:horse"] = "grassland",
["animalia:pig"] = "temperate",
["animalia:rat"] = "urban",
["animalia:reindeer"] = "boreal",
["animalia:sheep"] = "grassland",
["animalia:song_bird"] = "temperate",
["animalia:turkey"] = "boreal",
["animalia:wolf"] = "boreal",
}
-----------
-- Pages --
-----------
local function get_spawn_biomes(name)
local biomes = asuna.features.animals[name] or {}
return (#biomes > 0) and biomes or {"grassland"}
end
local function can_lasso(name)
return tostring(minetest.registered_entities[name].catch_with_lasso or false)
end
local function can_net(name)
return tostring(minetest.registered_entities[name].catch_with_net or false)
end
local function max_health(name)
return minetest.registered_entities[name].max_health or 20
end
local function mob_textures(name, mesh_no)
local def = minetest.registered_entities[name]
local textures = def.textures
if def.male_textures
or def.female_textures then
textures = {unpack(def.male_textures), unpack(def.female_textures)}
end
if def.mesh_textures then
textures = def.mesh_textures[mesh_no or 1]
end
return textures
end
local biome_cubes = {}
local function generate_page(mob)
local name = mob:split(":")[2]
local def = minetest.registered_entities[mob]
if not def then return end
local page = {
{ -- Info
element_type = "label",
center_text = true,
font_size = 20,
offset = {x = 8, y = 1.5},
file = "animalia_libri_" .. name .. ".txt"
},
{ -- Image
element_type = "model",
offset = {x = 1.5, y = 1.5},
size = {x = 5, y = 5},
mesh_iter = def.meshes and 1,
texture_iter = 1,
text = "mesh;" .. def.mesh .. ";" .. mob_textures(mob)[1] .. ";-30,225;false;false;0,0;0"
},
{ -- Spawn Biome
element_type = "image",
offset = {x = 0.825, y = 8.15},
size = {x = 1, y = 1},
biome_iter = 1,
text = biome_cubes[get_spawn_biomes(mob)[1]]
},
{ -- Biome Label
element_type = "tooltip",
offset = {x = 0.825, y = 8.15},
size = {x = 1, y = 1},
biome_iter = 1,
text = correct_string(get_spawn_biomes(mob)[1])
},
libri.render_element({ -- Health Icon
element_type = "image",
offset = {x = 2.535, y = 8.15},
size = {x = 1, y = 1},
text = "animalia_libri_health_fg.png"
}),
libri.render_element({ -- Health Amount
element_type = "label",
offset = {x = 3.25, y = 9},
text = "x" .. max_health(mob) / 2
}),
libri.render_element({ -- Lasso Icon
element_type = "item_image",
offset = {x = 4.25, y = 8.15},
size = {x = 1, y = 1},
text = "animalia:lasso"
}),
libri.render_element({ -- Lasso Indication Icon
element_type = "image",
offset = {x = 4.75, y = 8.75},
size = {x = 0.5, y = 0.5},
text = "animalia_libri_" .. can_lasso(mob) .. "_icon.png"
}),
libri.render_element({ -- Net Icon
element_type = "item_image",
offset = {x = 6, y = 8.15},
size = {x = 1, y = 1},
text = "animalia:net"
}),
libri.render_element({ -- Net Indication Icon
element_type = "image",
offset = {x = 6.5, y = 8.75},
size = {x = 0.5, y = 0.5},
text = "animalia_libri_" .. can_net(mob) .. "_icon.png"
}),
libri.render_element({ -- Styling
element_type = "image",
offset = {x = -0.7, y = -0.5},
size = {x = 17.5, y = 11.5},
text = "animalia_libri_info_fg.png"
})
}
pages[mob] = page
end
minetest.register_on_mods_loaded(function()
-- Register Biome Cubes
for name, def in pairs(minetest.registered_biomes) do
if def.node_top then
local tiles = {
"unknown_node.png",
"unknown_node.png",
"unknown_node.png"
}
local n_def = minetest.registered_nodes[def.node_top]
if n_def then
local def_tiles = table.copy(n_def.tiles or n_def.textures)
for i, tile in ipairs(def_tiles) do
if tile.name then
def_tiles[i] = tile.name
end
end
tiles = (#def_tiles > 0 and def_tiles) or tiles
end
biome_cubes[name] = minetest.inventorycube(tiles[1], tiles[3], tiles[3])
else
biome_cubes[name] = minetest.inventorycube("unknown_node.png", "unknown_node.png", "unknown_node.png")
end
end
pages = {
["home_1"] = {
{ -- Info
element_type = "label",
center_text = true,
font_size = 24,
offset = {x = 0, y = 1.5},
file = "animalia_libri_home.txt"
},
{
element_type = "mobs",
start_iter = 0,
offset = {x = 10.25, y = 1.5}
}
},
["home_2"] = {
{
element_type = "mobs",
start_iter = 4,
offset = {x = 1.75, y = 1.5}
}
},
["home_3"] = {
{
element_type = "mobs",
start_iter = 12,
offset = {x = 1.75, y = 1.5}
}
}
}
for mob in pairs(generate_mobs) do
generate_page(mob)
end
end)
---------
-- API --
---------
local function get_item_list(list, offset_x, offset_y) -- Creates a visual list of items for Libri formspecs
local size = 1 / ((#list < 3 and #list) or 3)
if size < 0.45 then size = 0.45 end
local spacing = size * 0.5
local total_scale = size + spacing
local max_horiz = 3
local form = ""
for i, item in ipairs(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 )
form = form .. "item_image[" .. pos_x .. "," .. pos_y .. ";" .. size .. "," .. size .. ";" .. item .. "]"
end
return form
end
function libri.generate_list(meta, offset, start_iter)
local chapters = minetest.deserialize(meta:get_string("chapters")) or {}
local i = 0
local elements = ""
local offset_x = offset.x
local offset_y = offset.y
for mob in pairs(chapters) do
if not mob then break end
i = i + 1
if i > start_iter then
local mob_name = mob:split(":")[2]
local offset_txt = offset_x .. "," .. offset_y
local element = "button[" .. offset_txt .. ";4,1;btn_" .. mob_name .. ";" .. correct_string(mob_name) .. "]"
elements = elements .. element
offset_y = offset_y + 2
if offset_y > 7.5 then
offset_x = offset_x + 8.5
if offset_x > 10.25 then
return elements
end
offset_y = 1.5
end
end
end
return elements
end
function libri.render_element(def, meta, playername)
local chapters = (meta and minetest.deserialize(meta:get_string("chapters"))) or {}
local chap_no = 0
for _ in pairs(chapters) do
chap_no = chap_no + 1
end
local offset_x = def.offset.x
local offset_y = def.offset.y
local form = ""
-- Add text
if def.element_type == "label" then
local font_size_x = (animalia.libri_font_size[playername] or 1)
local font_size = (def.font_size or 16) * font_size_x
if def.file then
local filename = path .. "/libri/" .. def.file
local file = io.open(filename)
if file then
local text = ""
for line in file:lines() do
text = text .. line .. "\n"
end
local total_offset = (offset_x + (0.35 - 0.35 * font_size_x)) .. "," .. offset_y
form = form ..
"hypertext[" .. total_offset .. ";8,9;text;<global color=#000000 size="..
font_size .. " halign=center>" .. text .. "]"
file:close()
end
else
form = form .. "style_type[label;font_size=" .. font_size .. "]"
local line = def.text
form = form .. "label[" .. offset_x .. "," .. offset_y .. ";" .. color("#000000", line .. "\n") .. "]"
end
elseif def.element_type == "mobs" then
form = form .. libri.generate_list(meta, def.offset, def.start_iter)
if chap_no > def.start_iter + 4 then form = form .. libri_btn_next end
if def.start_iter > 3 then form = form .. libri_btn_last end
else
-- Add Images/Interaction
local render_element = false
if def.unlock_key
and #chapters > 0 then
for _, chapter in ipairs(chapters) do
if chapter
and chapter == def.unlock_key then
render_element = true
break
end
end
elseif not def.unlock_key then
render_element = true
end
if render_element then
local offset = def.offset.x .. "," .. def.offset.y
local size = def.size.x .. "," .. def.size.y
form = form .. def.element_type .. "[" .. offset .. ";" .. size .. ";" .. def.text .. "]"
end
end
return form
end
local function get_page(key, meta, playername)
local form = table.copy(libri_bg)
local chapters = minetest.deserialize(meta:get_string("chapters")) or {}
local chap_no = 0
for _ in pairs(chapters) do
chap_no = chap_no + 1
end
local page = pages[key]
for _, element in ipairs(page) do
if type(element) == "table" then
local element_rendered = libri.render_element(element, meta, playername)
table.insert(form, element_rendered)
else
table.insert(form, element)
end
end
table.insert(form, "style[drp_font_scale;noclip=true]")
table.insert(form, libri_drp_font_scale)
table.insert(form, "style[drp_font_scale;noclip=true]")
local def = minetest.registered_entities[key]
if def then
if def.follow then
table.insert(form, get_item_list(def.follow, 12.45, 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, get_item_list(drops, 8, 8.05))
end
end
return table.concat(form, "")
end
-- Iterate through Animal textures and Biomes
local libri_players = {}
local function iterate_libri_images()
for page, elements in pairs(pages) do
if page ~= "home" then
for _, info in ipairs(elements) do
if info.texture_iter then
local def = minetest.registered_entities[page]
local textures = mob_textures(page, info.mesh_iter)
local tex_i = info.texture_iter
info.texture_iter = (textures[tex_i + 1] and tex_i + 1) or 1
local mesh_i = info.mesh_iter
if info.texture_iter < 2 then -- Only iterate mesh if all textures have been shown
info.mesh_iter = def.meshes and ((def.meshes[mesh_i + 1] and mesh_i + 1) or 1)
textures = mob_textures(page, info.mesh_iter)
end
local mesh = (info.mesh_iter and def.meshes[info.mesh_iter]) or def.mesh
info.text = "mesh;" .. mesh .. ";" .. textures[info.texture_iter] .. ";-30,225;false;false;0,0;0]"
end
if info.biome_iter then
local mobname = page:split(":")[2]
local biomes = asuna.features.animals[mobname] or {}
if biomes[info.biome_iter + 1] then
info.biome_iter = info.biome_iter + 1
else
info.biome_iter = 1
end
local spawn_biome = biomes[info.biome_iter] or "grassland"
if info.element_type == "image" then
info.text = biome_cubes[spawn_biome]
else
info.text = correct_string(asuna.biomes[spawn_biome].name)
end
end
end
end
end
for name, page in pairs(libri_players) do
local player = minetest.get_player_by_name(name)
if player
and spawn_biomes[page] then
local meta = player:get_wielded_item():get_meta()
minetest.show_formspec(name, "animalia:libri_" .. page:split(":")[2], get_page(page, meta, name))
end
end
minetest.after(2, iterate_libri_images)
end
iterate_libri_images()
-- Craftitem
minetest.register_craftitem("animalia:libri_animalia", {
description = "Libri Animalia",
inventory_image = "animalia_libri_animalia.png",
stack_max = 1,
groups = {book = 1},
on_place = function(itemstack, player)
local meta = itemstack:get_meta()
if meta:get_string("pages") ~= "" then meta:set_string("pages", "") end
local name = player:get_player_name()
minetest.show_formspec(name, "animalia:libri_home_1", get_page("home_1", meta, name))
libri_players[name] = "home_1"
end,
on_secondary_use = function(itemstack, player, pointed)
local meta = itemstack:get_meta()
if meta:get_string("pages") ~= "" then meta:set_string("pages", "") end
local chapters = minetest.deserialize(meta:get_string("chapters")) or {}
if pointed
and pointed.type == "object" then
local ent = pointed.ref and pointed.ref:get_luaentity()
if ent
and pages[ent.name]
and not chapters[ent.name] then
chapters[ent.name] = true
itemstack:get_meta():set_string("chapters", minetest.serialize(chapters))
player:set_wielded_item(itemstack)
end
return itemstack
end
local name = player:get_player_name()
minetest.show_formspec(name, "animalia:libri_home_1", get_page("home_1", meta, name))
libri_players[name] = "home_1"
end
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
local plyr_name = player:get_player_name()
local wielded_item = player:get_wielded_item()
local meta = wielded_item:get_meta()
if formname:match("animalia:libri_") then
for page in pairs(pages) do
if not page:match("^home") then
local name = page:split(":")[2]
if fields["btn_" .. name] then
minetest.show_formspec(plyr_name, "animalia:libri_" .. name, get_page(page, meta, plyr_name))
libri_players[plyr_name] = page
return true
end
end
end
if fields.btn_next then
local current_no = tonumber(formname:sub(-1))
local page = "home_" .. current_no + 1
if pages[page] then
minetest.show_formspec(plyr_name, "animalia:libri_" .. page, get_page(page, meta, plyr_name))
libri_players[plyr_name] = page
return true
end
end
if fields.btn_last then
local current_no = tonumber(formname:sub(-1))
local page = "home_" .. current_no - 1
if pages[page] then
minetest.show_formspec(plyr_name, "animalia:libri_" .. page, get_page(page, meta, plyr_name))
libri_players[plyr_name] = page
return true
end
end
if fields.drp_font_scale then
animalia.libri_font_size[plyr_name] = fields.drp_font_scale
local page = libri_players[plyr_name]
if not page then return end
minetest.show_formspec(plyr_name, "animalia:libri_" .. page, get_page(page, meta, plyr_name))
end
if fields.quit or fields.key_enter then
libri_players[plyr_name] = nil
end
end
end)

2065
mods/animalia/api/mob_ai.lua Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,409 @@
--------------
-- Spawning --
--------------
local random = math.random
local function table_contains(tbl, val)
for _, v in pairs(tbl) do
if v == val then
return true
end
end
return false
end
local common_spawn_chance = tonumber(minetest.settings:get("animalia_common_chance")) or 60000
local ambient_spawn_chance = tonumber(minetest.settings:get("animalia_ambient_chance")) or 9000
local pest_spawn_chance = tonumber(minetest.settings:get("animalia_pest_chance")) or 3000
local predator_spawn_chance = tonumber(minetest.settings:get("animalia_predator_chance")) or 45000
-- Get Biomes -- already happens via Asuna/biomes.lua
--[[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_abm_spawn("animalia:grizzly_bear", {
chance = predator_spawn_chance,
min_height = -1,
max_height = 31000,
min_group = 1,
max_group = 1,
biomes = asuna.features.animals.grizzly_bear,
nodes = {"group:sand","group:soil","group:snowy"},
})
creatura.register_abm_spawn("animalia:chicken", {
chance = common_spawn_chance,
spawn_active = true,
min_height = 0,
max_height = 31000,
min_group = 3,
max_group = 5,
spawn_cap = 3,
biomes = asuna.features.animals.chicken,
nodes = {"group:soil"},
})
creatura.register_abm_spawn("animalia:cat", {
chance = common_spawn_chance,
min_height = 0,
max_height = 31000,
min_group = 1,
max_group = 2,
biomes = asuna.features.animals.cat,
nodes = {"group:soil"},
neighbors = {"group:wood"}
})
creatura.register_abm_spawn("animalia:cow", {
chance = common_spawn_chance,
spawn_active = true,
min_height = 0,
max_height = 31000,
min_group = 3,
max_group = 4,
spawn_cap = 3,
biomes = asuna.features.animals.cow,
nodes = {"group:soil"},
neighbors = {"air", "group:grass", "group:flora"}
})
creatura.register_abm_spawn("animalia:fox", {
chance = predator_spawn_chance,
min_height = 0,
max_height = 31000,
min_group = 1,
max_group = 2,
biomes = asuna.features.animals.fox,
nodes = {"group:soil"},
})
creatura.register_abm_spawn("animalia:horse", {
chance = common_spawn_chance,
spawn_active = true,
min_height = 0,
max_height = 31000,
min_group = 3,
max_group = 4,
spawn_cap = 3,
biomes = asuna.features.animals.horse,
nodes = {"group:soil"},
neighbors = {"air", "group:grass", "group:flora"}
})
creatura.register_abm_spawn("animalia:rat", {
chance = pest_spawn_chance,
interval = 60,
min_height = -1,
max_height = 31000,
min_group = 1,
max_group = 3,
spawn_in_nodes = true,
biomes = asuna.features.animals.rat,
nodes = {"group:crop"}
})
creatura.register_abm_spawn("animalia:owl", {
chance = predator_spawn_chance,
interval = 60,
min_height = 3,
max_height = 31000,
min_group = 1,
max_group = 1,
spawn_cap = 1,
biomes = asuna.features.animals.owl,
nodes = {"group:leaves"}
})
creatura.register_abm_spawn("animalia:opossum", {
chance = predator_spawn_chance,
interval = 60,
min_height = -1,
max_height = 31000,
min_group = 1,
max_group = 2,
biomes = asuna.features.animals.opossum,
nodes = {"group:soil", "group:leaves"}
})
creatura.register_abm_spawn("animalia:pig", {
chance = common_spawn_chance,
spawn_active = true,
min_height = 0,
max_height = 31000,
min_group = 2,
max_group = 3,
spawn_cap = 3,
biomes = asuna.features.animals.pig,
nodes = {"group:soil"},
})
creatura.register_abm_spawn("animalia:reindeer", {
chance = common_spawn_chance,
spawn_active = true,
min_height = 0,
max_height = 31000,
min_group = 6,
max_group = 8,
spawn_cap = 3,
biomes = asuna.features.animals.reindeer,
nodes = {"group:soil"},
})
creatura.register_abm_spawn("animalia:sheep", {
chance = common_spawn_chance,
spawn_active = true,
min_height = 0,
max_height = 31000,
min_group = 3,
max_group = 6,
spawn_cap = 3,
biomes = asuna.features.animals.sheep,
nodes = {"group:soil"},
neighbors = {"air", "group:grass", "group:flora"}
})
creatura.register_abm_spawn("animalia:turkey", {
chance = common_spawn_chance,
spawn_active = true,
min_height = 0,
max_height = 31000,
min_group = 3,
max_group = 4,
spawn_cap = 3,
biomes = asuna.features.animals.turkey,
nodes = {"group:soil"},
})
creatura.register_abm_spawn("animalia:wolf", {
chance = predator_spawn_chance,
min_height = 0,
max_height = 31000,
min_group = 2,
max_group = 3,
biomes = asuna.features.animals.wolf,
nodes = {"group:soil"},
})
-- Ambient Spawning
creatura.register_abm_spawn("animalia:bat", {
chance = ambient_spawn_chance,
interval = 30,
min_light = 0,
min_height = -31000,
max_height = 1,
min_group = 3,
max_group = 5,
spawn_cap = 6,
biomes = asuna.features.animals.bat,
nodes = {"group:stone"}
})
creatura.register_abm_spawn("animalia:song_bird", {
chance = ambient_spawn_chance,
interval = 60,
min_light = 0,
min_height = 1,
max_height = 31000,
min_group = 6,
max_group = 12,
spawn_cap = 6,
biomes = asuna.features.animals.song_bird,
nodes = {"group:leaves", "animalia:nest_song_bird"},
neighbors = {"group:leaves"}
})
creatura.register_on_spawn("animalia:song_bird", function(self, pos)
local nests = minetest.find_nodes_in_area_under_air(
{x = pos.x - 16, y = pos.y - 16, z = pos.z - 16},
{x = pos.x + 16, y = pos.y + 16, z = pos.z + 16},
"animalia:nest_song_bird"
)
if nests[1] then return end
local node = minetest.get_node(pos)
if node.name == "air" then
minetest.set_node(pos, {name = "animalia:nest_song_bird"})
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"})
end
end
end)
creatura.register_abm_spawn("animalia:frog", {
chance = ambient_spawn_chance * 0.75,
interval = 60,
min_light = 0,
min_height = -1,
max_height = 8,
min_group = 1,
max_group = 2,
--neighbors = {"group:water"},
nodes = {"group:soil"}
})
creatura.register_on_spawn("animalia:frog", function(self, pos)
local biome_data = minetest.get_biome_data(pos)
local biome_name = minetest.get_biome_name(biome_data.biome)
if table_contains(animalia.registered_biome_groups["tropical"].biomes, biome_name) then
self:set_mesh(3)
elseif table_contains(animalia.registered_biome_groups["temperate"].biomes, biome_name)
or table_contains(animalia.registered_biome_groups["boreal"].biomes, biome_name) then
self:set_mesh(1)
elseif table_contains(animalia.registered_biome_groups["grassland"].biomes, biome_name) then
self:set_mesh(2)
else
self.object:remove()
end
local activate = self.activate_func
activate(self)
end)
creatura.register_abm_spawn("animalia:tropical_fish", {
chance = ambient_spawn_chance,
min_height = -40,
max_height = -2,
min_group = 6,
max_group = 12,
biomes = asuna.features.animals.tropical_fish,
nodes = {"group:water","mapgen_water_source"},
neighbors = {"group:coral","group:sand"},
spawn_in_nodes = true,
})
-- World Gen Spawning
minetest.register_node("animalia:spawner", {
description = "???",
drawtype = "airlike",
walkable = false,
pointable = false,
buildable_to = true,
paramtype = "light",
sunlight_propagates = true,
groups = {oddly_breakable_by_hand = 1, not_in_creative_inventory = 1}
})
minetest.register_decoration({
name = "animalia:world_gen_spawning",
deco_type = "simple",
place_on = {"group:stone", "group:sand", "group:soil"},
sidelen = 1,
fill_ratio = 0.0001, -- One node per chunk
decoration = "animalia:spawner"
})
local function do_on_spawn(pos, obj)
local name = obj and obj:get_luaentity().name
if not name then return end
local spawn_functions = creatura.registered_on_spawns[name] or {}
if #spawn_functions > 0 then
for _, func in ipairs(spawn_functions) do
func(obj:get_luaentity(), pos)
if not obj:get_yaw() then break end
end
end
end
minetest.register_abm({
label = "[animalia] World Gen Spawning",
nodenames = {"animalia:spawner"},
interval = 10, -- TODO: Set this to 1 if world is singleplayer and just started
chance = 16,
action = function(pos, _, active_object_count)
minetest.remove_node(pos)
if active_object_count > 4 then return end
local spawnable_mobs = {}
local current_biome = minetest.get_biome_name(minetest.get_biome_data(pos).biome)
local spawn_definitions = creatura.registered_mob_spawns
for mob, def in pairs(spawn_definitions) do
if mob:match("^animalia:")
and def.biomes
and table_contains(def.biomes, current_biome) then
table.insert(spawnable_mobs, mob)
end
end
if #spawnable_mobs > 0 then
local mob_to_spawn = spawnable_mobs[math.random(#spawnable_mobs)]
local spawn_definition = creatura.registered_mob_spawns[mob_to_spawn]
local group_size = random(spawn_definition.min_group or 1, spawn_definition.max_group or 1)
local obj
local mob_positions
if group_size > 1 then
mob_positions = {}
local offset
local spawn_pos
for _ = 1, group_size do
offset = group_size * 0.5
spawn_pos = creatura.get_ground_level({
x = pos.x + random(-offset, offset),
y = pos.y,
z = pos.z + random(-offset, offset)
}, 3)
if not creatura.is_pos_moveable(spawn_pos, 0.5, 0.5) then
table.insert(mob_positions,pos)
else
table.insert(mob_positions,spawn_pos)
end
end
else
mob_positions = { pos }
end
for _,mpos in ipairs(mob_positions) do
local node = minetest.get_node(vector.offset(mpos,0,-1,0)).name
for _,target in ipairs(spawn_definition.nodes) do
if node == target or (target:find("^group:") and minetest.get_item_group(node,target:sub(7)) > 0) then
do_on_spawn(mpos, minetest.add_entity(mpos, mob_to_spawn))
minetest.log("action","[Animalia] [ABM Spawning] Spawned " .. mob_to_spawn .. " at " .. minetest.pos_to_string(mpos))
goto next_mpos
end
end
::next_mpos::
end
end
end
})

View file

@ -0,0 +1,22 @@
local mod_storage = minetest.get_mod_storage()
local data = {
spawn_points = minetest.deserialize(mod_storage:get_string("spawn_points")) or {},
libri_font_size = minetest.deserialize(mod_storage:get_string("libri_font_size")) or {},
}
local function save()
mod_storage:set_string("spawn_points", minetest.serialize(data.spawn_points))
mod_storage:set_string("libri_font_size", minetest.serialize(data.libri_font_size))
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