-------------- -- Spawning -- -------------- creatura.registered_mob_spawns = {} creatura.registered_on_spawns = {} -- Math -- local abs = math.abs local ceil = math.ceil local pi = math.pi local random = math.random local min = math.min local vec_add, vec_dist, vec_sub = vector.add, vector.distance, vector.subtract -- Utility Functions -- local function format_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 function table_contains(tbl, val) for _, v in pairs(tbl) do if v == val then return true end 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 end end end return true 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") function creatura.register_spawn_item(name, def) local inventory_image if not def.inventory_image 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 .. ")" 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 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 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() 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 minetest.register_craftitem(def.itemstring or (mod_name .. ":spawn_" .. mob_name), def) end function creatura.register_on_spawn(name, func) if not creatura.registered_on_spawns[name] then creatura.registered_on_spawns[name] = {} end table.insert(creatura.registered_on_spawns[name], func) end -------------- -- Spawning -- -------------- --[[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 })]] local protected_spawn = minetest.settings:get_bool("creatura_protected_spawn", true) local abr = (tonumber(minetest.get_mapgen_setting("active_block_range")) or 4) * 16 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 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 = {} 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 local min_time = def.min_time or 0 local max_time = def.max_time or 24000 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 local block_protected = def.block_protected_spawn or false local biomes = def.biomes or {} 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 if not spawn_in_nodes then pos.y = pos.y + 1 end if (not protected_spawn or block_protected) and minetest.is_protected(pos, "") then return end 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 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 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 if mob_count > spawn_cap or mob_count > max_in_abr then return end end 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 return end local mob_def = minetest.registered_entities[mob] local mob_width = mob_def.collisionbox[4] local mob_height = mob_def.collisionbox[5] if not can_spawn(pos, mob_width, mob_height) then return end local group_size = random(min_group or 1, max_group or 1) local obj 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, z = pos.z + random(-offset, offset) }, 3) 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 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 }) 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 -- Mapgen -- minetest.register_node("creatura:spawn_node", { drawtype = "airlike", groups = {not_in_creative_inventory = 1}, walkable = false }) local mapgen_spawning = minetest.settings:get_bool("creatura_mapgen_spawning", true) 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 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 ---------------- -- DEPRECATED -- ---------------- 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 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 }) end