2023-04-24 17:56:28 +03:00
|
|
|
--[[
|
2023-04-25 06:16:05 +03:00
|
|
|
Adds environmental spawners to the map. When enabled, the spawners will be added to newly generated Dungeons and Temples. They are dropping a real mob spawner by change (small chance).
|
2023-04-24 17:56:28 +03:00
|
|
|
Copyright (C) 2016 - 2023 SaKeL <juraj.vajda@gmail.com>
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
modify it pos the terms of the GNU Lesser General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
|
|
License along with this library; if not, write to juraj.vajda@gmail.com
|
|
|
|
--]]
|
|
|
|
|
2016-10-09 23:05:30 +03:00
|
|
|
-- main tables
|
2023-04-24 17:56:28 +03:00
|
|
|
spawners_env = {
|
2023-04-25 06:16:05 +03:00
|
|
|
registered_spawners_names = {}
|
2023-04-24 17:56:28 +03:00
|
|
|
}
|
|
|
|
|
2023-04-25 06:16:05 +03:00
|
|
|
function spawners_env.register_spawner(name, def)
|
|
|
|
local mod_prefix = name:split(':')[1]
|
|
|
|
local mob_name = name:split(':')[2]
|
|
|
|
|
|
|
|
-- Entity inside the spawner
|
2023-04-25 07:34:49 +03:00
|
|
|
local ent_name = 'spawners_env:dummy_' .. mod_prefix .. '_' .. mob_name
|
2023-04-25 06:16:05 +03:00
|
|
|
local ent_def = {
|
|
|
|
hp_max = 1,
|
|
|
|
physical = true,
|
|
|
|
collisionbox = { 0, 0, 0, 0, 0, 0 },
|
|
|
|
visual = def.dummy_visual or 'mesh',
|
|
|
|
visual_size = def.dummy_size,
|
|
|
|
mesh = def.dummy_mesh,
|
|
|
|
textures = def.dummy_texture,
|
|
|
|
makes_footstep_sound = false,
|
|
|
|
timer = 0,
|
|
|
|
automatic_rotate = math.pi * -3,
|
2023-04-25 07:34:49 +03:00
|
|
|
on_activate = function(self)
|
|
|
|
self.object:set_velocity({ x = 0, y = 0, z = 0 })
|
|
|
|
self.object:set_acceleration({ x = 0, y = 0, z = 0 })
|
|
|
|
self.object:set_armor_groups({ immortal = 1 })
|
|
|
|
end,
|
|
|
|
on_step = function(self, dtime)
|
|
|
|
-- remove dummy after dig the spawner
|
|
|
|
self.timer = self.timer + dtime
|
|
|
|
|
|
|
|
if self.timer > 2 then
|
|
|
|
local n = minetest.get_node_or_nil(self.object:get_pos())
|
|
|
|
if n and n.name ~= 'spawners_env:' .. mod_prefix .. '_' .. mob_name .. '_spawner_active'
|
|
|
|
then
|
|
|
|
self.object:remove()
|
|
|
|
end
|
2023-04-25 06:16:05 +03:00
|
|
|
end
|
|
|
|
end
|
2023-04-25 07:34:49 +03:00
|
|
|
}
|
2023-04-25 06:16:05 +03:00
|
|
|
|
|
|
|
minetest.register_entity('spawners_env:dummy_' .. mod_prefix .. '_' .. mob_name, ent_def)
|
|
|
|
|
|
|
|
-- Default spawner (inactive)
|
|
|
|
local node_def = {}
|
|
|
|
local node_name = 'spawners_env:' .. mod_prefix .. '_' .. mob_name .. '_spawner'
|
|
|
|
node_def.description = mod_prefix .. '_' .. mob_name .. ' spawner env'
|
|
|
|
node_def.paramtype = 'light'
|
|
|
|
node_def.paramtype2 = 'glasslikeliquidlevel'
|
|
|
|
node_def.drawtype = 'glasslike_framed_optional'
|
|
|
|
node_def.walkable = true
|
|
|
|
node_def.sounds = default.node_sound_metal_defaults()
|
|
|
|
node_def.sunlight_propagates = true
|
|
|
|
node_def.tiles = { 'spawners_env_spawner_16.png' }
|
|
|
|
node_def.is_ground_content = true
|
|
|
|
node_def.groups = {
|
|
|
|
-- MTG
|
|
|
|
cracky = 1,
|
|
|
|
level = 2
|
|
|
|
}
|
|
|
|
node_def.stack_max = 1
|
|
|
|
node_def.drop = ''
|
|
|
|
node_def.on_construct = function(pos)
|
|
|
|
spawners_env.check_for_spawning_timer(pos, mob_name, def.night_only, mod_prefix, def.sound_custom, def.boss)
|
|
|
|
end
|
|
|
|
|
|
|
|
local drop_item_name = 'spawners_mobs:' .. mod_prefix .. '_' .. mob_name .. '_spawner'
|
|
|
|
|
|
|
|
if minetest.get_modpath('spawners_mobs') and minetest.registered_nodes[drop_item_name] then
|
|
|
|
node_def.drop = {
|
|
|
|
max_items = 1,
|
|
|
|
items = {
|
|
|
|
{ items = { 'spawners_mobs:' .. mod_prefix .. '_' .. mob_name .. '_spawner' }, rarity = 5 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
minetest.register_node(node_name, node_def)
|
|
|
|
|
|
|
|
table.insert(spawners_env.registered_spawners_names, node_name)
|
|
|
|
|
|
|
|
-- Waiting spawner
|
|
|
|
local node_def_waiting = table.copy(node_def)
|
|
|
|
node_name = 'spawners_env:' .. mod_prefix .. '_' .. mob_name .. '_spawner_waiting'
|
|
|
|
node_def_waiting.description = mod_prefix .. '_' .. mob_name .. ' spawner waiting env'
|
|
|
|
node_def_waiting.light_source = 2
|
|
|
|
node_def_waiting.tiles = {
|
|
|
|
{
|
|
|
|
name = 'spawners_env_spawner_waiting_animated_16.png',
|
|
|
|
animation = {
|
|
|
|
type = 'vertical_frames',
|
|
|
|
aspect_w = 16,
|
|
|
|
aspect_h = 16,
|
|
|
|
length = 2.0
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
node_def_waiting.groups = {
|
|
|
|
-- MTG
|
|
|
|
cracky = 1,
|
|
|
|
level = 2,
|
|
|
|
not_in_creative_inventory = 1
|
|
|
|
}
|
|
|
|
node_def_waiting.on_timer = function(pos, elapsed)
|
|
|
|
spawners_env.check_for_spawning_timer(pos, mob_name, def.night_only, mod_prefix, def.sound_custom, def.boss)
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
node_def_waiting.on_construct = nil
|
|
|
|
|
|
|
|
minetest.register_node(node_name, node_def_waiting)
|
|
|
|
|
|
|
|
-- Active spawner
|
|
|
|
local node_def_active = table.copy(node_def)
|
|
|
|
node_name = 'spawners_env:' .. mod_prefix .. '_' .. mob_name .. '_spawner_active'
|
|
|
|
node_def_active.description = mod_prefix .. '_' .. mob_name .. ' spawner active env'
|
|
|
|
node_def_active.light_source = 4
|
|
|
|
node_def_active.damage_per_second = 4
|
|
|
|
node_def_active.tiles = {
|
|
|
|
{
|
|
|
|
name = 'spawners_env_spawner_animated_16.png',
|
|
|
|
animation = {
|
|
|
|
type = 'vertical_frames',
|
|
|
|
aspect_w = 16,
|
|
|
|
aspect_h = 16,
|
|
|
|
length = 2.0
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
node_def_active.groups = {
|
|
|
|
-- MTG
|
|
|
|
cracky = 1,
|
|
|
|
level = 2,
|
|
|
|
igniter = 1,
|
|
|
|
not_in_creative_inventory = 1
|
|
|
|
}
|
|
|
|
node_def_active.on_timer = function(pos, elapsed)
|
|
|
|
spawners_env.check_for_spawning_timer(pos, mob_name, def.night_only, mod_prefix, def.sound_custom, def.boss)
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
node_def_active.on_construct = function(pos)
|
|
|
|
pos.y = pos.y + def.dummy_offset
|
|
|
|
minetest.add_entity(pos, ent_name)
|
|
|
|
end
|
|
|
|
|
|
|
|
minetest.register_node(node_name, node_def_active)
|
|
|
|
|
|
|
|
--
|
2023-04-25 07:34:49 +03:00
|
|
|
-- LBM
|
2023-04-25 06:16:05 +03:00
|
|
|
--
|
|
|
|
minetest.register_lbm({
|
|
|
|
name = 'spawners_env:check_for_spawning_timer_' .. mob_name,
|
|
|
|
nodenames = {
|
|
|
|
'spawners_env:' .. mod_prefix .. '_' .. mob_name .. '_spawner',
|
|
|
|
'spawners_env:' .. mod_prefix .. '_' .. mob_name .. '_spawner_active',
|
|
|
|
'spawners_env:' .. mod_prefix .. '_' .. mob_name .. '_spawner_waiting'
|
|
|
|
},
|
|
|
|
action = function(pos)
|
|
|
|
spawners_env.check_for_spawning_timer(pos, mob_name, def.night_only, mod_prefix, def.sound_custom, def.boss)
|
|
|
|
end
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
--
|
|
|
|
-- Check for spawning
|
|
|
|
--
|
|
|
|
function spawners_env.check_for_spawning_timer(pos, mob_name, night_only, mod_prefix, sound_custom, boss)
|
|
|
|
local random_pos = spawners_env.check_node_status(pos, mob_name, night_only, boss)
|
|
|
|
local node = minetest.get_node_or_nil(pos)
|
|
|
|
|
|
|
|
-- minetest.log('action', '[Mod][Spawners] checking for: ' .. mob_name .. ' at ' .. minetest.pos_to_string(pos))
|
|
|
|
|
|
|
|
if random_pos and node then
|
|
|
|
-- print('try to spawn another mob at: ' .. minetest.pos_to_string(random_pos))
|
|
|
|
local mobs_counter_table = {}
|
|
|
|
local mobs_check_radius = 10
|
|
|
|
local mobs_max = 3
|
|
|
|
mobs_counter_table[mob_name] = 0
|
|
|
|
|
|
|
|
if boss then
|
|
|
|
mobs_max = 1
|
|
|
|
mobs_check_radius = 35
|
|
|
|
end
|
|
|
|
|
|
|
|
-- collect all spawned mobs around area
|
|
|
|
for _, obj in ipairs(minetest.get_objects_inside_radius(pos, mobs_check_radius)) do
|
|
|
|
if obj:get_luaentity() then
|
|
|
|
-- get entity name
|
|
|
|
local name_split = string.split(obj:get_luaentity().name, ':')
|
|
|
|
|
|
|
|
if name_split[2] == mob_name then
|
|
|
|
mobs_counter_table[mob_name] = mobs_counter_table[mob_name] + 1
|
2023-04-24 17:56:28 +03:00
|
|
|
end
|
2023-04-25 06:16:05 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- print(mob_name .. ' : ' .. mobs_counter_table[mob_name])
|
|
|
|
|
|
|
|
-- enough place to spawn more mobs
|
|
|
|
if mobs_counter_table[mob_name] < mobs_max then
|
|
|
|
-- make sure the right node status is shown
|
|
|
|
if node.name ~= 'spawners_env:' .. mod_prefix .. '_' .. mob_name .. '_spawner_active' then
|
|
|
|
minetest.set_node(pos, { name = 'spawners_env:' .. mod_prefix .. '_' .. mob_name .. '_spawner_active' })
|
|
|
|
end
|
|
|
|
|
|
|
|
if boss then
|
|
|
|
minetest.chat_send_all(minetest.colorize('#FF5722', 'Boss ' .. mob_name .. ' has spawned to this World!'))
|
|
|
|
end
|
2023-04-24 17:56:28 +03:00
|
|
|
|
2023-04-25 06:16:05 +03:00
|
|
|
spawners_env.start_spawning(random_pos, 1, 'spawners_env:' .. mob_name, mod_prefix, sound_custom)
|
|
|
|
else
|
|
|
|
-- print('too many mobs: waiting')
|
|
|
|
-- waiting status
|
|
|
|
if node.name ~= 'spawners_env:' .. mod_prefix .. '_' .. mob_name .. '_spawner_waiting' then
|
|
|
|
minetest.set_node(pos, { name = 'spawners_env:' .. mod_prefix .. '_' .. mob_name .. '_spawner_waiting' })
|
2023-04-24 17:56:28 +03:00
|
|
|
end
|
|
|
|
end
|
2023-04-25 06:16:05 +03:00
|
|
|
|
|
|
|
elseif node then
|
|
|
|
-- print('no random_pos found: waiting')
|
|
|
|
-- waiting status
|
|
|
|
if node.name ~= 'spawners_env:' .. mod_prefix .. '_' .. mob_name .. '_spawner_waiting' then
|
|
|
|
minetest.set_node(pos, { name = 'spawners_env:' .. mod_prefix .. '_' .. mob_name .. '_spawner_waiting' })
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- 6 hours = 21600 seconds
|
|
|
|
-- 4 hours = 14400 seconds
|
|
|
|
-- 1 hour = 3600 seconds
|
|
|
|
if boss then
|
|
|
|
minetest.get_node_timer(pos):start(3600)
|
|
|
|
else
|
|
|
|
minetest.get_node_timer(pos):start(math.random(5, 15))
|
2023-04-24 17:56:28 +03:00
|
|
|
end
|
2016-10-09 23:05:30 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
-- start spawning mobs
|
|
|
|
function spawners_env.start_spawning(pos, how_many, mob_name, mod_prefix, sound_custom)
|
2023-04-24 17:56:28 +03:00
|
|
|
|
|
|
|
if not (pos or mob_name) then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- remove 'spawners_env:' from the string
|
|
|
|
local _mob_name = string.sub(mob_name, 14)
|
|
|
|
local sound_name
|
2023-04-25 06:16:05 +03:00
|
|
|
|
2023-04-24 17:56:28 +03:00
|
|
|
-- use custom sounds
|
2023-04-25 06:16:05 +03:00
|
|
|
if sound_custom and sound_custom ~= '' then
|
2023-04-24 17:56:28 +03:00
|
|
|
sound_name = sound_custom
|
|
|
|
else
|
|
|
|
sound_name = mod_prefix .. '_' .. _mob_name
|
|
|
|
end
|
|
|
|
|
2023-04-25 06:16:05 +03:00
|
|
|
if not how_many then
|
2023-04-24 17:56:28 +03:00
|
|
|
how_many = math.random(1, 2)
|
|
|
|
end
|
|
|
|
|
|
|
|
for i = 1, how_many do
|
|
|
|
pos.y = pos.y + 1
|
|
|
|
local obj = minetest.add_entity(pos, mod_prefix .. ':' .. _mob_name)
|
|
|
|
|
|
|
|
if obj then
|
|
|
|
if sound_name then
|
|
|
|
minetest.sound_play(sound_name, {
|
|
|
|
pos = pos,
|
2023-04-25 06:16:05 +03:00
|
|
|
max_hear_distance = 16,
|
2023-04-24 17:56:28 +03:00
|
|
|
gain = 5,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-10-09 23:05:30 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
function spawners_env.check_around_radius(pos)
|
2023-04-24 17:56:28 +03:00
|
|
|
local player_near = false
|
|
|
|
local radius = 21
|
2016-10-09 23:05:30 +03:00
|
|
|
|
2023-04-25 06:16:05 +03:00
|
|
|
for _, obj in ipairs(minetest.get_objects_inside_radius(pos, radius)) do
|
2023-04-24 17:56:28 +03:00
|
|
|
if obj:is_player() then
|
|
|
|
player_near = true
|
2023-04-25 06:16:05 +03:00
|
|
|
break
|
2023-04-24 17:56:28 +03:00
|
|
|
end
|
|
|
|
end
|
2016-10-09 23:05:30 +03:00
|
|
|
|
2023-04-24 17:56:28 +03:00
|
|
|
return player_near
|
2016-10-09 23:05:30 +03:00
|
|
|
end
|
|
|
|
|
2016-12-16 02:09:41 +03:00
|
|
|
function spawners_env.check_node_status(pos, mob, night_only, boss)
|
2023-04-24 17:56:28 +03:00
|
|
|
local player_near = spawners_env.check_around_radius(pos)
|
|
|
|
|
|
|
|
if player_near or boss then
|
|
|
|
local random_pos
|
|
|
|
local min_node_light = 10
|
|
|
|
local tod = minetest.get_timeofday() * 24000
|
|
|
|
local node_light = minetest.get_node_light(pos)
|
|
|
|
|
|
|
|
if not node_light then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
local spawn_positions = {}
|
|
|
|
local right = minetest.get_node({ x = pos.x + 1, y = pos.y, z = pos.z })
|
|
|
|
local front = minetest.get_node({ x = pos.x, y = pos.y, z = pos.z + 1 })
|
|
|
|
local left = minetest.get_node({ x = pos.x - 1, y = pos.y, z = pos.z })
|
|
|
|
local back = minetest.get_node({ x = pos.x, y = pos.y, z = pos.z - 1 })
|
|
|
|
local top = minetest.get_node({ x = pos.x, y = pos.y + 1, z = pos.z })
|
|
|
|
local bottom = minetest.get_node({ x = pos.x, y = pos.y - 1, z = pos.z })
|
|
|
|
|
|
|
|
-- make sure that at least one side of the spawner is open
|
|
|
|
if right.name == 'air' then
|
|
|
|
table.insert(spawn_positions, { x = pos.x + 1.5, y = pos.y, z = pos.z })
|
|
|
|
end
|
|
|
|
if front.name == 'air' then
|
|
|
|
table.insert(spawn_positions, { x = pos.x, y = pos.y, z = pos.z + 1.5 })
|
|
|
|
end
|
|
|
|
if left.name == 'air' then
|
|
|
|
table.insert(spawn_positions, { x = pos.x - 1.5, y = pos.y, z = pos.z })
|
|
|
|
end
|
|
|
|
if back.name == 'air' then
|
|
|
|
table.insert(spawn_positions, { x = pos.x, y = pos.y, z = pos.z - 1.5 })
|
|
|
|
end
|
|
|
|
if top.name == 'air' then
|
|
|
|
table.insert(spawn_positions, { x = pos.x, y = pos.y + 1.5, z = pos.z })
|
|
|
|
end
|
|
|
|
if bottom.name == 'air' then
|
|
|
|
table.insert(spawn_positions, { x = pos.x, y = pos.y - 1.5, z = pos.z })
|
|
|
|
end
|
|
|
|
|
|
|
|
-- spawner is closed from all sides
|
|
|
|
if #spawn_positions < 1 then
|
|
|
|
return false
|
|
|
|
|
|
|
|
else
|
|
|
|
-- find random position in all posible places
|
|
|
|
local possible_spawn_pos = {}
|
|
|
|
local pick_random_key
|
|
|
|
|
|
|
|
-- get a position value from the picked/random key
|
|
|
|
for k, v in pairs(spawn_positions) do
|
|
|
|
local node_above = minetest.get_node({ x = v.x, y = v.y + 1, z = v.z }).name
|
|
|
|
local node_below = minetest.get_node({ x = v.x, y = v.y - 1, z = v.z }).name
|
|
|
|
|
|
|
|
-- make super sure there is enough place to spawn mob and collect all possible spawn points
|
|
|
|
if node_above == 'air' or node_below == 'air' then
|
|
|
|
table.insert(possible_spawn_pos, v)
|
|
|
|
-- print('possible pos: ' .. minetest.pos_to_string(v))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- no possible spawn points found - not enough place around the spawner
|
|
|
|
if #possible_spawn_pos < 1 then
|
|
|
|
return false
|
|
|
|
|
|
|
|
elseif #possible_spawn_pos == 1 then
|
|
|
|
-- only one possible position ?
|
|
|
|
pick_random_key = #possible_spawn_pos
|
|
|
|
|
|
|
|
else
|
|
|
|
-- pick random from the possible open sides
|
|
|
|
pick_random_key = math.random(1, #possible_spawn_pos)
|
|
|
|
end
|
|
|
|
|
|
|
|
random_pos = possible_spawn_pos[pick_random_key]
|
|
|
|
-- print(minetest.pos_to_string(random_pos))
|
|
|
|
end
|
|
|
|
|
|
|
|
if night_only ~= 'disable' then
|
|
|
|
-- spawn only at day
|
|
|
|
if not night_only and node_light < min_node_light then
|
|
|
|
return false, true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- spawn only at night
|
|
|
|
if night_only then
|
|
|
|
if not (19359 > tod and tod > 5200) or node_light < min_node_light then
|
|
|
|
return random_pos
|
|
|
|
else
|
|
|
|
return false, true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- random_pos, waiting
|
|
|
|
return random_pos, false
|
|
|
|
else
|
|
|
|
-- random_pos, waiting
|
|
|
|
return false, true
|
|
|
|
end
|
2016-10-09 23:05:30 +03:00
|
|
|
end
|