1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2025-09-16 09:06:21 +02:00

desync fix and more

This commit is contained in:
MewMew
2019-10-11 21:52:32 +02:00
parent ebd11a96f2
commit 57611ad036
9 changed files with 134 additions and 105 deletions

View File

@@ -56,6 +56,7 @@ require "modules.floaty_chat"
--require "modules.more_attacks" --require "modules.more_attacks"
--require "modules.evolution_extended" --require "modules.evolution_extended"
--require "modules.no_blueprint_library" --require "modules.no_blueprint_library"
--require "modules.explosives"
----------------------------- -----------------------------
---- enable maps here ---- ---- enable maps here ----

View File

@@ -29,7 +29,7 @@ market.ammo = {
} }
market.caspules = { market.caspules = {
["grenade"] = {value = 12, rarity = 1}, ["grenade"] = {value = 16, rarity = 2},
["cluster-grenade"] = {value = 32, rarity = 5}, ["cluster-grenade"] = {value = 32, rarity = 5},
["poison-capsule"] = {value = 48, rarity = 6}, ["poison-capsule"] = {value = 48, rarity = 6},
["slowdown-capsule"] = {value = 8, rarity = 1}, ["slowdown-capsule"] = {value = 8, rarity = 1},

View File

@@ -32,6 +32,8 @@ local function fish_tag()
end end
local function accelerate() local function accelerate()
if not global.locomotive then return end
if not global.locomotive.valid then return end
local driver = global.locomotive.get_driver() local driver = global.locomotive.get_driver()
if driver then return end if driver then return end
global.locomotive_driver = global.locomotive.surface.create_entity({name = "character", position = global.locomotive.position, force = "player"}) global.locomotive_driver = global.locomotive.surface.create_entity({name = "character", position = global.locomotive.position, force = "player"})
@@ -40,6 +42,8 @@ local function accelerate()
end end
local function remove_acceleration() local function remove_acceleration()
if not global.locomotive then return end
if not global.locomotive.valid then return end
if global.locomotive_driver then global.locomotive_driver.destroy() end if global.locomotive_driver then global.locomotive_driver.destroy() end
end end
--[[ --[[
@@ -98,14 +102,10 @@ local function force_nearby_units_to_attack()
end end
local function tick() local function tick()
if not global.locomotive then return end
if not global.locomotive.valid then return end
--constant_speed()
if game.tick % 30 == 0 then if game.tick % 30 == 0 then
fish_tag() fish_tag()
accelerate() accelerate()
if game.tick % 1800 == 0 then if game.tick % 1800 == 0 then
--force_nearby_units_to_attack()
set_player_spawn_and_refill_fish() set_player_spawn_and_refill_fish()
if global.game_reset_tick then if global.game_reset_tick then
if global.game_reset_tick < game.tick then if global.game_reset_tick < game.tick then

View File

@@ -1,5 +1,6 @@
-- Mountain digger fortress, protect the locomotive! -- by MewMew -- Mountain digger fortress, protect the cargo wagon! -- by MewMew
--require "modules.flashlight_toggle_button"
require "modules.biter_noms_you" require "modules.biter_noms_you"
require "modules.biter_evasion_hp_increaser" require "modules.biter_evasion_hp_increaser"
require "modules.wave_defense.main" require "modules.wave_defense.main"
@@ -16,29 +17,10 @@ require "modules.spawners_contain_biters"
require "modules.map_info" require "modules.map_info"
require "modules.rpg" require "modules.rpg"
global.map_info = {}
global.map_info.main_caption = "Mountain Fortress"
global.map_info.sub_caption = " ..diggy diggy choo choo.."
global.map_info.text = [[
The biters have catched the scent of fish in the cargo wagon.
Guide the choo into the mountain and protect it as long as possible!
This however will not be an easy task,
since their strength and resistance increases constantly over time.
Delve deep for greater treasures, but also face increased dangers.
Mining productivity research, will overhaul your mining equipment,
reinforcing your pickaxe as well as increasing the size of your backpack.
As you dig, you will encounter black bedrock that is just too solid for your pickaxe.
Some explosives could even break through the impassable dark rock.
All they need is a container and a well aimed shot.
]]
require "maps.mountain_fortress_v2.market" require "maps.mountain_fortress_v2.market"
require "maps.mountain_fortress_v2.treasure" require "maps.mountain_fortress_v2.treasure"
require "maps.mountain_fortress_v2.terrain" require "maps.mountain_fortress_v2.terrain"
require "maps.mountain_fortress_v2.locomotive" require "maps.mountain_fortress_v2.locomotive"
--require "maps.mountain_fortress_v2.explosives"
require "maps.mountain_fortress_v2.flamethrower_nerf" require "maps.mountain_fortress_v2.flamethrower_nerf"
local starting_items = {['pistol'] = 1, ['firearm-magazine'] = 16, ['rail'] = 16, ['wood'] = 16} local starting_items = {['pistol'] = 1, ['firearm-magazine'] = 16, ['rail'] = 16, ['wood'] = 16}
@@ -68,19 +50,21 @@ end
function reset_map() function reset_map()
global.chunk_queue = {} global.chunk_queue = {}
if not global.active_surface then if not global.active_surface_index then
global.active_surface = game.create_surface("mountain_fortress", get_gen_settings()) global.active_surface_index = game.create_surface("mountain_fortress", get_gen_settings()).index
else else
game.forces.player.set_spawn_position({-2, 16}, global.active_surface) game.forces.player.set_spawn_position({-2, 16}, game.surfaces[global.active_surface_index])
global.active_surface = soft_reset_map(global.active_surface, get_gen_settings(), starting_items) global.active_surface_index = soft_reset_map(game.surfaces[global.active_surface_index], get_gen_settings(), starting_items).index
end end
global.active_surface.request_to_generate_chunks({0,0}, 2) local surface = game.surfaces[global.active_surface_index]
global.active_surface.force_generate_chunk_requests()
surface.request_to_generate_chunks({0,0}, 2)
surface.force_generate_chunk_requests()
for x = -768 + 32, 768 - 32, 32 do for x = -768 + 32, 768 - 32, 32 do
global.active_surface.request_to_generate_chunks({x, 96}, 1) surface.request_to_generate_chunks({x, 96}, 1)
global.active_surface.force_generate_chunk_requests() surface.force_generate_chunk_requests()
end end
game.map_settings.enemy_evolution.destroy_factor = 0 game.map_settings.enemy_evolution.destroy_factor = 0
@@ -94,12 +78,12 @@ function reset_map()
game.map_settings.pollution.enabled = false game.map_settings.pollution.enabled = false
game.forces.player.technologies["railway"].researched = true game.forces.player.technologies["railway"].researched = true
game.forces.player.set_spawn_position({-2, 16}, global.active_surface) game.forces.player.set_spawn_position({-2, 16}, surface)
locomotive_spawn(global.active_surface, {x = 0, y = 16}) locomotive_spawn(surface, {x = 0, y = 16})
reset_wave_defense() reset_wave_defense()
global.wave_defense.surface = global.active_surface global.wave_defense.surface = surface
global.wave_defense.target = global.locomotive_cargo global.wave_defense.target = global.locomotive_cargo
global.wave_defense.game_lost = false global.wave_defense.game_lost = false
@@ -170,7 +154,7 @@ local function on_entity_died(event)
if event.entity == global.locomotive_cargo then if event.entity == global.locomotive_cargo then
game.print("The cargo was destroyed!") game.print("The cargo was destroyed!")
global.wave_defense.game_lost = true global.wave_defense.game_lost = true
global.game_reset_tick = game.tick + 1800 global.game_reset_tick = game.tick + 900
for _, player in pairs(game.connected_players) do for _, player in pairs(game.connected_players) do
player.play_sound{path="utility/game_lost", volume_modifier=0.75} player.play_sound{path="utility/game_lost", volume_modifier=0.75}
end end
@@ -209,16 +193,17 @@ end
local function on_player_joined_game(event) local function on_player_joined_game(event)
local player = game.players[event.player_index] local player = game.players[event.player_index]
local surface = game.surfaces[global.active_surface_index]
if player.online_time == 0 then if player.online_time == 0 then
player.teleport(global.active_surface.find_non_colliding_position("character", game.forces.player.get_spawn_position(global.active_surface), 3, 0.5), global.active_surface) player.teleport(surface.find_non_colliding_position("character", game.forces.player.get_spawn_position(surface), 3, 0.5), surface)
for item, amount in pairs(starting_items) do for item, amount in pairs(starting_items) do
player.insert({name = item, count = amount}) player.insert({name = item, count = amount})
end end
end end
if player.surface.index ~= global.active_surface.index then if player.surface.index ~= global.active_surface_index then
player.teleport(global.active_surface.find_non_colliding_position("character", game.forces.player.get_spawn_position(global.active_surface), 3, 0.5), global.active_surface) player.teleport(surface.find_non_colliding_position("character", game.forces.player.get_spawn_position(surface), 3, 0.5), surface)
end end
end end
@@ -227,6 +212,25 @@ local function on_init(surface)
global.rocks_yield_ore_base_amount = 50 global.rocks_yield_ore_base_amount = 50
global.rocks_yield_ore_distance_modifier = 0.04 global.rocks_yield_ore_distance_modifier = 0.04
global.map_info = {}
global.map_info.main_caption = "Mountain Fortress"
global.map_info.sub_caption = " ..diggy diggy choo choo.."
global.map_info.text = table.concat({
"The biters have catched the scent of fish in the cargo wagon.\n",
"Guide the choo into the mountain and protect it as long as possible!\n",
"This however will not be an easy task,\n",
"since their strength and resistance increases constantly over time.\n",
"\n",
"Delve deep for greater treasures, but also face increased dangers.\n",
"Mining productivity research, will overhaul your mining equipment,\n",
"reinforcing your pickaxe as well as increasing the size of your backpack.\n",
"\n",
"As you dig, you will encounter impassable dark chasms.\n",
"Some explosives may cause parts of the ceiling to crumble,\n",
"filling the void and creating new ways.\n",
"All they need is a container and a well aimed shot.\n",
})
global.explosion_cells_destructible_tiles = { global.explosion_cells_destructible_tiles = {
["out-of-map"] = 2500, ["out-of-map"] = 2500,
} }

View File

@@ -31,6 +31,18 @@ function get_cave_density_modifer(y)
return m return m
end end
local function get_replacement_tile(surface, position)
for i = 1, 128, 1 do
local vectors = {{0, i}, {0, i * -1}, {i, 0}, {i * -1, 0}}
table.shuffle_table(vectors)
for k, v in pairs(vectors) do
local tile = surface.get_tile(position.x + v[1], position.y + v[2])
if not tile.collides_with("resource-layer") then return tile.name end
end
end
return "grass-1"
end
local function process_rock_chunk_position(p, seed, tiles, entities, markets, treasure) local function process_rock_chunk_position(p, seed, tiles, entities, markets, treasure)
local m = get_cave_density_modifer(p.y) local m = get_cave_density_modifer(p.y)
@@ -244,6 +256,17 @@ local function biter_chunk(surface, left_top)
for _, e in pairs(surface.find_entities_filtered({area = {{left_top.x, left_top.y},{left_top.x + 32, left_top.y + 32}}, type = "cliff"})) do e.destroy() end for _, e in pairs(surface.find_entities_filtered({area = {{left_top.x, left_top.y},{left_top.x + 32, left_top.y + 32}}, type = "cliff"})) do e.destroy() end
end end
local function replace_water(surface, left_top)
for x = 0, 31, 1 do
for y = 0, 31, 1 do
local p = {x = left_top.x + x, y = left_top.y + y}
if surface.get_tile(p).collides_with("resource-layer") then
surface.set_tiles({{name = get_replacement_tile(surface, p), position = p}}, true)
end
end
end
end
local function out_of_map(surface, left_top) local function out_of_map(surface, left_top)
for x = 0, 31, 1 do for x = 0, 31, 1 do
for y = 0, 31, 1 do for y = 0, 31, 1 do
@@ -257,7 +280,8 @@ local function process_chunk(surface, left_top)
if not surface.valid then return end if not surface.valid then return end
if left_top.x >= 768 then return end if left_top.x >= 768 then return end
if left_top.x < -768 then return end if left_top.x < -768 then return end
if left_top.y > 0 then game.forces.player.chart(surface, {{left_top.x, left_top.y},{left_top.x + 31, left_top.y + 31}}) end if left_top.y >= 0 then replace_water(surface, left_top) end
if left_top.y > 32 then game.forces.player.chart(surface, {{left_top.x, left_top.y},{left_top.x + 31, left_top.y + 31}}) end
if left_top.y == 64 and left_top.x == 64 then if left_top.y == 64 and left_top.x == 64 then
local p = global.locomotive.position local p = global.locomotive.position
for _, entity in pairs(surface.find_entities_filtered({area = {{p.x - 3, p.y - 4},{p.x + 3, p.y + 8}}, force = "neutral"})) do entity.destroy() end for _, entity in pairs(surface.find_entities_filtered({area = {{p.x - 3, p.y - 4},{p.x + 3, p.y + 8}}, force = "neutral"})) do entity.destroy() end
@@ -282,5 +306,5 @@ local function on_chunk_generated(event)
end end
local event = require 'utils.event' local event = require 'utils.event'
event.on_nth_tick(6, process_chunk_queue) event.on_nth_tick(4, process_chunk_queue)
event.add(defines.events.on_chunk_generated, on_chunk_generated) event.add(defines.events.on_chunk_generated, on_chunk_generated)

View File

@@ -58,7 +58,7 @@ function treasure_chest(surface, position)
{{name = "advanced-circuit", count = math_random(50,150)}, weight = 3, evo_min = 0.4, evo_max = 1}, {{name = "advanced-circuit", count = math_random(50,150)}, weight = 3, evo_min = 0.4, evo_max = 1},
{{name = "electronic-circuit", count = math_random(50,150)}, weight = 3, evo_min = 0.0, evo_max = 0.4}, {{name = "electronic-circuit", count = math_random(50,150)}, weight = 3, evo_min = 0.0, evo_max = 0.4},
{{name = "processing-unit", count = math_random(50,150)}, weight = 3, evo_min = 0.7, evo_max = 1}, {{name = "processing-unit", count = math_random(50,150)}, weight = 3, evo_min = 0.7, evo_max = 1},
{{name = "explosives", count = math_random(40,50)}, weight = 10, evo_min = 0.0, evo_max = 1}, {{name = "explosives", count = math_random(50,150)}, weight = 20, evo_min = 0.0, evo_max = 1},
{{name = "lubricant-barrel", count = math_random(4,10)}, weight = 1, evo_min = 0.3, evo_max = 0.5}, {{name = "lubricant-barrel", count = math_random(4,10)}, weight = 1, evo_min = 0.3, evo_max = 0.5},
{{name = "rocket-fuel", count = math_random(4,10)}, weight = 2, evo_min = 0.3, evo_max = 0.7}, {{name = "rocket-fuel", count = math_random(4,10)}, weight = 2, evo_min = 0.3, evo_max = 0.7},
--{{name = "computer", count = 1}, weight = 2, evo_min = 0, evo_max = 1}, --{{name = "computer", count = 1}, weight = 2, evo_min = 0, evo_max = 1},
@@ -156,7 +156,7 @@ function treasure_chest(surface, position)
local e = surface.create_entity({name = name, position=position, force="neutral"}) local e = surface.create_entity({name = name, position=position, force="neutral"})
e.minable = false e.minable = false
local i = e.get_inventory(defines.inventory.chest) local i = e.get_inventory(defines.inventory.chest)
for x = 1, math_random(3,4), 1 do for x = 1, math_random(2,6), 1 do
local loot = chest_raffle[math_random(1,#chest_raffle)] local loot = chest_raffle[math_random(1,#chest_raffle)]
i.insert(loot) i.insert(loot)
end end

View File

@@ -136,7 +136,7 @@ end
local function tick(event) local function tick(event)
for key, cell in pairs(global.explosion_cells) do for key, cell in pairs(global.explosion_cells) do
if cell.spawn_tick < game.tick then if cell.spawn_tick < game.tick then
life_cycle(cell, key) life_cycle(cell)
global.explosion_cells[key] = nil global.explosion_cells[key] = nil
end end
end end

View File

@@ -6,9 +6,8 @@ local side_target_types = {"accumulator", "assembling-machine", "beacon", "boile
"logistic-container", "mining-drill", "container", "pump", "radar", "reactor", "roboport", "rocket-silo", "solar-panel", "storage-tank",} "logistic-container", "mining-drill", "container", "pump", "radar", "reactor", "roboport", "rocket-silo", "solar-panel", "storage-tank",}
local function debug_print(msg) local function debug_print(msg)
if global.wave_defense.debug then if not global.wave_defense.debug then return end
print("WaveDefense>> " .. msg) print("WaveDefense: " .. msg)
end
end end
local function is_unit_valid(biter) local function is_unit_valid(biter)
@@ -99,7 +98,7 @@ local function set_group_spawn_position()
if not spawner then return end if not spawner then return end
local position = global.wave_defense.surface.find_non_colliding_position("rocket-silo", spawner.position, 48, 1) local position = global.wave_defense.surface.find_non_colliding_position("rocket-silo", spawner.position, 48, 1)
if not position then return end if not position then return end
global.wave_defense.spawn_position = {x = position.x, y = position.y} global.wave_defense.spawn_position = {x = math.floor(position.x), y = math.floor(position.y)}
end end
local function set_enemy_evolution() local function set_enemy_evolution()
@@ -117,7 +116,7 @@ local function spawn_biter()
if global.wave_defense.threat <= 0 then return false end if global.wave_defense.threat <= 0 then return false end
if global.wave_defense.active_biter_count >= global.wave_defense.max_active_biters then return false end if global.wave_defense.active_biter_count >= global.wave_defense.max_active_biters then return false end
local name = wave_defense_roll_biter_name() local name = wave_defense_roll_biter_name()
local position = global.wave_defense.surface.find_non_colliding_position(name, global.wave_defense.spawn_position, 32, 1) local position = global.wave_defense.surface.find_non_colliding_position(name, global.wave_defense.spawn_position, 48, 2)
if not position then return false end if not position then return false end
local biter = global.wave_defense.surface.create_entity({name = name, position = position, force = "enemy"}) local biter = global.wave_defense.surface.create_entity({name = name, position = position, force = "enemy"})
biter.ai_settings.allow_destroy_when_commands_fail = false biter.ai_settings.allow_destroy_when_commands_fail = false
@@ -132,6 +131,7 @@ local function spawn_unit_group()
if global.wave_defense.threat <= 0 then return false end if global.wave_defense.threat <= 0 then return false end
if global.wave_defense.active_biter_count >= global.wave_defense.max_active_biters then return false end if global.wave_defense.active_biter_count >= global.wave_defense.max_active_biters then return false end
set_group_spawn_position() set_group_spawn_position()
debug_print("Spawning unit group at position:" .. global.wave_defense.spawn_position.x .." " .. global.wave_defense.spawn_position.y)
local unit_group = global.wave_defense.surface.create_unit_group({position = global.wave_defense.spawn_position, force = "enemy"}) local unit_group = global.wave_defense.surface.create_unit_group({position = global.wave_defense.spawn_position, force = "enemy"})
for a = 1, global.wave_defense.group_size, 1 do for a = 1, global.wave_defense.group_size, 1 do
local biter = spawn_biter() local biter = spawn_biter()
@@ -148,28 +148,26 @@ local function spawn_unit_group()
return true return true
end end
local function set_unit_group_count() local function get_active_unit_groups_count()
c = 0 local count = 0
for k, group in pairs(global.wave_defense.unit_groups) do for _, g in pairs(global.wave_defense.unit_groups) do
if group.valid then if g.valid then
if #group.members > 0 then count = count + 1
c = c + 1
else
group.destroy()
global.wave_defense.unit_groups[k] = nil
end
else
global.wave_defense.unit_groups[k] = nil
end end
end end
global.wave_defense.active_unit_group_count = c debug_print("Active unit groups count: " .. count)
return count
end end
local function spawn_attack_groups() local function spawn_attack_groups()
if global.wave_defense.active_biter_count >= global.wave_defense.max_active_biters then return false end if global.wave_defense.active_biter_count >= global.wave_defense.max_active_biters then return false end
if global.wave_defense.threat <= 0 then return false end if global.wave_defense.threat <= 0 then return false end
wave_defense_set_biter_raffle(global.wave_defense.wave_number) wave_defense_set_biter_raffle(global.wave_defense.wave_number)
for a = 1, global.wave_defense.max_active_unit_groups - global.wave_defense.active_unit_group_count, 1 do
local count = get_active_unit_groups_count()
if count >= global.wave_defense.max_active_unit_groups then return end
for _ = 1, math_random(1,2), 1 do
if not spawn_unit_group() then break end if not spawn_unit_group() then break end
end end
end end
@@ -178,7 +176,7 @@ local function set_next_wave()
global.wave_defense.wave_number = global.wave_defense.wave_number + 1 global.wave_defense.wave_number = global.wave_defense.wave_number + 1
global.wave_defense.group_size = global.wave_defense.wave_number * 2 global.wave_defense.group_size = global.wave_defense.wave_number * 2
if global.wave_defense.group_size > global.wave_defense.max_group_size then global.wave_defense.group_size = global.wave_defense.max_group_size end if global.wave_defense.group_size > global.wave_defense.max_group_size then global.wave_defense.group_size = global.wave_defense.max_group_size end
global.wave_defense.threat = global.wave_defense.threat + global.wave_defense.wave_number * 3 global.wave_defense.threat = global.wave_defense.threat + global.wave_defense.wave_number * 2
global.wave_defense.last_wave = global.wave_defense.next_wave global.wave_defense.last_wave = global.wave_defense.next_wave
global.wave_defense.next_wave = game.tick + global.wave_defense.wave_interval global.wave_defense.next_wave = game.tick + global.wave_defense.wave_interval
end end
@@ -188,6 +186,7 @@ local function get_commmands(group)
local group_position = {x = group.position.x, y = group.position.y} local group_position = {x = group.position.x, y = group.position.y}
local step_length = global.wave_defense.unit_group_command_step_length local step_length = global.wave_defense.unit_group_command_step_length
if math_random(1,3) ~= 1 then
local side_target = false local side_target = false
for _ = 1, 3, 1 do for _ = 1, 3, 1 do
side_target = get_side_target() side_target = get_side_target()
@@ -227,6 +226,7 @@ local function get_commmands(group)
distraction = defines.distraction.by_enemy, distraction = defines.distraction.by_enemy,
} }
end end
end
local target_position = global.wave_defense.target.position local target_position = global.wave_defense.target.position
local distance_to_target = math.floor(math.sqrt((target_position.x - group_position.x) ^ 2 + (target_position.y - group_position.y) ^ 2)) local distance_to_target = math.floor(math.sqrt((target_position.x - group_position.x) ^ 2 + (target_position.y - group_position.y) ^ 2))
@@ -350,7 +350,6 @@ local function on_tick()
set_main_target() set_main_target()
set_enemy_evolution() set_enemy_evolution()
spawn_attack_groups() spawn_attack_groups()
set_unit_group_count()
give_commands_to_unit_groups() give_commands_to_unit_groups()
build_nest() build_nest()
build_worm() build_worm()
@@ -370,7 +369,6 @@ function reset_wave_defense()
max_active_unit_groups = 8, max_active_unit_groups = 8,
max_active_biters = 1024, max_active_biters = 1024,
max_biter_age = 3600 * 60, max_biter_age = 3600 * 60,
active_unit_group_count = 0,
active_biter_count = 0, active_biter_count = 0,
get_random_close_spawner_attempts = 5, get_random_close_spawner_attempts = 5,
side_target_search_radius = 768, side_target_search_radius = 768,

View File

@@ -10,6 +10,7 @@ end
function build_nest() function build_nest()
if global.wave_defense.threat < 1000 then return end if global.wave_defense.threat < 1000 then return end
if math_random(1, global.wave_defense.nest_building_chance) ~= 1 then return end if math_random(1, global.wave_defense.nest_building_chance) ~= 1 then return end
if #global.wave_defense.unit_groups == 0 then return end
local group = global.wave_defense.unit_groups[math_random(1, #global.wave_defense.unit_groups)] local group = global.wave_defense.unit_groups[math_random(1, #global.wave_defense.unit_groups)]
if not group then return end if not group then return end
if not group.valid then return end if not group.valid then return end
@@ -32,6 +33,7 @@ end
function build_worm() function build_worm()
if global.wave_defense.threat < 1000 then return end if global.wave_defense.threat < 1000 then return end
if math_random(1, global.wave_defense.worm_building_chance) ~= 1 then return end if math_random(1, global.wave_defense.worm_building_chance) ~= 1 then return end
if #global.wave_defense.unit_groups == 0 then return end
local group = global.wave_defense.unit_groups[math_random(1, #global.wave_defense.unit_groups)] local group = global.wave_defense.unit_groups[math_random(1, #global.wave_defense.unit_groups)]
if not group then return end if not group then return end
if not group.valid then return end if not group.valid then return end