mirror of
https://github.com/ElCeejo/animalia.git
synced 2025-03-15 12:21:24 +00:00
1230 lines
No EOL
40 KiB
Lua
1230 lines
No EOL
40 KiB
Lua
---------
|
|
-- 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 --
|
|
-------------------
|
|
|
|
function animalia.initialize_api(self)
|
|
self.gender = self:recall("gender") or nil
|
|
if not self.gender then
|
|
local genders = {"male", "female"}
|
|
self.gender = self:memorize("gender", genders[random(2)])
|
|
end
|
|
self.food = self:recall("food") or 0
|
|
self.gotten = self:recall("gotten") or false
|
|
self.breeding = false
|
|
self.breeding_cooldown = self:recall("breeding_cooldown") or 0
|
|
if self.growth_scale then
|
|
self:memorize("growth_scale", self.growth_scale) -- This is for spawning children
|
|
end
|
|
self.growth_scale = self:recall("growth_scale") or 1
|
|
self:set_scale(self.growth_scale)
|
|
if self.growth_scale < 0.8
|
|
and self.child_textures then
|
|
if not self.texture_no then
|
|
self.texture_no = random(#self.child_textures)
|
|
end
|
|
self:set_texture(self.texture_no, self.child_textures)
|
|
return
|
|
elseif self.growth_scale > 0.7
|
|
and self.male_textures
|
|
and self.female_textures then
|
|
if not self.texture_no then
|
|
self.texture_no = random(#self[self.gender .. "_textures"])
|
|
end
|
|
self:set_texture(self.texture_no, self[self.gender .. "_textures"])
|
|
return
|
|
end
|
|
end
|
|
|
|
function animalia.step_timers(self)
|
|
self.breeding_cooldown = self.breeding_cooldown - self.dtime
|
|
if self.breeding
|
|
and self.breeding_cooldown <= 30 then
|
|
self.breeding = false
|
|
end
|
|
self:memorize("breeding_cooldown", self.breeding_cooldown)
|
|
end
|
|
|
|
function animalia.do_growth(self, interval)
|
|
if self.growth_scale
|
|
and self.growth_scale < 0.9 then
|
|
if self:timer(interval) then
|
|
self.growth_scale = self.growth_scale + 0.1
|
|
self:set_scale(self.growth_scale)
|
|
if self.growth_scale < 0.8
|
|
and self.child_textures then
|
|
local tex_no = self.texture_no
|
|
if not self.child_textures[tex_no] then
|
|
tex_no = 1
|
|
end
|
|
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
|
|
|
|
-----------------------
|
|
-- 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_v2.png]",
|
|
"image[-0.7,-0.5;17.5,11.5;animalia_libri_info_fg.png]",
|
|
-- Mesh
|
|
"model[1.5,1.5;5,5;libri_mesh;" .. mesh .. ";" .. texture .. ";-30,225;false;false;0,0;0]",
|
|
-- Spawn Biome Group
|
|
"image[0.825,8.15;1,1;" .. invcube .. "]",
|
|
"tooltip[0.825,8.15;1,1;" .. correct_name(spawn_biome) .. "]",
|
|
-- Health
|
|
"image[2.535,8.15;1,1;animalia_libri_health_fg.png]",
|
|
"label[3.25,9;x" .. def.max_health / 2 .. "]",
|
|
-- Net
|
|
"item_image[4.25,8.15;1,1;animalia:lasso]",
|
|
"image[4.75,8.75;0.5,0.5;animalia_libri_true_icon.png]",
|
|
-- Lasso
|
|
"item_image[6,8.15;1,1;animalia:net]",
|
|
"image[6.5,8.75;0.5,0.5;animalia_libri_true_icon.png]",
|
|
-- Labels
|
|
"label[9.5,7.25;" .. minetest.colorize("#383329", "Drops:") .. "]",
|
|
"label[14,7.25;" .. minetest.colorize("#383329", "Eats:") .. "]",
|
|
-- Info Text
|
|
"label[9.25,1.5;" .. minetest.colorize("#000000", "Domestication:") .. "]",
|
|
"label[13.5,1.5;" .. minetest.colorize("#000000", "Behavior:") .. "]",
|
|
}
|
|
-- Mob Info
|
|
if libri_animal_info[mob_name] then
|
|
if libri_animal_info[mob_name].info.domestication then
|
|
table.insert(form, offset_info_text(8.5, 2, libri_animal_info[mob_name].info.domestication))
|
|
end
|
|
if libri_animal_info[mob_name].info.behavior then
|
|
table.insert(form, offset_info_text(12.5, 2, libri_animal_info[mob_name].info.behavior))
|
|
end
|
|
end
|
|
if def.follow then
|
|
table.insert(form, animalia.get_item_list(def.follow, 12.35, 8.05))
|
|
end
|
|
if def.drops then
|
|
local drops = {}
|
|
for i = 1, #def.drops do
|
|
table.insert(drops, def.drops[i].name)
|
|
end
|
|
table.insert(form, animalia.get_item_list(drops, 8, 8.05))
|
|
end
|
|
return table.concat(form, "")
|
|
end
|
|
|
|
local function update_libri(player_name, mob_name)
|
|
if not animalia_libri_info[player_name]
|
|
or animalia_libri_info[player_name].name ~= mob_name then
|
|
return
|
|
end
|
|
local texture_idx = animalia_libri_info[player_name].texture_idx or 1
|
|
local biome_idx = animalia_libri_info[player_name].biome_idx or 1
|
|
if texture_idx >= #get_textures(mob_name) then
|
|
texture_idx = 1
|
|
else
|
|
texture_idx = texture_idx + 1
|
|
end
|
|
local spawn_biomes = animalia.registered_biome_groups[spawn_biomes[mob_name]].biomes
|
|
if biome_idx >= #spawn_biomes then
|
|
biome_idx = 1
|
|
else
|
|
biome_idx = biome_idx + 1
|
|
end
|
|
animalia_libri_info[player_name] = {
|
|
texture_idx = texture_idx,
|
|
biome_idx = biome_idx,
|
|
name = mob_name
|
|
}
|
|
minetest.show_formspec(player_name, "animalia:libri_" .. string.split(mob_name, ":")[2], get_libri_page(mob_name, player_name))
|
|
minetest.after(4, function()
|
|
update_libri(player_name, mob_name)
|
|
end)
|
|
end
|
|
|
|
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
|
local player_name = player:get_player_name()
|
|
if formname == "animalia:libri_main" then
|
|
animalia_libri_info[player_name] = {}
|
|
for i = 1, #animalia.animals do
|
|
local name = string.split(animalia.animals[i], ":")[2]
|
|
if fields["pg_" .. name] then
|
|
-- Get data for mob and biome visuals
|
|
animalia_libri_info[player_name] = {
|
|
texture_idx = 1,
|
|
biome_idx = 1,
|
|
name = animalia.animals[i]
|
|
}
|
|
update_libri(player_name, animalia.animals[i])
|
|
break
|
|
end
|
|
end
|
|
if fields["btn_next"] then
|
|
local pages = animalia.libri_pages[player_name]
|
|
if pages
|
|
and #pages > 1 then
|
|
animalia.show_libri_main_form(player, pages, 2)
|
|
end
|
|
end
|
|
end
|
|
if formname:match("^animalia:libri_") then
|
|
if fields.quit or fields.key_enter then
|
|
animalia_libri_info[player_name] = nil
|
|
end
|
|
end
|
|
end) |