Minor Behavior Overhaul

This commit is contained in:
ElCeejo 2024-01-04 11:15:02 -08:00
parent 20c55da709
commit 09f2dce19f
30 changed files with 2118 additions and 3130 deletions

View File

@ -8,7 +8,8 @@ globals = {
"animalia",
"farming",
"mcl_player",
"player_api"
"player_api",
"armor"
}
read_globals = {

View File

@ -42,13 +42,9 @@ end
-- Vector Math --
local vec_dir = vector.direction
local vec_add = vector.add
local vec_sub = vector.subtract
local vec_multi = vector.multiply
local vec_normal = vector.normalize
local vec_divide = vector.divide
local vec_len = vector.length
local 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
@ -203,6 +199,56 @@ 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
@ -316,6 +362,38 @@ function animalia.add_food_particle(self, item_name)
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 --
----------
@ -329,6 +407,7 @@ 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()
@ -342,6 +421,35 @@ function animalia.get_dropped_food(self, item, radius)
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)
@ -569,7 +677,8 @@ function animalia.feed(self, clicker, tame, breed)
end
function animalia.mount(self, player, params)
if not creatura.is_alive(player) then
if not creatura.is_alive(player)
or player:get_attach() then
return
end
local plyr_name = player:get_player_name()

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@ local generate_mobs = {
["animalia:cat"] = "Cat",
["animalia:chicken"] = "Chicken",
["animalia:cow"] = "Cow",
["animalia:opossum"] = "Opossum",
["animalia:owl"] = "Owl",
["animalia:tropical_fish"] = "Tropical Fish",
["animalia:fox"] = "Fox",
@ -54,6 +55,7 @@ local spawn_biomes = {
["animalia:cat"] = "urban",
["animalia:chicken"] = "tropical",
["animalia:cow"] = "grassland",
["animalia:opossum"] = "temperate",
["animalia:owl"] = "temperate",
["animalia:tropical_fish"] = "ocean",
["animalia:fox"] = "boreal",
@ -109,6 +111,7 @@ 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",
@ -521,4 +524,4 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
libri_players[plyr_name] = nil
end
end
end)
end)

1822
api/mob_ai.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@ -127,6 +127,17 @@ creatura.register_abm_spawn("animalia:owl", {
nodes = {"group:leaves"}
})
creatura.register_abm_spawn("animalia:opossum", {
chance = predator_spawn_chance,
interval = 60,
min_height = -1,
max_height = 1024,
min_group = 1,
max_group = 2,
biomes = animalia.registered_biome_groups["boreal"].biomes,
nodes = {"group:soil", "group:leaves"}
})
creatura.register_abm_spawn("animalia:pig", {
chance = common_spawn_chance,
chance_on_load = 64,

View File

@ -3,20 +3,11 @@ 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 {},
bound_horse = minetest.deserialize(mod_storage:get_string("bound_horse")) 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))
for name, bound_data in pairs(data.bound_horse) do
if bound_data
and bound_data.obj then
data.bound_horse[name].obj = nil
end
end
mod_storage:set_string("bound_horse", minetest.serialize(data.bound_horse))
end
minetest.register_on_shutdown(save)

View File

