creatura/spawning.lua

574 lines
16 KiB
Lua
Raw Permalink Normal View History

2022-02-10 16:32:53 -08:00
--------------
-- Spawning --
--------------
creatura.registered_mob_spawns = {}
creatura.registered_on_spawns = {}
2022-02-10 16:32:53 -08:00
-- Math --
2022-05-26 15:58:44 -07:00
local abs = math.abs
2022-11-03 23:55:20 -07:00
local ceil = math.ceil
2022-05-26 15:58:44 -07:00
local pi = math.pi
2022-02-10 16:32:53 -08:00
local random = math.random
local min = math.min
2022-02-10 16:32:53 -08:00
2022-09-30 15:25:00 -07:00
local vec_add, vec_dist, vec_sub = vector.add, vector.distance, vector.subtract
-- Utility Functions --
2022-05-26 15:27:26 -07:00
2022-02-10 16:32:53 -08:00
local function format_name(str)
2022-05-26 15:27:26 -07:00
if str then
if str:match(":") then str = str:split(":")[2] end
return (string.gsub(" " .. str, "%W%l", string.upper):sub(2):gsub("_", " "))
end
end
local function table_contains(tbl, val)
for _, v in pairs(tbl) do
if v == val then
return true
end
2022-05-26 15:27:26 -07:00
end
return false
end
local function pos_meets_params(pos, def)
if not minetest.find_nodes_in_area(pos, pos, def.nodes) then return false end
if not minetest.find_node_near(pos, 1, def.neighbors) then return false end
return true
end
local function can_spawn(pos, width, height)
local pos2
local w_iter = width / ceil(width)
for y = 0, height, height / ceil(height) do
for z = -width, width, w_iter do
for x = -width, width, w_iter do
pos2 = {x = pos.x + x, y = pos.y + y, z = pos.z + z}
local def = creatura.get_node_def(pos2)
if def.walkable then return false end
2022-05-26 15:27:26 -07:00
end
end
end
return true
2022-02-10 16:32:53 -08:00
end
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
----------------
-- Spawn Item --
----------------
local creative = minetest.settings:get_bool("creative_mode")
2022-05-26 15:27:26 -07:00
function creatura.register_spawn_item(name, def)
2022-05-26 15:58:44 -07:00
local inventory_image
2022-05-26 15:27:26 -07:00
if not def.inventory_image
2022-12-05 14:46:42 -08:00
and ((def.col1 and def.col2)
or (def.hex_primary and def.hex_secondary)) then
local primary = def.col1 or def.hex_primary
local secondary = def.col2 or def.hex_secondary
local base = "(creatura_spawning_crystal_primary.png^[multiply:#" .. primary .. ")"
local spots = "(creatura_spawning_crystal_secondary.png^[multiply:#" .. secondary .. ")"
2022-05-26 15:27:26 -07:00
inventory_image = base .. "^" .. spots
end
local mod_name = name:split(":")[1]
local mob_name = name:split(":")[2]
def.description = def.description or "Spawn " .. format_name(name)
def.inventory_image = def.inventory_image or inventory_image
def.on_place = function(itemstack, player, pointed_thing)
-- If the player right-clicks something like a chest or item frame then
-- run the node's on_rightclick callback
local under = pointed_thing.under
local node = minetest.get_node(under)
local node_def = minetest.registered_nodes[node.name]
if node_def and node_def.on_rightclick and
not (player and player:is_player() and
player:get_player_control().sneak) then
return node_def.on_rightclick(under, node, player, itemstack,
pointed_thing) or itemstack
end
-- Otherwise spawn the mob
2022-06-05 03:37:12 -07:00
local pos = minetest.get_pointed_thing_position(pointed_thing, true)
if minetest.is_protected(pos, player and player:get_player_name() or "") then return end
local mobdef = minetest.registered_entities[name]
local spawn_offset = abs(mobdef.collisionbox[2])
pos.y = (pos.y - 0.49) + spawn_offset
if def.antispam then
local objs = minetest.get_objects_in_area(vec_sub(pos, 0.51), vec_add(pos, 0.51))
for _, obj in ipairs(objs) do
if obj
and obj:get_luaentity()
and obj:get_luaentity().name == name then
return
end
2022-05-26 15:27:26 -07:00
end
end
local object = minetest.add_entity(pos, name)
if object then
object:set_yaw(random(0, pi * 2))
object:get_luaentity().last_yaw = object:get_yaw()
2022-12-05 14:46:42 -08:00
if def.on_spawn then
def.on_spawn(object:get_luaentity(), player)
end
end
if not minetest.is_creative_enabled(player:get_player_name())
or def.consume_in_creative then
itemstack:take_item()
return itemstack
end
end
2023-01-26 14:35:09 -08:00
minetest.register_craftitem(def.itemstring or (mod_name .. ":spawn_" .. mob_name), def)
2022-02-10 16:32:53 -08:00
end
function creatura.register_on_spawn(name, func)
2022-05-26 15:27:26 -07:00
if not creatura.registered_on_spawns[name] then
creatura.registered_on_spawns[name] = {}
end
table.insert(creatura.registered_on_spawns[name], func)
end
--------------
-- Spawning --
--------------
2022-08-22 18:34:07 -07:00
--[[creatura.register_abm_spawn("mymod:mymob", {
chance = 3000,
interval = 30,
min_height = 0,
max_height = 128,
min_light = 1,
max_light = 15,
min_group = 1,
max_group = 4,
nodes = {"group:soil", "group:stone"},
neighbors = {"air"},
spawn_on_load = false,
spawn_in_nodes = false,
spawn_cap = 5
})]]
2022-10-17 14:25:33 -07:00
local protected_spawn = minetest.settings:get_bool("creatura_protected_spawn", true)
local abr = (tonumber(minetest.get_mapgen_setting("active_block_range")) or 4) * 16
2022-10-30 14:58:40 -07:00
local max_per_block = tonumber(minetest.settings:get("creatura_mapblock_limit")) or 12
local max_in_abr = tonumber(minetest.settings:get("creatura_abr_limit")) or 24
2022-12-04 17:06:13 -08:00
local min_abm_dist = min(abr / 2, tonumber(minetest.settings:get("creatura_min_abm_dist")) or 32)
local mobs_spawn = minetest.settings:get_bool("mobs_spawn") ~= false
local mapgen_mobs = {}
2022-08-22 18:34:07 -07:00
function creatura.register_abm_spawn(mob, def)
local chance = def.chance or 3000
local interval = def.interval or 30
local min_height = def.min_height or 0
local max_height = def.max_height or 128
2022-09-30 15:25:00 -07:00
local min_time = def.min_time or 0
local max_time = def.max_time or 24000
2022-08-22 18:34:07 -07:00
local min_light = def.min_light or 1
local max_light = def.max_light or 15
local min_group = def.min_group or 1
local max_group = def.max_group or 4
2022-10-17 14:25:33 -07:00
local block_protected = def.block_protected_spawn or false
2022-10-21 15:54:28 -07:00
local biomes = def.biomes or {}
2022-08-22 18:34:07 -07:00
local nodes = def.nodes or {"group:soil", "group:stone"}
local neighbors = def.neighbors or {"air"}
local spawn_on_load = def.spawn_on_load or false
local spawn_in_nodes = def.spawn_in_nodes or false
local spawn_cap = def.spawn_cap or 5
local function spawn_func(pos, aocw)
if not mobs_spawn then
return
end
2022-08-22 18:34:07 -07:00
if not spawn_in_nodes then
pos.y = pos.y + 1
2022-08-22 18:34:07 -07:00
end
2022-10-17 14:25:33 -07:00
if (not protected_spawn
or block_protected)
and minetest.is_protected(pos, "") then
return
end
2022-09-30 15:25:00 -07:00
local tod = (minetest.get_timeofday() or 0) * 24000
local bounds_in = tod >= min_time and tod <= max_time
local bounds_ex = tod >= max_time or tod <= min_time
if (max_time > min_time and not bounds_in)
or (min_time > max_time and not bounds_ex) then
return
end
2022-08-22 18:34:07 -07:00
local light = minetest.get_node_light(pos) or 7
if light > max_light
or light < min_light then
return
end
if aocw
and aocw >= max_per_block then
return
end
2022-10-21 15:54:28 -07:00
if biomes
and #biomes > 0 then
local biome_id = minetest.get_biome_data(pos).biome
local biome_name = minetest.get_biome_name(biome_id)
local is_spawn_biome = false
for _, biome in ipairs(biomes) do
if biome:match("^" .. biome_name) then
is_spawn_biome = true
break
end
end
if not is_spawn_biome then return end
end
local mob_count = 0
local plyr_found = false
local objects = minetest.get_objects_inside_radius(pos, abr)
for _, object in ipairs(objects) do
local ent = object:get_luaentity()
if ent
and ent.name == mob then
mob_count = mob_count + 1
2022-10-30 14:58:40 -07:00
if mob_count > spawn_cap
or mob_count > max_in_abr then
return
end
end
2022-09-30 15:25:00 -07:00
if object:is_player() then
plyr_found = true
if vec_dist(pos, object:get_pos()) < min_abm_dist then
return
end
end
end
if not plyr_found then
2022-08-22 18:34:07 -07:00
return
end
local mob_def = minetest.registered_entities[mob]
local mob_width = mob_def.collisionbox[4]
2022-09-28 14:41:56 -07:00
local mob_height = mob_def.collisionbox[5]
2022-08-22 18:34:07 -07:00
2022-09-28 14:41:56 -07:00
if not can_spawn(pos, mob_width, mob_height) then
2022-08-22 18:34:07 -07:00
return
end
local group_size = random(min_group or 1, max_group or 1)
local obj
2022-08-22 18:34:07 -07:00
if group_size > 1 then
local offset
local spawn_pos
for _ = 1, group_size do
offset = ceil(mob_width)
spawn_pos = creatura.get_ground_level({
x = pos.x + random(-offset, offset),
y = pos.y,
2022-11-03 23:55:20 -07:00
z = pos.z + random(-offset, offset)
}, 3)
2022-09-28 14:41:56 -07:00
if not can_spawn(spawn_pos, mob_width, mob_height) then
spawn_pos = pos
end
obj = minetest.add_entity(spawn_pos, mob)
do_on_spawn(spawn_pos, obj)
end
else
obj = minetest.add_entity(pos, mob)
do_on_spawn(pos, obj)
end
2022-08-22 18:34:07 -07:00
minetest.log("action",
"[Creatura] [ABM Spawning] Spawned " .. group_size .. " " .. mob .. " at " .. minetest.pos_to_string(pos))
end
minetest.register_abm({
label = mob .. " spawning",
nodenames = nodes,
neighbors = neighbors,
interval = interval,
chance = chance,
min_y = min_height,
max_y = max_height,
catch_up = false,
action = function(pos, _, _, aocw)
spawn_func(pos, aocw)
end
})
2022-08-22 18:34:07 -07:00
if spawn_on_load then
table.insert(mapgen_mobs, mob)
end
creatura.registered_mob_spawns[mob] = {
chance = def.chance or 3000,
interval = def.interval or 30,
min_height = def.min_height or 0,
max_height = def.max_height or 128,
min_time = def.min_time or 0,
max_time = def.max_time or 24000,
min_light = def.min_light or 1,
max_light = def.max_light or 15,
min_group = def.min_group or 1,
max_group = def.max_group or 4,
block_protected = def.block_protected_spawn or false,
biomes = def.biomes or {},
nodes = def.nodes or {"group:soil", "group:stone"},
neighbors = def.neighbors or {"air"},
spawn_on_load = def.spawn_on_load or false,
spawn_in_nodes = def.spawn_in_nodes or false,
spawn_cap = def.spawn_cap or 5
}
end
2024-01-03 18:24:04 -08:00
----------------
-- DEPRECATED --
----------------
-- Mapgen --
minetest.register_node("creatura:spawn_node", {
drawtype = "airlike",
groups = {not_in_creative_inventory = 1},
walkable = false
})
2024-01-03 18:24:04 -08:00
local mapgen_spawning = false
local mapgen_spawning_int = tonumber(minetest.settings:get("creatura_mapgen_spawn_interval")) or 64
if mapgen_spawning then
local chunk_delay = 0
local c_air = minetest.get_content_id("air")
local c_spawn = minetest.get_content_id("creatura:spawn_node")
minetest.register_on_generated(function(minp, maxp)
if chunk_delay > 0 then chunk_delay = chunk_delay - 1 end
local meta_queue = {}
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
local area = VoxelArea:new{MinEdge = emin, MaxEdge = emax}
local data = vm:get_data()
local min_x, max_x = minp.x, maxp.x
local min_y, max_y = minp.y, maxp.y
local min_z, max_z = minp.z, maxp.z
local def
local center
local current_biome
local spawn_biomes
local current_pos
for _, mob_name in ipairs(mapgen_mobs) do
local mob_spawned = false
def = creatura.registered_mob_spawns[mob_name]
center = {
x = min_x + (max_x - min_x) * 0.5,
y = min_y + (max_y - min_y) * 0.5,
z = min_z + (max_z - min_z) * 0.5
}
current_biome = minetest.get_biome_name(minetest.get_biome_data(center).biome)
spawn_biomes = def.biomes
if not mob_spawned
and (not spawn_biomes
or table_contains(spawn_biomes, current_biome)) then
for z = min_z + 8, max_z - 7, 8 do
if mob_spawned then break end
for x = min_x + 8, max_x - 7, 8 do
if mob_spawned then break end
for y = min_y, max_y do
local vi = area:index(x, y, z)
if data[vi] == c_air
or data[vi] == c_spawn then
break
end
-- Check if position is outside of vertical bounds
if y > def.max_height
or y < def.min_height then
break
end
current_pos = vector.new(x, y, z)
-- Check if position has required nodes
if not pos_meets_params(current_pos, def) then
break
end
if def.spawn_in_nodes then
-- Add Spawn Node to Map
data[vi] = c_spawn
local group_size = random(def.min_group or 1, def.max_group or 1)
table.insert(meta_queue, {pos = current_pos, mob = mob_name, cluster = group_size})
mob_spawned = true
break
elseif data[area:index(x, y + 1, z)] == c_air then
vi = area:index(x, y + 1, z)
current_pos = vector.new(x, y + 1, z)
-- Add Spawn Node to Map
data[vi] = c_spawn
local group_size = random(def.min_group or 1, def.max_group or 1)
table.insert(meta_queue, {pos = current_pos, mob = mob_name, cluster = group_size})
mob_spawned = true
break
end
end
end
end
2022-08-22 18:34:07 -07:00
end
end
if #meta_queue > 0 then
vm:set_data(data)
vm:write_to_map()
for _, unset_meta in ipairs(meta_queue) do
local pos = unset_meta.pos
local mob = unset_meta.mob
local cluster = unset_meta.cluster
local meta = minetest.get_meta(pos)
meta:set_string("mob", mob)
meta:set_int("cluster", cluster)
end
chunk_delay = mapgen_spawning_int
end
end)
local spawn_interval = tonumber(minetest.settings:get("creatura_spawn_interval")) or 10
minetest.register_abm({
label = "Creatura Spawning",
nodenames = {"creatura:spawn_node"},
interval = spawn_interval,
chance = 1,
action = function(pos)
local plyr_found = false
local objects = minetest.get_objects_inside_radius(pos, abr)
for _, object in ipairs(objects) do
if object:is_player() then
plyr_found = true
break
end
end
if not plyr_found then return end
local meta = minetest.get_meta(pos)
local name = meta:get_string("mob") or ""
if name == "" then minetest.remove_node(pos) return end
local amount = meta:get_int("cluster")
local obj
if amount > 0 then
for _ = 1, amount do
obj = minetest.add_entity(pos, name)
do_on_spawn(pos, obj)
end
minetest.log("action",
"[Creatura] Spawned " .. amount .. " " .. name .. " at " .. minetest.pos_to_string(pos))
else
obj = minetest.add_entity(pos, name)
do_on_spawn(pos, obj)
minetest.log("action",
"[Creatura] Spawned a " .. name .. " at " .. minetest.pos_to_string(pos))
end
minetest.remove_node(pos)
end,
})
end
function creatura.register_mob_spawn(name, def)
local spawn_def = {
chance = def.chance or 5,
min_height = def.min_height or 0,
max_height = def.max_height or 128,
min_time = def.min_time or 0,
max_time = def.max_time or 24000,
min_light = def.min_light or 6,
max_light = def.max_light or 15,
min_group = def.min_group or 1,
max_group = def.max_group or 4,
nodes = def.nodes or nil,
biomes = def.biomes or nil,
--spawn_cluster = def.spawn_cluster or false,
spawn_on_load = def.spawn_on_gen or false,
spawn_in_nodes = def.spawn_in_nodes or false,
spawn_cap = def.spawn_cap or 5,
--send_debug = def.send_debug or false
}
--creatura.registered_mob_spawns[name] = spawn_def
creatura.register_abm_spawn(name, spawn_def)
end
function creatura.register_spawn_egg(name, col1, col2, inventory_image)
if col1 and col2 then
local base = "(creatura_spawning_crystal_primary.png^[multiply:#" .. col1 .. ")"
local spots = "(creatura_spawning_crystal_secondary.png^[multiply:#" .. col2 .. ")"
inventory_image = base .. "^" .. spots
2022-08-22 18:34:07 -07:00
end
local mod_name = name:split(":")[1]
local mob_name = name:split(":")[2]
minetest.register_craftitem(mod_name .. ":spawn_" .. mob_name, {
description = "Spawn " .. format_name(name),
inventory_image = inventory_image,
stack_max = 99,
on_place = function(itemstack, _, pointed_thing)
local mobdef = minetest.registered_entities[name]
local spawn_offset = abs(mobdef.collisionbox[2])
local pos = minetest.get_pointed_thing_position(pointed_thing, true)
pos.y = (pos.y - 0.4) + spawn_offset
local object = minetest.add_entity(pos, name)
if object then
object:set_yaw(random(1, 6))
object:get_luaentity().last_yaw = object:get_yaw()
end
if not creative then
itemstack:take_item()
return itemstack
end
end
})
2022-10-14 20:07:51 -07:00
end