@ -168,6 +168,12 @@ minetest.register_craftitem("animalia:feather", {
groups = {flammable = 2, feather = 1},
})
minetest.register_craftitem("animalia:pelt_bear", {
description = "Bear Pelt",
inventory_image = "animalia_pelt_bear.png",
groups = {flammable = 2, pelt = 1},
})
-- Meat --
minetest.register_craftitem("animalia:beef_raw", {

View File

@ -6,7 +6,6 @@ local storage = dofile(path .. "/api/storage.lua")
animalia.spawn_points = storage.spawn_points
animalia.libri_font_size = storage.libri_font_size
animalia.bound_horse = storage.bound_horse
animalia.pets = {}
@ -90,8 +89,16 @@ end)
-- Load Files
local function load_file(filepath, filename)
if io.open(filepath .. "/" .. filename, "r") then
dofile(filepath .. "/" .. filename)
else
minetest.log("action", "[Creatura] The file " .. filename .. " could not be loaded.")
end
end
dofile(path.."/api/api.lua")
dofile(path.."/api/behaviors.lua")
dofile(path.."/api/mob_ai.lua")
dofile(path.."/api/lasso.lua")
dofile(path.."/craftitems.lua")
@ -115,10 +122,25 @@ animalia.animals = {
"animalia:wolf",
}
for i = 1, #animalia.animals do
local name = animalia.animals[i]:split(":")[2]
dofile(path.."/mobs/" .. name .. ".lua")
end
dofile(path.."/api/api.lua")
load_file(path .. "/mobs", "bat.lua")
load_file(path .. "/mobs", "cat.lua")
load_file(path .. "/mobs", "chicken.lua")
load_file(path .. "/mobs", "cow.lua")
load_file(path .. "/mobs", "fox.lua")
load_file(path .. "/mobs", "frog.lua")
load_file(path .. "/mobs", "horse.lua")
load_file(path .. "/mobs", "opossum.lua")
load_file(path .. "/mobs", "owl.lua")
load_file(path .. "/mobs", "pig.lua")
load_file(path .. "/mobs", "rat.lua")
load_file(path .. "/mobs", "reindeer.lua")
load_file(path .. "/mobs", "sheep.lua")
load_file(path .. "/mobs", "song_bird.lua")
load_file(path .. "/mobs", "turkey.lua")
load_file(path .. "/mobs", "tropical_fish.lua")
load_file(path .. "/mobs", "wolf.lua")
if minetest.settings:get_bool("spawn_mobs", true) then
dofile(path.."/api/spawning.lua")

View File

@ -0,0 +1,5 @@
A unique Marsupial and a key member
of it's ecosystem. The Opossum helps
keep pests like rodents and insects
under control, allowing plantlife and
crops to flourish.

View File

@ -81,48 +81,9 @@ creatura.register_mob("animalia:bat", {
-- Functions
utility_stack = {
{
utility = "animalia:aerial_wander",
step_delay = 0.25,
get_score = function(self)
local pos = self.object:get_pos()
if not pos then return end
local player = creatura.get_nearby_player(self)
local plyr_pos = player and not player:get_player_control().sneak and player:get_pos()
if plyr_pos then
local dist = vec_dist(pos, plyr_pos)
self._target = player
self.is_landed = false
return (self.tracking_range - dist) / self.tracking_range, {self}
end
return 0.1, {self}
end
},
{
utility = "animalia:swim_to_land",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.3, {self}
end
return 0
end
},
{
utility = "animalia:fly_to_roost",
get_score = function(self)
local pos = self.object:get_pos()
if not pos then return end
local home = animalia.is_day and self.home_position
if (home
and home.x
and vec_dist(pos, home) < 8)
or self.is_landed then
return 0.6, {self}
end
return 0
end
}
animalia.mob_ai.fly_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.bat_seek_home
},
is_home = function(pos, home_pos)

View File

@ -2,10 +2,6 @@
-- Cat --
---------
local random = math.random
local vec_dist, vec_add, vec_sub = vector.distance, vector.add, vector.subtract
local follow = {
"animalia:poultry_raw"
}
@ -17,24 +13,6 @@ if minetest.registered_items["ethereal:fish_raw"] then
}
end
local function find_glass_vessel(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),
{"vessels:glass_bottle", "vessels:drinking_glass"}) or {}
if #nodes < 1 then return end
return nodes[math.random(#nodes)]
end
local function destroy_glass_vessel(self, pos)
if not minetest.is_protected(pos, "") then
minetest.remove_node(pos)
minetest.add_item(pos, "vessels:glass_fragments")
return true
end
end
creatura.register_mob("animalia:cat", {
-- Engine Props
visual_size = {x = 10, y = 10},
@ -101,6 +79,9 @@ creatura.register_mob("animalia:cat", {
follow = follow,
drops = {},
-- Behavior Parameters
is_skittish_mob = true,
-- Animalia Props
flee_puncher = true,
catch_with_net = true,
@ -111,119 +92,17 @@ creatura.register_mob("animalia:cat", {
pivot_h = 0.4,
pivot_v = 0.4
},
skittish_wander = true,
-- Functions
utility_stack = {
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:swim_to_land",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.3, {self}
end
return 0
end
},
{
utility = "animalia:walk_to_pos_and_interact",
step_delay = 0.25,
get_score = function(self)
if random(2) < 2 then
return 0.2, {self, find_glass_vessel, destroy_glass_vessel, nil, 10}
end
return 0
end
},
{
utility = "animalia:bother_player",
step_delay = 0.25,
get_score = function(self)
if random(24) > 1 then return 0 end
local owner = self.owner and minetest.get_player_by_name(self.owner)
local pos = self.object:get_pos()
if not pos then return end
local trust = self.trust[self.owner] or 0
if trust > 3
and owner
and vec_dist(pos, owner:get_pos()) < self.tracking_range then
return 0.2, {self, owner}
end
return 0
end
},
{
utility = "animalia:stay",
step_delay = 0.25,
get_score = function(self)
local trust = (self.owner and self.trust[self.owner]) or 0
if trust < 5 then return 0 end
local order = self.order or "wander"
if order == "sit" then
return 0.5, {self}
end
return 0
end
},
{
utility = "animalia:play_with_player",
step_delay = 0.25,
get_score = function(self)
if self.trust_cooldown > 0 then return 0 end
local owner = self.owner and minetest.get_player_by_name(self.owner)
if owner
and owner:get_wielded_item():get_name() == "animalia:cat_toy" then
return 0.6, {self, owner}
end
return 0
end
},
{
utility = "animalia:follow_player",
get_score = function(self)
local lasso_tgt = self._lassod_to
local lasso = type(lasso_tgt) == "string" and minetest.get_player_by_name(lasso_tgt)
local trust = (self.owner and self.trust[self.owner]) or 0
local owner = self.owner and self.order == "follow" and trust > 4 and minetest.get_player_by_name(self.owner)
local force = (lasso and lasso ~= false) or (owner and owner ~= false)
local player = (force and (owner or lasso)) or creatura.get_nearby_player(self)
if player
and self:follow_wielded_item(player) then
return 0.6, {self, player, force}
end
return 0
end
},
{
utility = "animalia:attack_target",
get_score = function(self)
local target = self._target or creatura.get_nearby_object(self, "animalia:rat")
local tgt_pos = target and target:get_pos()
if tgt_pos
and self:is_pos_safe(tgt_pos) then
return 0.7, {self, target}
end
return 0
end
},
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name) then
return 0.8, {self}
end
return 0
end
}
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.cat_seek_vessel,
animalia.mob_ai.cat_stay,
--animalia.mob_ai.cat_play
animalia.mob_ai.cat_follow_owner,
animalia.mob_ai.basic_attack,
animalia.mob_ai.basic_breed
},
activate_func = function(self)

View File

@ -81,36 +81,11 @@ creatura.register_mob("animalia:chicken", {
-- Functions
utility_stack = {
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:swim_to_land",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.5, {self}
end
return 0
end
},
animalia.global_utils.basic_follow,
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name) then
return 0.4, {self}
end
return 0
end
},
animalia.global_utils.basic_flee
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.tamed_follow_owner,
animalia.mob_ai.basic_breed,
animalia.mob_ai.basic_flee
},
add_child = function(self)

View File

@ -94,36 +94,11 @@ creatura.register_mob("animalia:cow", {
-- Functions
utility_stack = {
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:swim_to_land",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.3, {self}
end
return 0
end
},
animalia.global_utils.basic_follow,
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name) then
return 0.5, {self}
end
return 0
end
},
animalia.global_utils.basic_flee
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.tamed_follow_owner,
animalia.mob_ai.basic_breed,
animalia.mob_ai.basic_flee
},
activate_func = function(self)

View File

@ -2,45 +2,6 @@
-- Fox --
---------
local vec_dir, vec_dist = vector.direction, vector.distance
local dir2yaw = minetest.dir_to_yaw
local function get_food_pos(self)
local _, pos = animalia.get_dropped_food(self)
return pos
end
local function eat_dropped_food(self)
local pos = self.object:get_pos()
if not pos then return end
local food = 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
creatura.register_mob("animalia:fox", {
-- Engine Props
visual_size = {x = 10, y = 10},
@ -79,6 +40,10 @@ creatura.register_mob("animalia:fox", {
-- Behavior Parameters
is_skittish_mob = true,
attack_list = {
"animalia:chicken",
"animalia:rat"
},
-- Animalia Props
flee_puncher = true,
@ -93,85 +58,13 @@ creatura.register_mob("animalia:fox", {
-- Functions
utility_stack = {
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:swim_to_land",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.3, {self}
end
return 0
end
},
{
utility = "animalia:attack_target",
get_score = function(self)
local target = self._target or creatura.get_nearby_object(self, {"animalia:rat", "animalia:chicken"})
local tgt_pos = target and target:get_pos()
if tgt_pos
and self:is_pos_safe(tgt_pos) then
return 0.4, {self, target}
end
return 0
end
},
{
utility = "animalia:flee_from_target",
get_score = function(self)
local target = self._puncher or creatura.get_nearby_player(self)
local pos, tgt_pos = self.object:get_pos(), target and target:get_pos()
if not pos then return end
if not tgt_pos then self._puncher = nil return 0 end
local sneaking = target:get_player_control().sneak
if not sneaking then
local dist = vec_dist(pos, tgt_pos)
local score = (self.tracking_range - dist) / self.tracking_range
self._puncher = target
return score / 2, {self, target}
end
self._puncher = nil
return 0
end
},
{
utility = "animalia:walk_to_pos_and_interact",
get_score = function(self)
if math.random(14) < 2 then
return 0.7, {self, get_food_pos, eat_dropped_food, nil, 12}
end
return 0
end
},
{
utility = "animalia:follow_player",
get_score = function(self)
local lasso_tgt = self._lassod_to
local lasso = type(lasso_tgt) == "string" and minetest.get_player_by_name(lasso_tgt)
if lasso
and lasso:get_pos() then
return 0.6, {self, lasso, true}
end
return 0
end
},
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name) then
return 0.7, {self}
end
return 0
end
}
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.basic_attack,
animalia.mob_ai.fox_flee,
animalia.mob_ai.basic_seek_food,
animalia.mob_ai.tamed_follow_owner,
animalia.mob_ai.basic_breed
},
on_eat_drop = function(self)

View File

@ -4,80 +4,6 @@
local random = math.random
local vec_add = vector.add
local vec_dir = vector.direction
local vec_dist = vector.distance
local vec_sub = vector.subtract
local dir2yaw = minetest.dir_to_yaw
local function get_food_pos(self)
local _, pos = animalia.get_dropped_food(self)
return pos
end
local function eat_dropped_food(self)
local pos = self.object:get_pos()
if not pos then return end
local food = 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
local function get_bug_pos(self)
local pos = self.object:get_pos()
if not pos then return end
local food = minetest.find_nodes_in_area(
vec_sub(pos, 3),
vec_add(pos, 3),
self.follow
) or {}
return #food > 0 and food[1]
end
local function eat_bug(self)
local pos = self.object:get_pos()
if not pos then return end
local bug = get_bug_pos(self)
if not bug then return end
local dir = vec_dir(pos, bug)
local dist = vec_dist(pos, bug)
local frame = math.floor(dist * 10)
self.object:set_yaw(dir2yaw(dir))
animalia.move_head(self, dir2yaw(dir), dir.y)
creatura.action_idle(self, 0.4, "tongue_" .. frame)
minetest.remove_node(bug)
return true
end
local function poison_effect(object)
object:punch(object, 1.0, {
full_punch_interval = 1.0,
@ -119,252 +45,30 @@ local animations = {
local utility_stacks = {
{ -- Tree Frog
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:aquatic_wander",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.2, {self}
end
return 0
end
},
{
utility = "animalia:walk_to_pos_and_interact",
get_score = function(self)
if math.random(2) < 2 then
return 0.3, {self, get_bug_pos, eat_bug, nil, 0}
end
return 0
end
},
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name)
and self.in_liquid then
return 1, {self}
end
return 0
end
},
{
utility = "animalia:flop",
step_delay = 0.25,
get_score = function(self)
if not self.in_liquid
and self.growth_scale < 0.8 then
return 1, {self}
end
return 0
end
},
{
utility = "animalia:flee_from_target",
get_score = function(self)
if self.in_liquid then return 0 end
local pos = self.object:get_pos()
if not pos then return end
local target = self._puncher or self._target or creatura.get_nearby_player(self)
local tgt_pos = target and target:get_pos()
local plyr_name = (target and target:is_player() and target:get_player_name()) or ""
if tgt_pos then
local trust = self.trust[plyr_name] or 0
self._target = target -- stored to memory to avoid calling get_nearby_player again
return (10 - (vec_dist(pos, tgt_pos) + trust)) * 0.1, {self, target}
end
return 0
end
},
{
utility = "animalia:run_to_pos",
get_score = function(self)
if self.in_liquid then return 0 end
local pos = self.object:get_pos()
if not pos then return end
local water = minetest.find_nodes_in_area(vec_sub(pos, 1.5), vec_add(pos, 1.5), {"group:water"})
if not water[1] then return 0 end
local player = self._target
local plyr_name = player and player:is_player() and player:get_player_name()
if plyr_name then
local plyr_pos = player and player:get_pos()
local trust = self.trust[plyr_name] or 0
return (10 - (vec_dist(pos, plyr_pos) + trust)) * 0.1, {self, water[1]}
end
return 0
end
}
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_wander,
animalia.mob_ai.frog_seek_bug,
animalia.mob_ai.frog_breed,
animalia.mob_ai.basic_flee,
animalia.mob_ai.frog_flop,
animalia.mob_ai.frog_seek_water
},
{ -- Bull Frog
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:aquatic_wander",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.2, {self}
end
return 0
end
},
{
utility = "animalia:walk_to_pos_and_interact",
get_score = function(self)
if math.random(8) < 2 then
return 0.3, {self, get_food_pos, eat_dropped_food, nil, 12}
end
return 0
end
},
{
utility = "animalia:attack_target",
get_score = function(self)
local target = creatura.get_nearby_player(self) or creatura.get_nearby_object(self, "animalia:rat")
if target then
if target:is_player() then
local trust = self.trust[target:get_player_name()] or 0
if trust > 5 then
return 0
end
end
return 0.4, {self, target}
end
return 0
end
},
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name)
and self.in_liquid then
return 1, {self}
end
return 0
end
},
{
utility = "animalia:flop",
step_delay = 0.25,
get_score = function(self)
if not self.in_liquid
and self.growth_scale < 0.8 then
return 1, {self}
end
return 0
end
},
{
utility = "animalia:run_to_pos",
get_score = function(self)
if self.in_liquid then return 0 end
local pos = self.object:get_pos()
if not pos then return end
local water = minetest.find_nodes_in_area(vec_sub(pos, 1.5), vec_add(pos, 1.5), {"group:water"})
if not water[1] then return 0 end
local player = self._target
local plyr_name = player and player:is_player() and player:get_player_name()
if plyr_name then
local plyr_pos = player and player:get_pos()
local trust = self.trust[plyr_name] or 0
return (10 - (vec_dist(pos, plyr_pos) + trust)) * 0.1, {self, water[1]}
end
return 0
end
}
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_wander,
animalia.mob_ai.basic_seek_food,
animalia.mob_ai.basic_attack,
animalia.mob_ai.frog_breed,
animalia.mob_ai.frog_flop,
animalia.mob_ai.frog_seek_water
},
{
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:aquatic_wander",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.2, {self}
end
return 0
end
},
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name)
and self.in_liquid then
return 1, {self}
end
return 0
end
},
{
utility = "animalia:flop",
step_delay = 0.25,
get_score = function(self)
if not self.in_liquid
and self.growth_scale < 0.8 then
return 1, {self}
end
return 0
end
},
{
utility = "animalia:flee_from_target",
get_score = function(self)
if self.in_liquid then return 0 end
local pos = self.object:get_pos()
if not pos then return end
local target = self._puncher or self._target or creatura.get_nearby_player(self)
local tgt_pos = target and target:get_pos()
local plyr_name = (target and target:is_player() and target:get_player_name()) or ""
if tgt_pos then
local trust = self.trust[plyr_name] or 0
self._target = target -- stored to memory to avoid calling get_nearby_player again
return (10 - (vec_dist(pos, tgt_pos) + trust)) * 0.1, {self, target}
end
return 0
end
},
{
utility = "animalia:run_to_pos",
get_score = function(self)
if self.in_liquid then return 0 end
local pos = self.object:get_pos()
if not pos then return end
local water = minetest.find_nodes_in_area(vec_sub(pos, 1.5), vec_add(pos, 1.5), {"group:water"})
if not water[1] then return 0 end
local player = self._target
local plyr_name = player and player:is_player() and player:get_player_name()
if plyr_name then
local plyr_pos = player and player:get_pos()
local trust = self.trust[plyr_name] or 0
return (10 - (vec_dist(pos, plyr_pos) + trust)) * 0.1, {self, water[1]}
end
return 0
end
}
{ -- Poison Dart Frog
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_wander,
animalia.mob_ai.frog_breed,
animalia.mob_ai.basic_flee,
animalia.mob_ai.frog_flop,
animalia.mob_ai.frog_seek_water
}
}

View File

@ -239,49 +239,13 @@ creatura.register_mob("animalia:horse", {
pivot_v = 1.75
},
utility_stack = {
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.tamed_follow_owner,
animalia.mob_ai.basic_breed,
animalia.mob_ai.basic_flee,
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:swim_to_land",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.3, {self}
end
return 0
end
},
animalia.global_utils.basic_follow,
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name) then
return 0.5, {self}
end
return 0
end
},
{
utility = "animalia:flee_from_target",
get_score = function(self)
local puncher = self._puncher
if puncher
and puncher:get_pos() then
return 0.6, {self, puncher, true}
end
self._puncher = nil
return 0
end
},
{
utility = "animalia:horse_taming",
utility = "animalia:horse_tame",
get_score = function(self)
local rider = not self.owner and self.rider
if rider
@ -292,7 +256,7 @@ creatura.register_mob("animalia:horse", {
end
},
{
utility = "animalia:mount_horse",
utility = "animalia:horse_ride",
get_score = function(self)
if not self.owner then return 0 end
local owner = self.owner and minetest.get_player_by_name(self.owner)
@ -389,11 +353,6 @@ creatura.register_mob("animalia:horse", {
animalia.do_growth(self, 60)
animalia.update_lasso_effects(self)
animalia.random_sound(self)
if self.owner
and animalia.bound_horse[self.owner] then
animalia.bound_horse[self.owner].last_pos = self.object:get_pos()
end
end,
death_func = function(self)

View File

@ -1,45 +1,6 @@
---------
-- Fox --
---------
local vec_dir, vec_dist = vector.direction, vector.distance
local dir2yaw = minetest.dir_to_yaw
local function get_food_pos(self)
local _, pos = animalia.get_dropped_food(self)
return pos
end
local function eat_dropped_food(self)
local pos = self.object:get_pos()
if not pos then return end
local food = 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
-------------
-- Opossum --
-------------
creatura.register_mob("animalia:opossum", {
-- Engine Props
@ -59,6 +20,7 @@ creatura.register_mob("animalia:opossum", {
max_boids = 0,
despawn_after = 500,
stepheight = 1.1,
max_fall = 8,
sound = {},
hitbox = {
width = 0.25,
@ -81,6 +43,7 @@ creatura.register_mob("animalia:opossum", {
-- Behavior Parameters
is_skittish_mob = true,
attack_list = {"animalia:rat"},
-- Animalia Props
flee_puncher = true,
@ -95,85 +58,13 @@ creatura.register_mob("animalia:opossum", {
-- Functions
utility_stack = {
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:swim_to_land",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.3, {self}
end
return 0
end
},
{
utility = "animalia:attack_target",
get_score = function(self)
local target = self._target or creatura.get_nearby_object(self, {"animalia:rat"})
local tgt_pos = target and target:get_pos()
if tgt_pos
and self:is_pos_safe(tgt_pos) then
return 0.4, {self, target}
end
return 0
end
},
{
utility = "animalia:idle",
get_score = function(self)
local target = self._puncher or creatura.get_nearby_player(self)
local pos, tgt_pos = self.object:get_pos(), target and target:get_pos()
if not pos then return end
if not tgt_pos then self._puncher = nil return 0 end
local sneaking = target:get_player_control().sneak
if not sneaking then
local dist = vec_dist(pos, tgt_pos)
local score = (self.tracking_range - dist) / self.tracking_range
self._puncher = target
return score / 3, {self, 5, "feint"}
end
self._puncher = nil
return 0
end
},
{
utility = "animalia:walk_to_pos_and_interact",
get_score = function(self)
if math.random(14) < 2 then
return 0.7, {self, get_food_pos, eat_dropped_food, nil, 12}
end
return 0
end
},
{
utility = "animalia:follow_player",
get_score = function(self)
local lasso_tgt = self._lassod_to
local lasso = type(lasso_tgt) == "string" and minetest.get_player_by_name(lasso_tgt)
if lasso
and lasso:get_pos() then
return 0.6, {self, lasso, true}
end
return 0
end
},
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name) then
return 0.7, {self}
end
return 0
end
}
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.basic_attack,
animalia.mob_ai.opossum_feint,
animalia.mob_ai.basic_seek_food,
animalia.mob_ai.tamed_follow_owner,
animalia.mob_ai.basic_breed
},
on_eat_drop = function(self)

View File

@ -4,46 +4,8 @@
local abs = math.abs
local vec_dir = vector.direction
local vec_dist = vector.distance
local dir2yaw = minetest.dir_to_yaw
local function get_food_pos(self)
local _, pos = animalia.get_dropped_food(self)
return pos
end
local function eat_dropped_food(self)
local pos = self.object:get_pos()
if not pos then return end
local food = 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
local function get_home_pos(self)
local pos = self.object:get_pos()
if not pos then return end
@ -133,62 +95,11 @@ creatura.register_mob("animalia:owl", {
wander_action = creatura.action_move,
utility_stack = {
{
utility = "animalia:aerial_wander",
--step_delay = 0.25,
get_score = function(self)
if not self.is_landed
or self.in_liquid then
return 0.1, {self}
end
return 0
end
},
{
utility = "animalia:fly_to_roost",
get_score = function(self)
local pos = self.object:get_pos()
if not pos then return end
local player = creatura.get_nearby_player(self)
local plyr_pos = player and player:get_pos()
if plyr_pos then
local dist = vector.distance(pos, plyr_pos)
if dist < 3 then
return 0
end
end
local home = animalia.is_day and self.home_position
if home
and vec_dist(pos, home) < 8 then
return 0.2, {self}
end
return 0
end
},
{
utility = "animalia:fly_to_pos_and_interact",
get_score = function(self)
if math.random(1) < 2 then
return 0.3, {self, get_food_pos, eat_dropped_food, nil, 12}
end
return 0
end
},
{
utility = "animalia:raptor_hunt",
get_score = function(self)
if math.random(12) > 1
and (self:get_utility() or "") ~= "animalia:raptor_hunt" then
return 0
end
local target = self._target or creatura.get_nearby_object(self, {"animalia:rat", "animalia:song_bird"})
local tgt_pos = target and target:get_pos()
if tgt_pos then
return 0.4, {self, target}
end
return 0
end
},
animalia.mob_ai.fly_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.bat_seek_home,
animalia.mob_ai.fly_seek_food,
animalia.mob_ai.eagle_attack
},
activate_func = function(self)

View File

@ -78,46 +78,12 @@ creatura.register_mob("animalia:pig", {
-- Functions
utility_stack = {
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self, true}
end
},
{
utility = "animalia:walk_to_pos_and_interact",
get_score = function(self)
if math.random(6) < 2
or self:get_utility() == "animalia:walk_to_pos_and_interact" then
return 0.2, {self, animalia.find_crop, animalia.eat_crop}
end
return 0
end
},
{
utility = "animalia:swim_to_land",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.3, {self}
end
return 0
end
},
animalia.global_utils.basic_follow,
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name) then
return 0.5, {self}
end
return 0
end
},
animalia.global_utils.basic_flee
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.basic_seek_crop,
animalia.mob_ai.tamed_follow_owner,
animalia.mob_ai.basic_breed,
animalia.mob_ai.basic_flee
},
activate_func = function(self)

View File

@ -2,47 +2,6 @@
-- Mice --
----------
local vec_add, vec_sub = vector.add, vector.subtract
local function find_chest(self)
local pos = self.object:get_pos()
if not pos then return end
local nodes = minetest.find_nodes_with_meta(vec_sub(pos, 6), vec_add(pos, 6)) or {}
local pos2
for _, node_pos in ipairs(nodes) do
local meta = minetest.get_meta(node_pos)
if meta:get_string("owner") == "" then
local inv = minetest.get_inventory({type = "node", pos = node_pos})
if inv
and inv:get_list("main") then
pos2 = node_pos
end
end
end
return pos2
end
local function take_food_from_chest(self, pos)
local inv = minetest.get_inventory({type = "node", pos = pos})
if inv
and inv:get_list("main") then
for i, stack in ipairs(inv:get_list("main")) do
local item_name = stack:get_name()
local def = minetest.registered_items[item_name]
for group in pairs(def.groups) do
if group:match("food_") then
stack:take_item()
inv:set_stack("main", i, stack)
animalia.add_food_particle(self, item_name)
return true
end
end
end
end
end
creatura.register_mob("animalia:rat", {
-- Engine Props
visual_size = {x = 10, y = 10},
@ -85,53 +44,11 @@ creatura.register_mob("animalia:rat", {
-- Functions
utility_stack = {
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:swim_to_land",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.3, {self}
end
return 0
end
},
{
utility = "animalia:walk_to_pos_and_interact",
get_score = function(self)
-- Eat Crops
if math.random(6) < 2
or self:get_utility() == "animalia:walk_to_pos_and_interact" then
return 0.2, {self, animalia.find_crop, animalia.eat_crop, "eat"}
end
-- Steal From Chest
if math.random(12) < 2
or self:get_utility() == "animalia:walk_to_pos_and_interact" then
return 0.3, {self, find_chest, take_food_from_chest, "eat"}
end
return 0
end
},
{
utility = "animalia:flee_from_target",
get_score = function(self)
local target = creatura.get_nearby_object(self, {"animalia:fox", "animalia:cat"})
if not target then
target = creatura.get_nearby_player(self)
end
if target
and target:get_pos() then
return 0.6, {self, target}
end
return 0
end
}
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.basic_seek_crop,
animalia.mob_ai.rat_seek_chest,
animalia.mob_ai.basic_flee
},
activate_func = function(self)
@ -162,4 +79,4 @@ creatura.register_mob("animalia:rat", {
creatura.register_spawn_item("animalia:rat", {
col1 = "605a55",
col2 = "ff936f"
})
})

View File

@ -78,36 +78,11 @@ creatura.register_mob("animalia:reindeer", {
-- Functions
utility_stack = {
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:swim_to_land",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.3, {self}
end
return 0
end
},
animalia.global_utils.basic_follow,
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name) then
return 0.5, {self}
end
return 0
end
},
animalia.global_utils.basic_flee
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.tamed_follow_owner,
animalia.mob_ai.basic_breed,
animalia.mob_ai.basic_flee
},
activate_func = function(self)

View File

@ -97,36 +97,11 @@ creatura.register_mob("animalia:sheep", {
-- Functions
utility_stack = {
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:swim_to_land",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.3, {self}
end
return 0
end
},
animalia.global_utils.basic_follow,
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name) then
return 0.5, {self}
end
return 0
end
},
animalia.global_utils.basic_flee
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.tamed_follow_owner,
animalia.mob_ai.basic_breed,
animalia.mob_ai.basic_flee
},
activate_func = function(self)

View File

@ -54,6 +54,9 @@ creatura.register_mob("animalia:song_bird", {
{name = "animalia:feather", min = 1, max = 1, chance = 2}
},
-- Behavior Parameters
uses_boids = true,
-- Animalia Props
flee_puncher = true,
catch_with_net = true,
@ -63,41 +66,9 @@ creatura.register_mob("animalia:song_bird", {
-- Functions
utility_stack = {
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self, true}
end
},
{
utility = "animalia:aerial_wander",
get_score = function(self)
if self.is_landed then
local player = creatura.get_nearby_player(self)
if player then
self.is_landed = self:memorize("is_landed", false)
end
end
if not self.is_landed
or self.in_liquid then
return 0.2, {self}
end
return 0
end
},
{
utility = "animalia:fly_to_land",
get_score = function(self)
if self.is_landed
and not self.touching_ground
and not self.in_liquid
and creatura.sensor_floor(self, 3, true) > 2 then
return 0.3, {self}
end
return 0
end
}
animalia.mob_ai.basic_wander,
animalia.mob_ai.fly_landing_wander,
animalia.mob_ai.fly_seek_land
},
activate_func = function(self)

View File

@ -52,13 +52,7 @@ creatura.register_mob("animalia:tropical_fish", {
-- Functions
utility_stack = {
{
utility = "animalia:aquatic_wander_school",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
}
animalia.mob_ai.swim_wander
},
activate_func = function(self)

View File

@ -70,36 +70,11 @@ creatura.register_mob("animalia:turkey", {
-- Functions
utility_stack = {
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:swim_to_land",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.5, {self}
end
return 0
end
},
animalia.global_utils.basic_follow,
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name) then
return 0.4, {self}
end
return 0
end
},
animalia.global_utils.basic_flee
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.tamed_follow_owner,
animalia.mob_ai.basic_breed,
animalia.mob_ai.basic_flee
},
add_child = function(self)

View File

@ -2,20 +2,7 @@
-- Wolf --
----------
local function shared_owner(obj1, obj2)
if not obj1 or not obj2 then return false end
obj1 = creatura.is_valid(obj1)
obj2 = creatura.is_valid(obj2)
if obj1
and obj2
and obj1:get_luaentity()
and obj2:get_luaentity() then
obj1 = obj1:get_luaentity()
obj2 = obj2:get_luaentity()
return obj1.owner and obj2.owner and obj1.owner == obj2.owner
end
return false
end
local follow = {
"animalia:mutton_raw",
@ -94,74 +81,12 @@ creatura.register_mob("animalia:wolf", {
-- Functions
utility_stack = {
{
utility = "animalia:wander",
step_delay = 0.25,
get_score = function(self)
return 0.1, {self}
end
},
{
utility = "animalia:swim_to_land",
step_delay = 0.25,
get_score = function(self)
if self.in_liquid then
return 0.3, {self}
end
return 0
end
},
{
utility = "animalia:attack_target",
get_score = function(self)
local order = self.order or "wander"
if order ~= "wander" then return 0 end
local target = self._target or creatura.get_nearby_object(self, "animalia:sheep")
if target
and not shared_owner(self, target) then
return 0.4, {self, target}
end
return 0
end
},
{
utility = "animalia:stay",
step_delay = 0.25,
get_score = function(self)
local order = self.order or "wander"
if order == "sit" then
return 0.5, {self}
end
return 0
end
},
{
utility = "animalia:follow_player",
get_score = function(self)
local lasso_tgt = self._lassod_to
local lasso = type(lasso_tgt) == "string" and minetest.get_player_by_name(lasso_tgt)
local owner = self.owner and self.order == "follow" and minetest.get_player_by_name(self.owner)
local force = (lasso and lasso ~= false) or owner
local player = (force and (owner or lasso)) or creatura.get_nearby_player(self)
if player
and (self:follow_wielded_item(player)
or force) then
return 0.6, {self, player, force}
end
return 0
end
},
{
utility = "animalia:breed",
step_delay = 0.25,
get_score = function(self)
if self.breeding
and animalia.get_nearby_mate(self, self.name) then
return 0.7, {self}
end
return 0
end
}
animalia.mob_ai.basic_wander,
animalia.mob_ai.swim_seek_land,
animalia.mob_ai.tamed_stay,
animalia.mob_ai.tamed_follow_owner,
animalia.mob_ai.basic_attack,
animalia.mob_ai.basic_breed
},
activate_func = function(self)

View File

@ -3,3 +3,5 @@ depends = creatura
optional_depends = default, mcl_player, farming
description = Adds unique and consistantly designed Animals
title = Animalia
release = 22640
author = ElCeejo

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB