|
local util = require("util") |
|
local get_walkable_tile = util.get_walkable_tile |
|
local mod_gui = require("mod-gui") |
|
local config = require("wave_defense_config") |
|
local upgrades = require("wave_defense_upgrades") |
|
local increment = util.increment |
|
local format_number = util.format_number |
|
local format_time = util.formattime |
|
local insert = table.insert |
|
local floor = math.floor |
|
local ceil = math.ceil |
|
|
|
local game_state = |
|
{ |
|
in_round = 1, |
|
in_preview = 2, |
|
defeat = 3, |
|
victory = 4 |
|
} |
|
|
|
local script_data = |
|
{ |
|
config = config, |
|
difficulty = config.difficulties.normal, |
|
day_number = 1, |
|
money = 0, |
|
team_upgrades = {}, |
|
gui_elements = |
|
{ |
|
preview_frame = {}, |
|
day_button = {}, |
|
upgrade_frame_button = {}, |
|
upgrade_frame = {}, |
|
upgrade_table = {}, |
|
admin_frame_button = {}, |
|
admin_frame = {} |
|
}, |
|
gui_labels = |
|
{ |
|
money_label = {}, |
|
time_label = {}, |
|
day_label = {} |
|
}, |
|
gui_actions = {}, |
|
spawners = {}, |
|
spawner_distances = {}, |
|
spawner_path_requests = {}, |
|
state = game_state.in_preview, |
|
random = nil, |
|
wave_tick = nil, |
|
spawn_time = nil, |
|
wave_time = nil, |
|
path_request_queue = {} |
|
} |
|
|
|
local get_starting_point = function() |
|
return {x = 0, y = 0} |
|
end |
|
|
|
local is_player_force = function(force) |
|
return force == game.forces.player |
|
end |
|
|
|
local get_preview_size = function() |
|
return 32 * 10 |
|
end |
|
|
|
local script_events = |
|
{ |
|
on_round_started = script.generate_event_name() |
|
} |
|
|
|
local power_functions = |
|
{ |
|
default = function(level) |
|
return (level ^ 1.15) * 500 * ((#game.connected_players) ^ 0.5) |
|
end, |
|
hard = function(level) |
|
return (level ^ 1.2) * 500 * ((#game.connected_players) ^ 0.75) |
|
end |
|
} |
|
|
|
local speed_multiplier_functions = |
|
{ |
|
default = function(level) |
|
return (level ^ 0.1) - 0.2 |
|
end |
|
} |
|
|
|
local set_daytime_settings = function() |
|
local surface = script_data.surface |
|
if not (surface and surface.valid) then return end |
|
local settings = script_data.difficulty.day_settings |
|
for name, value in pairs (settings) do |
|
surface[name] = value |
|
end |
|
end |
|
|
|
local max_seed = 2^32 - 2 |
|
local initial_seed = 2390375328 |
|
|
|
local players = function(index) |
|
return (index and game.get_player(index)) or game.players |
|
end |
|
|
|
function deregister_gui(gui) |
|
local player_gui_actions = script_data.gui_actions[gui.player_index] |
|
if not player_gui_actions then return end |
|
player_gui_actions[gui.index] = nil |
|
for k, child in pairs (gui.children) do |
|
deregister_gui(child) |
|
end |
|
end |
|
|
|
function register_gui_action(gui, param) |
|
local gui_actions = script_data.gui_actions |
|
local player_gui_actions = gui_actions[gui.player_index] |
|
if not player_gui_actions then |
|
gui_actions[gui.player_index] = {} |
|
player_gui_actions = gui_actions[gui.player_index] |
|
end |
|
player_gui_actions[gui.index] = param |
|
end |
|
|
|
function init_player_force() |
|
|
|
for name, upgrade in pairs (get_upgrades()) do |
|
script_data.team_upgrades[name] = 0 |
|
end |
|
|
|
local force = game.forces.player |
|
force.reset() |
|
local surface = script_data.surface |
|
if surface and surface.valid then |
|
local size = get_preview_size() |
|
local starting_point = get_starting_point() |
|
force.chart(surface, {{starting_point.x - size, starting_point.y - size},{starting_point.x + (size - 32), starting_point.y + (size - 32)}}) |
|
end |
|
set_research(force) |
|
set_recipes(force) |
|
|
|
force.disable_research() |
|
|
|
end |
|
|
|
function set_tiles_safe(surface, tiles) |
|
local grass = get_walkable_tile() |
|
local grass_tiles = {} |
|
for k, tile in pairs (tiles) do |
|
grass_tiles[k] = {position = {x = (tile.position.x or tile.position[1]), y = (tile.position.y or tile.position[2])}, name = grass} |
|
end |
|
surface.set_tiles(grass_tiles, false) |
|
surface.set_tiles(tiles) |
|
end |
|
|
|
local set_up_player = function(player) |
|
if not player.connected then return end |
|
gui_init(player) |
|
|
|
if not is_player_force(player.force) then return end |
|
|
|
if player.ticks_to_respawn then player.ticks_to_respawn = nil end |
|
|
|
if script_data.state == game_state.in_preview then |
|
if player.character then |
|
player.character.destroy() |
|
end |
|
player.spectator = true |
|
player.set_controller{type = defines.controllers.god} |
|
player.teleport({0,0}, game.surfaces.nauvis) |
|
player.create_character() |
|
return |
|
end |
|
|
|
if script_data.state == game_state.in_round or script_data.state == game_state.victory then |
|
local surface = script_data.surface |
|
if player.surface == surface then return end |
|
if player.character then |
|
player.character.destroy() |
|
end |
|
local force = game.forces.player |
|
local spawn = force.get_spawn_position(surface) |
|
player.teleport(spawn, surface) |
|
local character = surface.create_entity{name = "character", position = surface.find_non_colliding_position("character", spawn, 0, 1), force = force} |
|
player.set_controller{type = defines.controllers.character, character = character} |
|
give_respawn_equipment(player) |
|
player.print({"wave-defense-intro"}) |
|
return |
|
end |
|
|
|
if script_data.state == game_state.defeat then |
|
if player.character then |
|
player.character.destroy() |
|
end |
|
local surface = script_data.surface |
|
local force = game.forces.player |
|
local position = force.get_spawn_position(surface) |
|
player.set_controller{type = defines.controllers.spectator} |
|
player.teleport(position, surface) |
|
return |
|
end |
|
|
|
end |
|
|
|
function set_up_players() |
|
for k, player in pairs (players()) do |
|
set_up_player(player) |
|
end |
|
end |
|
|
|
local init_enemy_force = function() |
|
local force = game.forces.enemy |
|
force.reset() |
|
force.evolution_factor = script_data.difficulty.starting_evolution_factor |
|
end |
|
|
|
function start_round() |
|
game.reset_time_played() |
|
local surface = script_data.surface |
|
surface.daytime = surface.dawn |
|
surface.always_day = false |
|
script_data.state = game_state.in_round |
|
local tick = game.tick |
|
script_data.money = 0 |
|
script_data.day_number = 1 |
|
|
|
script_data.wave_time = surface.ticks_per_day |
|
|
|
script_data.spawn_time = floor(surface.ticks_per_day * (surface.morning - surface.evening)) |
|
|
|
script_data.wave_tick = tick + ceil(surface.ticks_per_day * surface.evening) + ceil((1 - surface.dawn) * surface.ticks_per_day) |
|
script_data.dawn_tick = nil |
|
script_data.spawn_tick = nil |
|
script_data.end_spawn_tick = nil |
|
game.print({"start-round-message"}) |
|
set_up_players() |
|
init_player_force() |
|
init_enemy_force() |
|
script.raise_event(script_events.on_round_started, {}) |
|
for k, player in pairs (players()) do |
|
player.clear_recipe_notifications() |
|
end |
|
end |
|
|
|
function restart_round() |
|
script_data.game_state = game_state.in_preview |
|
set_up_players() |
|
local seed = script_data.surface.map_gen_settings.seed |
|
create_battle_surface(seed) |
|
start_round() |
|
end |
|
|
|
local get_random_seed = function() |
|
return (32452867 * game.tick) % max_seed |
|
end |
|
|
|
local get_starting_area_size = function() |
|
return script_data.difficulty.starting_area_size |
|
end |
|
|
|
local get_base_radius = function() |
|
return (32 * (floor(((script_data.surface.get_starting_area_radius() / 32) - 1) / (2 ^ 0.5)))) |
|
end |
|
|
|
function create_battle_surface(seed) |
|
local index = 1 |
|
local name = "Surface " |
|
while game.surfaces[name..index] do |
|
index = index + 1 |
|
end |
|
name = name..index |
|
for k, surface in pairs (game.surfaces) do |
|
if surface.name ~= "nauvis" then |
|
game.delete_surface(surface.name) |
|
end |
|
end |
|
|
|
|
|
script_data.spawners = {} |
|
script_data.spawner_distances = {} |
|
script_data.spawner_path_requests = {} |
|
script_data.path_request_queue = {} |
|
|
|
local settings = script_data.config.map_gen_settings |
|
local seed = seed or get_random_seed() |
|
script_data.random = game.create_random_generator(seed) |
|
settings.seed = seed |
|
settings.starting_area = get_starting_area_size() |
|
local starting_point = get_starting_point() |
|
settings.starting_points = {starting_point} |
|
|
|
settings.property_expression_names = |
|
{ |
|
elevation = not script_data.config.infinite and "0_17-island" or nil |
|
} |
|
|
|
local surface = game.create_surface(name, settings) |
|
local size = get_preview_size() |
|
script_data.surface = surface |
|
set_daytime_settings() |
|
surface.request_to_generate_chunks(starting_point, 1 + ceil(get_base_radius() / 32)) |
|
surface.force_generate_chunk_requests() |
|
|
|
create_silo(starting_point) |
|
create_wall(starting_point) |
|
create_turrets(starting_point) |
|
create_starting_chest(starting_point) |
|
game.forces.player.chart(surface, {{starting_point.x - size, starting_point.y - size},{starting_point.x + (size - 32), starting_point.y + (size - 32)}}) |
|
for k, player in pairs (players()) do |
|
refresh_preview_gui(player) |
|
end |
|
end |
|
|
|
function create_silo(starting_point) |
|
local force = game.forces.player |
|
local surface = script_data.surface |
|
local silo_position = {starting_point.x, starting_point.y - 8} |
|
local silo_name = "rocket-silo" |
|
if not game.entity_prototypes[silo_name] then log("Silo not created as "..silo_name.." is not a valid entity prototype") return end |
|
local silo = surface.create_entity{name = silo_name, position = silo_position, force = force, raise_built = true, create_build_effect_smoke = false} |
|
if not (silo and silo.valid) then return end |
|
rendering.draw_light |
|
{ |
|
sprite = "utility/light_medium", |
|
target = silo, |
|
surface = silo.surface, |
|
scale = 4 |
|
} |
|
silo.minable = false |
|
if silo.supports_backer_name() then |
|
silo.backer_name = "" |
|
end |
|
script_data.silo = silo |
|
|
|
local tile_name = "concrete" |
|
if not game.tile_prototypes[tile_name] then tile_name = get_walkable_tile() end |
|
|
|
local tiles_2 = {} |
|
local box = silo.bounding_box |
|
local x1, x2, y1, y2 = |
|
floor(box.left_top.x) - 1, |
|
floor(box.right_bottom.x) + 1, |
|
floor(box.left_top.y) - 1, |
|
floor(box.right_bottom.y) + 1 |
|
for X = x1, x2 do |
|
for Y = y1, y2 do |
|
insert(tiles_2, {name = tile_name, position = {X, Y}}) |
|
end |
|
end |
|
|
|
for i, entity in pairs(surface.find_entities_filtered({area = {{x1 - 1, y1 - 1},{x2 + 1, y2 + 1}}, force = "neutral"})) do |
|
entity.destroy() |
|
end |
|
|
|
set_tiles_safe(surface, tiles_2) |
|
end |
|
|
|
local is_in_map = function(width, height, position) |
|
return position.x >= -width |
|
and position.x < width |
|
and position.y >= -height |
|
and position.y < height |
|
end |
|
|
|
function create_wall(starting_point) |
|
local force = game.forces.player |
|
local surface = script_data.surface |
|
local origin = starting_point or force.get_spawn_position(surface) |
|
local radius = get_base_radius() + 5 |
|
local height = surface.map_gen_settings.height / 2 |
|
local width = surface.map_gen_settings.width / 2 |
|
local perimeter_top = {} |
|
local perimeter_bottom = {} |
|
local perimeter_left = {} |
|
local perimeter_right = {} |
|
local tiles = {} |
|
local insert = insert |
|
local can_place_entity = surface.can_place_entity |
|
for X = -radius, radius - 1 do |
|
insert(perimeter_top, {x = origin.x + X, y = origin.y - radius}) |
|
insert(perimeter_bottom, {x = origin.x + X, y = origin.y + (radius-1)}) |
|
end |
|
for Y = -radius, radius - 1 do |
|
insert(perimeter_left, {x = origin.x - radius, y = origin.y + Y}) |
|
insert(perimeter_right, {x = origin.x + (radius-1), y = origin.y + Y}) |
|
end |
|
local tile_name = "refined-concrete" |
|
if not game.tile_prototypes[tile_name] then tile_name = get_walkable_tile() end |
|
local areas = |
|
{ |
|
{{perimeter_top[1].x, perimeter_top[1].y - 1}, {perimeter_top[#perimeter_top].x, perimeter_top[1].y + 3}}, |
|
{{perimeter_bottom[1].x, perimeter_bottom[1].y - 3}, {perimeter_bottom[#perimeter_bottom].x, perimeter_bottom[1].y + 1}}, |
|
{{perimeter_left[1].x - 1, perimeter_left[1].y}, {perimeter_left[1].x + 3, perimeter_left[#perimeter_left].y}}, |
|
{{perimeter_right[1].x - 3, perimeter_right[1].y}, {perimeter_right[1].x + 1, perimeter_right[#perimeter_right].y}} |
|
} |
|
local find_entities_filtered = surface.find_entities_filtered |
|
local destroy_param = {do_cliff_correction = true} |
|
for k, area in pairs (areas) do |
|
for i, entity in pairs(find_entities_filtered({area = area})) do |
|
entity.destroy(destroy_param) |
|
end |
|
end |
|
local wall_name = "stone-wall" |
|
local gate_name = "gate" |
|
if not game.entity_prototypes[wall_name] then |
|
log("Setting walls cancelled as "..wall_name.." is not a valid entity prototype") |
|
return |
|
end |
|
if not game.entity_prototypes[gate_name] then |
|
log("Setting walls cancelled as "..gate_name.." is not a valid entity prototype") |
|
return |
|
end |
|
local should_gate = |
|
{ |
|
[12] = true, |
|
[13] = true, |
|
[14] = true, |
|
[15] = true, |
|
[16] = true, |
|
[17] = true, |
|
[18] = true, |
|
[19] = true |
|
} |
|
local create_entity = surface.create_entity |
|
for k, position in pairs (perimeter_left) do |
|
if is_in_map(width, height, position) and can_place_entity{name = wall_name, position = position, force = force, build_check_type = defines.build_check_type.manual_ghost, forced = true} then |
|
if (k ~= 1) and (k ~= #perimeter_left) then |
|
insert(tiles, {name = tile_name, position = {position.x + 2, position.y}}) |
|
insert(tiles, {name = tile_name, position = {position.x + 1, position.y}}) |
|
end |
|
if should_gate[position.y % 32] then |
|
create_entity{name = gate_name, position = position, direction = 0, force = force, create_build_effect_smoke = false} |
|
else |
|
create_entity{name = wall_name, position = position, force = force, create_build_effect_smoke = false} |
|
end |
|
end |
|
end |
|
for k, position in pairs (perimeter_right) do |
|
if is_in_map(width, height, position) and can_place_entity{name = wall_name, position = position, force = force, build_check_type = defines.build_check_type.manual_ghost, forced = true} then |
|
if (k ~= 1) and (k ~= #perimeter_right) then |
|
insert(tiles, {name = tile_name, position = {position.x - 2, position.y}}) |
|
insert(tiles, {name = tile_name, position = {position.x - 1, position.y}}) |
|
end |
|
if should_gate[position.y % 32] then |
|
create_entity{name = gate_name, position = position, direction = 0, force = force, create_build_effect_smoke = false} |
|
else |
|
create_entity{name = wall_name, position = position, force = force, create_build_effect_smoke = false} |
|
end |
|
end |
|
end |
|
for k, position in pairs (perimeter_top) do |
|
if is_in_map(width, height, position) and can_place_entity{name = wall_name, position = position, force = force, build_check_type = defines.build_check_type.manual_ghost, forced = true} then |
|
if (k ~= 1) and (k ~= #perimeter_top) then |
|
insert(tiles, {name = tile_name, position = {position.x, position.y + 2}}) |
|
insert(tiles, {name = tile_name, position = {position.x, position.y + 1}}) |
|
end |
|
if should_gate[position.x % 32] then |
|
create_entity{name = gate_name, position = position, direction = 2, force = force, create_build_effect_smoke = false} |
|
else |
|
create_entity{name = wall_name, position = position, force = force, create_build_effect_smoke = false} |
|
end |
|
end |
|
end |
|
for k, position in pairs (perimeter_bottom) do |
|
if is_in_map(width, height, position) and can_place_entity{name = wall_name, position = position, force = force, build_check_type = defines.build_check_type.manual_ghost, forced = true} then |
|
if (k ~= 1) and (k ~= #perimeter_bottom) then |
|
insert(tiles, {name = tile_name, position = {position.x, position.y - 2}}) |
|
insert(tiles, {name = tile_name, position = {position.x, position.y - 1}}) |
|
end |
|
if should_gate[position.x % 32] then |
|
create_entity{name = gate_name, position = position, direction = 2, force = force, create_build_effect_smoke = false} |
|
else |
|
create_entity{name = wall_name, position = position, force = force, create_build_effect_smoke = false} |
|
end |
|
end |
|
end |
|
set_tiles_safe(surface, tiles) |
|
end |
|
|
|
function create_turrets(starting_point) |
|
local force = game.forces.player |
|
local turret_name = "gun-turret" |
|
if not game.entity_prototypes[turret_name] then return end |
|
local surface = script_data.surface |
|
local ammo_name = "firearm-magazine" |
|
local direction = defines.direction |
|
local surface = script_data.surface |
|
local height = surface.map_gen_settings.height / 2 |
|
local width = surface.map_gen_settings.width / 2 |
|
local origin = starting_point |
|
local radius = get_base_radius() - 5 |
|
local positions = {} |
|
local Xo = origin.x |
|
local Yo = origin.y |
|
for X = -radius, radius do |
|
local Xt = X + Xo |
|
if X == -radius then |
|
for Y = -radius, radius do |
|
local Yt = Y + Yo |
|
if (Yt + 16) % 32 ~= 0 and Yt % 8 == 0 then |
|
insert(positions, {x = Xo - radius, y = Yt, direction = direction.west}) |
|
insert(positions, {x = Xo + radius, y = Yt, direction = direction.east}) |
|
end |
|
end |
|
elseif (Xt + 16) % 32 ~= 0 and Xt % 8 == 0 then |
|
insert(positions, {x = Xt, y = Yo - radius, direction = direction.north}) |
|
insert(positions, {x = Xt, y = Yo + radius, direction = direction.south}) |
|
end |
|
end |
|
local tiles = {} |
|
local tile_name = "hazard-concrete-left" |
|
if not game.tile_prototypes[tile_name] then tile_name = get_walkable_tile() end |
|
local stack |
|
if ammo_name and game.item_prototypes[ammo_name] then |
|
stack = {name = ammo_name, count = 50} |
|
end |
|
local direction_offset = |
|
{ |
|
[direction.north] = {0, -13}, |
|
[direction.east] = {13, 0}, |
|
[direction.south] = {0, 13}, |
|
[direction.west] = {-13, 0} |
|
} |
|
local find_entities_filtered = surface.find_entities_filtered |
|
local neutral = game.forces.neutral |
|
local destroy_params = {do_cliff_correction = true} |
|
local floor = floor |
|
local create_entity = surface.create_entity |
|
local can_place_entity = surface.can_place_entity |
|
for k, position in pairs (positions) do |
|
if is_in_map(width, height, position) and can_place_entity{name = turret_name, position = position, force = force, build_check_type = defines.build_check_type.manual_ghost, forced = true} then |
|
local turret = create_entity{name = turret_name, position = position, force = force, direction = position.direction, create_build_effect_smoke = false} |
|
local box = turret.bounding_box |
|
for k, entity in pairs (find_entities_filtered{area = turret.bounding_box, force = neutral}) do |
|
entity.destroy(destroy_params) |
|
end |
|
if stack then |
|
turret.insert(stack) |
|
end |
|
for x = floor(box.left_top.x), floor(box.right_bottom.x) do |
|
for y = floor(box.left_top.y), floor(box.right_bottom.y) do |
|
insert(tiles, {name = tile_name, position = {x, y}}) |
|
end |
|
end |
|
end |
|
end |
|
set_tiles_safe(surface, tiles) |
|
end |
|
|
|
local root_2 = 2 ^ 0.5 |
|
|
|
function get_chest_offset(n) |
|
local offset_x = 0 |
|
n = n / 2 |
|
if n % 1 == 0.5 then |
|
offset_x = -1 |
|
n = n + 0.5 |
|
end |
|
local root = n ^ 0.5 |
|
local nearest_root = math.floor(root + 0.5) |
|
local upper_root = math.ceil(root) |
|
local root_difference = math.abs(nearest_root ^ 2 - n) |
|
if nearest_root == upper_root then |
|
x = upper_root - root_difference |
|
y = nearest_root |
|
else |
|
x = upper_root |
|
y = root_difference |
|
end |
|
local orientation = 2 * math.pi * (45/360) |
|
x = x * root_2 |
|
y = y * root_2 |
|
local rotated_x = math.floor(0.5 + x * math.cos(orientation) - y * math.sin(orientation)) |
|
local rotated_y = math.floor(0.5 + x * math.sin(orientation) + y * math.cos(orientation)) |
|
return {x = rotated_x + offset_x, y = rotated_y} |
|
end |
|
|
|
function create_starting_chest(starting_point) |
|
local force = game.forces.player |
|
local inventory = script_data.difficulty.starting_chest_items |
|
if not (table_size(inventory) > 0) then return end |
|
local surface = script_data.surface |
|
local chest_name = "iron-chest" |
|
local prototype = game.entity_prototypes[chest_name] |
|
if not prototype then |
|
log("Starting chest "..chest_name.." is not a valid entity prototype, picking a new container from prototype list") |
|
for name, chest in pairs (game.entity_prototypes) do |
|
if chest.type == "container" then |
|
chest_name = name |
|
prototype = chest |
|
break |
|
end |
|
end |
|
end |
|
local size = math.ceil(prototype.radius * 2) |
|
local origin = {x = starting_point.x, y = starting_point.y} |
|
local index = 1 |
|
local position = {x = origin.x + get_chest_offset(index).x * size, y = origin.y + get_chest_offset(index).y * size} |
|
local chest = surface.create_entity{name = chest_name, position = position, force = force, create_build_effect_smoke = false} |
|
for k, v in pairs (surface.find_entities_filtered{force = "neutral", area = chest.bounding_box}) do |
|
v.destroy() |
|
end |
|
local tiles = {} |
|
local grass = {} |
|
local tile_name = "refined-concrete" |
|
if not game.tile_prototypes[tile_name] then tile_name = get_walkable_tile() end |
|
insert(tiles, {name = tile_name, position = {x = position.x, y = position.y}}) |
|
chest.destructible = false |
|
local items = game.item_prototypes |
|
for name, count in pairs (inventory) do |
|
if items[name] then |
|
local count_to_insert = math.ceil(count) |
|
local difference = count_to_insert - chest.insert{name = name, count = count_to_insert} |
|
while difference > 0 do |
|
index = index + 1 |
|
position = {x = origin.x + get_chest_offset(index).x * size, y = origin.y + get_chest_offset(index).y * size} |
|
chest = surface.create_entity{name = chest_name, position = position, force = force, create_build_effect_smoke = false} |
|
for k, v in pairs (surface.find_entities_filtered{force = "neutral", area = chest.bounding_box}) do |
|
v.destroy() |
|
end |
|
insert(tiles, {name = tile_name, position = {x = position.x, y = position.y}}) |
|
chest.destructible = false |
|
difference = difference - chest.insert{name = name, count = difference} |
|
end |
|
end |
|
end |
|
set_tiles_safe(surface, tiles) |
|
end |
|
|
|
local get_ticks_till_dawn = function() |
|
local surface = script_data.surface |
|
local current_daytime = surface.daytime |
|
local dawn = surface.dawn |
|
local diff = dawn - current_daytime |
|
if diff < 0 then diff = diff + 1 end |
|
local ticks = math.ceil(diff * surface.ticks_per_day) |
|
return ticks |
|
end |
|
|
|
local make_next_dawn_tick = function() |
|
script_data.dawn_tick = game.tick + get_ticks_till_dawn() |
|
end |
|
|
|
local check_dawn = function(tick) |
|
if not script_data.dawn_tick or tick < script_data.dawn_tick then return end |
|
increment(script_data, "day_number") |
|
game.print({"dawn-of-new-day", script_data.day_number}) |
|
update_label_list(script_data.gui_labels.day_label, {"current-day", script_data.day_number}) |
|
script_data.dawn_tick = nil |
|
end |
|
|
|
function check_next_wave(tick) |
|
if not script_data.wave_tick then return end |
|
if script_data.wave_tick ~= tick then return end |
|
next_wave() |
|
end |
|
|
|
function next_wave() |
|
make_next_wave_tick() |
|
make_next_spawn_tick() |
|
spawn_units() |
|
end |
|
|
|
function wave_end() |
|
make_next_dawn_tick() |
|
script_data.spawn_tick = nil |
|
script_data.end_spawn_tick = nil |
|
end |
|
|
|
local victory_sound = {path = "utility/game_won"} |
|
|
|
local round_won = function() |
|
if script_data.state ~= game_state.in_round then return end |
|
game.play_sound(victory_sound) |
|
game.print({"you-win", script_data.day_number}) |
|
script_data.state = game_state.victory |
|
set_up_players() |
|
|
|
end |
|
|
|
function make_next_spawn_tick() |
|
local addition = 8 * 60 |
|
script_data.spawn_tick = game.tick + addition |
|
end |
|
|
|
function check_spawn_units(tick) |
|
if not script_data.spawn_tick then return end |
|
|
|
if script_data.end_spawn_tick <= tick then |
|
wave_end() |
|
return |
|
end |
|
|
|
if script_data.spawn_tick == tick then |
|
spawn_units() |
|
make_next_spawn_tick() |
|
end |
|
end |
|
|
|
function get_wave_spawners() |
|
local spawners = script_data.spawner_distances |
|
local wave_spawners = {} |
|
local count = math.min(#spawners, math.ceil(script_data.random(5, 15) * math.log(1 + script_data.day_number))) |
|
for k = count, 1, -1 do |
|
local spawn = spawners[k] |
|
if (spawn and spawn.entity.valid) then |
|
insert(wave_spawners, spawn.entity) |
|
else |
|
table.remove(spawners, k) |
|
end |
|
end |
|
return wave_spawners |
|
end |
|
|
|
local get_wave_power = function() |
|
return power_functions[script_data.difficulty.wave_power_function or "default"](script_data.day_number) |
|
end |
|
|
|
function get_wave_units() |
|
local wave = script_data.day_number |
|
local prices = script_data.difficulty.unit_prices |
|
local units = {} |
|
for name, unit_wave in pairs (script_data.difficulty.unit_waves) do |
|
if wave >= unit_wave[1] then |
|
if not unit_wave[2] or wave <= unit_wave[2] then |
|
insert(units, {name = name, amount = floor(((wave - unit_wave[1]) + 1) ^ 1.25), price = prices[name]}) |
|
end |
|
end |
|
end |
|
return units |
|
end |
|
|
|
local get_speed_multiplier = function() |
|
local level = script_data.day_number |
|
if level == 0 then return 0.8 end |
|
return speed_multiplier_functions[script_data.difficulty.speed_multiplier_function or "default"](level) |
|
end |
|
|
|
function select_unit(units, power) |
|
|
|
local roll_max = 1 |
|
local available = {} |
|
for k, unit in pairs (units) do |
|
if unit.price <= power then |
|
insert(available, unit) |
|
roll_max = roll_max + unit.amount |
|
end |
|
end |
|
|
|
local roll_value = script_data.random(roll_max) |
|
for k, unit in pairs (available) do |
|
roll_value = roll_value - unit.amount |
|
if (roll_value < 0) then |
|
return unit |
|
end |
|
end |
|
|
|
end |
|
|
|
local random_base_position = function() |
|
local random = script_data.random |
|
local position = get_starting_point() |
|
local radius = get_base_radius() |
|
position.x = position.x + random(-radius, radius) |
|
position.y = position.y + random(-radius, radius) |
|
return position |
|
end |
|
|
|
|
|
local group_path_flags = |
|
{ |
|
cache = false, |
|
low_priority = false, |
|
no_break = true |
|
} |
|
|
|
function spawn_units() |
|
local random = script_data.random |
|
local surface = script_data.surface |
|
local silo = script_data.silo |
|
if not (silo and silo.valid) then return end |
|
local command = |
|
{ |
|
type = defines.command.compound, |
|
structure_type = defines.compound_command.return_last, |
|
commands = |
|
{ |
|
{ |
|
type = defines.command.go_to_location, |
|
destination = random_base_position(), |
|
distraction = defines.distraction.by_anything, |
|
radius = 16, |
|
pathfind_flags = group_path_flags |
|
}, |
|
{ |
|
type = defines.command.go_to_location, |
|
destination_entity = silo, |
|
distraction = defines.distraction.by_enemy, |
|
radius = get_base_radius() / 2, |
|
pathfind_flags = group_path_flags |
|
}, |
|
{ |
|
type = defines.command.attack, |
|
target = silo, |
|
distraction = defines.distraction.by_damage |
|
} |
|
} |
|
} |
|
local power = get_wave_power() |
|
local some_spawns = get_wave_spawners() |
|
local spawns_count = #some_spawns |
|
|
|
if spawns_count == 0 then |
|
return |
|
end |
|
|
|
local units = get_wave_units() |
|
local units_length = #units |
|
local find_non_colliding_position = surface.find_non_colliding_position |
|
local create_entity = surface.create_entity |
|
local entities = game.entity_prototypes |
|
local speed_multiplier = get_speed_multiplier() |
|
|
|
local get_spawn_position = function(spawn_position, unit) |
|
local origin = {spawn_position[1] + random(-8, 8), spawn_position[2] + random(-8, 8)} |
|
local position = find_non_colliding_position(unit.name, origin, 0, 1) |
|
return position |
|
end |
|
|
|
local power_per_spawner = power / spawns_count |
|
for k, spawner in pairs (some_spawns) do |
|
|
|
local spawner_power = power_per_spawner |
|
|
|
local spawn_position = {spawner.position.x + random(-16, 16), spawner.position.y + random(-16, 16)} |
|
|
|
local group = surface.create_unit_group{position = spawn_position, force = spawner.force} |
|
|
|
for k, unit in pairs (spawner.units) do |
|
unit.release_from_spawner() |
|
unit.speed = unit.prototype.speed * speed_multiplier |
|
group.add_member(unit) |
|
end |
|
|
|
while true do |
|
local unit = select_unit(units, spawner_power) |
|
if not unit then break end |
|
spawner_power = spawner_power - unit.price |
|
local entity = create_entity{name = unit.name, position = get_spawn_position(spawn_position, unit)} |
|
entity.speed = entity.prototype.speed * speed_multiplier |
|
group.add_member(entity) |
|
if spawner_power <= 0 then break end |
|
end |
|
|
|
group.set_command(command) |
|
|
|
end |
|
|
|
end |
|
|
|
function make_next_wave_tick() |
|
script_data.end_spawn_tick = game.tick + script_data.spawn_time |
|
script_data.wave_tick = game.tick + script_data.wave_time |
|
end |
|
|
|
function time_to_next_wave() |
|
if not script_data.wave_tick then return end |
|
return format_time(script_data.wave_tick - game.tick) |
|
end |
|
|
|
function time_to_wave_end() |
|
if not script_data.end_spawn_tick then return end |
|
return format_time(script_data.end_spawn_tick - game.tick) |
|
end |
|
|
|
local lose_sound = {path = "utility/game_lost"} |
|
function rocket_died(event) |
|
if not (script_data.silo and script_data.silo.valid) then return end |
|
local silo = event.entity |
|
if silo ~= script_data.silo then |
|
return |
|
end |
|
script_data.state = game_state.defeat |
|
script_data.silo = nil |
|
set_up_players() |
|
game.play_sound(lose_sound) |
|
game.print({"you-lose", script_data.day_number}) |
|
end |
|
|
|
local insert_items = util.insert_safe |
|
|
|
give_respawn_equipment = function(player) |
|
if not is_player_force(player.force) then return end |
|
local equipment = script_data.difficulty.respawn_items |
|
local items = game.item_prototypes |
|
local list = {items = {}, armor = false, equipment = {}} |
|
for name, count in pairs (equipment) do |
|
local item = items[name] |
|
if item then |
|
if item.type == "armor" then |
|
local count = count |
|
if not list.armor then |
|
list.armor = item |
|
end |
|
count = count - 1 |
|
if count > 0 then |
|
list.items[item] = (list.items[item] or 0) + count |
|
end |
|
elseif item.place_as_equipment_result then |
|
list.equipment[item] = (list.equipment[item] or 0) + count |
|
else |
|
list.items[item] = (list.items[item] or 0) + count |
|
end |
|
else |
|
equipment[name] = nil |
|
end |
|
end |
|
local put_equipment = false |
|
if list.armor then |
|
local stack = player.get_inventory(defines.inventory.character_armor)[1] |
|
stack.set_stack{name = list.armor.name} |
|
local grid = stack.grid |
|
if grid then |
|
put_equipment = true |
|
for prototype, count in pairs (list.equipment) do |
|
local equipment = prototype.place_as_equipment_result |
|
for k = 1, count do |
|
local equipment = grid.put{name = equipment.name} |
|
if equipment then |
|
equipment.energy = equipment.max_energy |
|
else |
|
player.insert{name = prototype.name} |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
if not put_equipment then |
|
for prototype, count in pairs (list.equipment) do |
|
player.insert{name = prototype.name, count = count} |
|
end |
|
end |
|
|
|
for prototype, count in pairs (list.items) do |
|
player.insert{name = prototype.name, count = count} |
|
end |
|
end |
|
|
|
function refresh_preview_gui(player) |
|
local frame = script_data.gui_elements.preview_frame[player.index] |
|
if not (frame and frame.valid) then return end |
|
deregister_gui(frame) |
|
frame.clear() |
|
|
|
local admin = player.admin |
|
local inner = frame.add{type = "frame", style = "inside_deep_frame", direction = "vertical"}.add{type = "flow", direction = "vertical"} |
|
inner.style.vertical_spacing = 0 |
|
local subheader = inner.add{type = "frame", style = "subheader_frame"} |
|
local surface = script_data.surface |
|
if not (surface and surface.valid) then return end |
|
subheader.style.horizontally_stretchable = true |
|
local label = subheader.add{type = "label", caption = {"gui-map-generator.difficulty"}, style = "subheader_caption_label"} |
|
|
|
label.style.vertical_align = "center" |
|
label.style.right_padding = 4 |
|
if admin then |
|
local config = subheader.add{type = "drop-down"} |
|
local count = 1 |
|
local index |
|
for name, difficulty in pairs (script_data.config.difficulties) do |
|
config.add_item{name} |
|
if difficulty == script_data.difficulty then |
|
index = count |
|
end |
|
count = count + 1 |
|
end |
|
config.selected_index = index |
|
register_gui_action(config, {type = "difficulty_changed"}) |
|
else |
|
local key |
|
for k, value in pairs (script_data.config.difficulties) do |
|
if value == script_data.difficulty then key = k break end |
|
end |
|
subheader.add{type = "label", caption = {key}, style = "caption_label"} |
|
end |
|
|
|
local line = subheader.add{type = "line", direction = "vertical"} |
|
|
|
local infinite_checkbox = subheader.add{type = "checkbox", state = script_data.config.infinite, caption = {"infinite-map"}, enabled = admin} |
|
register_gui_action(infinite_checkbox, {type = "infinite_checkbox_input"}) |
|
|
|
local pusher = subheader.add{type = "flow"} |
|
pusher.style.horizontally_stretchable = true |
|
local seed_flow = subheader.add{type = "flow", direction = "horizontal", style = "player_input_horizontal_flow"} |
|
seed_flow.add{type = "label", style = "caption_label", caption = {"gui-map-generator.map-seed"}} |
|
if admin then |
|
local seed_input = seed_flow.add |
|
{ |
|
type = "textfield", text = surface.map_gen_settings.seed, style = "long_number_textfield", |
|
numeric = true, allow_decimal = false, allow_negative = false |
|
} |
|
register_gui_action(seed_input, {type = "check_seed_input"}) |
|
local shuffle_button = seed_flow.add{type = "sprite-button", sprite = "utility/shuffle", style = "tool_button"} |
|
register_gui_action(shuffle_button, {type = "shuffle_button"}) |
|
else |
|
seed_flow.add{type = "label", style = "caption_label", caption = surface.map_gen_settings.seed} |
|
end |
|
local size = get_preview_size() |
|
local max = math.min(size * 2, player.display_resolution.width * 0.8 / player.display_scale, player.display_resolution.height * 0.8 / player.display_scale) |
|
local zoom = max / (size * 2) |
|
local position = player.force.get_spawn_position(surface) |
|
local minimap = inner.add |
|
{ |
|
type = "minimap", |
|
surface_index = surface.index, |
|
zoom = zoom, |
|
force = player.force.name, |
|
position = position |
|
} |
|
minimap.style.natural_width = max |
|
minimap.style.natural_height = max |
|
|
|
local button_flow = frame.add{type = "flow"} |
|
button_flow.style.horizontally_stretchable = true |
|
button_flow.style.vertical_align = "center" |
|
button_flow.style.top_padding = 4 |
|
local pusher = button_flow.add{type = "empty-widget", style = "draggable_space_header"} |
|
pusher.style.vertically_stretchable = true |
|
pusher.style.horizontally_stretchable = true |
|
pusher.drag_target = frame |
|
local start_round = button_flow.add{type = "button", caption = {"start-round"}, style = "confirm_button", enabled = admin} |
|
start_round.style.natural_width = max / 3 |
|
register_gui_action(start_round, {type = "start_round"}) |
|
end |
|
|
|
local setup_frame = {type = "frame", caption = {"setup-frame"}, direction = "vertical"} |
|
|
|
function make_preview_gui(player) |
|
local gui = player.gui.screen |
|
local frame = script_data.gui_elements.preview_frame[player.index] |
|
if not (frame and frame.valid) then |
|
frame = gui.add(setup_frame) |
|
frame.auto_center = true |
|
frame.style.horizontal_align = "right" |
|
frame.style.maximal_height = player.display_resolution.height / player.display_scale |
|
frame.style.vertically_stretchable = true |
|
script_data.gui_elements.preview_frame[player.index] = frame |
|
end |
|
refresh_preview_gui(player) |
|
end |
|
|
|
local day_button_param = |
|
{ |
|
type = "button", |
|
ignored_by_interaction = true, |
|
style = mod_gui.button_style |
|
} |
|
|
|
local upgrade_button_param = |
|
{ |
|
type = "button", |
|
caption = {"upgrade-button"}, |
|
tooltip = {"upgrade-button-tooltip"}, |
|
style = mod_gui.button_style |
|
} |
|
|
|
local admin_button_param = |
|
{ |
|
type = "button", |
|
caption = {"admin"}, |
|
style = mod_gui.button_style |
|
} |
|
|
|
local add_admin_buttons = function(player) |
|
|
|
if not player.admin then return end |
|
|
|
local button_flow = mod_gui.get_button_flow(player) |
|
local admin_button = button_flow.add(admin_button_param) |
|
script_data.gui_elements.admin_frame_button[player.index] = admin_button |
|
register_gui_action(admin_button, {type = "admin_button"}) |
|
end |
|
|
|
local add_gui_buttons= function(player) |
|
|
|
if not is_player_force(player.force) then return end |
|
|
|
local button_flow = mod_gui.get_button_flow(player) |
|
|
|
local day_button = script_data.gui_elements.day_button[player.index] |
|
if not day_button then |
|
day_button = button_flow.add(day_button_param) |
|
script_data.gui_elements.day_button[player.index] = day_button |
|
end |
|
day_button.caption = {"current-day", script_data.day_number} |
|
insert(script_data.gui_labels.day_label, day_button) |
|
|
|
local upgrade_button = script_data.gui_elements.upgrade_frame_button[player.index] |
|
if not upgrade_button then |
|
upgrade_button = button_flow.add(upgrade_button_param) |
|
script_data.gui_elements.upgrade_frame_button[player.index] = upgrade_button |
|
register_gui_action(upgrade_button, {type = "upgrade_button"}) |
|
end |
|
end |
|
|
|
local delete_game_gui = function(player) |
|
local index = player.index |
|
for k, gui_list in pairs(script_data.gui_elements) do |
|
local element = gui_list[index] |
|
if (element and element.valid) then |
|
deregister_gui(element) |
|
element.destroy() |
|
end |
|
gui_list[index] = nil |
|
end |
|
end |
|
|
|
function gui_init(player) |
|
|
|
delete_game_gui(player) |
|
|
|
if script_data.state == game_state.in_preview then |
|
make_preview_gui(player) |
|
return |
|
end |
|
|
|
if script_data.state == game_state.in_round then |
|
add_gui_buttons(player) |
|
add_admin_buttons(player) |
|
return |
|
end |
|
|
|
if script_data.state == game_state.defeat or script_data.state == game_state.victory then |
|
add_admin_buttons(player) |
|
return |
|
end |
|
|
|
end |
|
|
|
local cash_font_color = {r = 0.8, b = 0.5, g = 0.8} |
|
|
|
local upgrade_frame = {type = "frame", style = mod_gui.frame_style, caption = {"buy-upgrades"}, direction = "vertical"} |
|
function toggle_upgrade_frame(player) |
|
|
|
local frame = script_data.gui_elements.upgrade_frame[player.index] |
|
if frame and frame.valid then |
|
deregister_gui(frame) |
|
frame.destroy() |
|
script_data.gui_elements.upgrade_frame[player.index] = nil |
|
return |
|
end |
|
|
|
frame = mod_gui.get_frame_flow(player).add(upgrade_frame) |
|
script_data.gui_elements.upgrade_frame[player.index] = frame |
|
frame.visible = true |
|
|
|
inner = frame.add{type = "frame", style = "inside_deep_frame", direction = "vertical"} |
|
local subheader = inner.add{type = "frame", style = "subheader_frame"} |
|
subheader.style.horizontally_stretchable = "true" |
|
local label = subheader.add{type = "label", caption = {"force-money"}, style = "subheader_label"} |
|
label.style.font = "default-semibold" |
|
local cash = subheader.add{type = "label", caption = get_money()} |
|
insert(script_data.gui_labels.money_label, cash) |
|
cash.style.font_color = {r = 0.8, b = 0.5, g = 0.8} |
|
local scroll = inner.add{type = "scroll-pane", style = "scroll_pane_with_dark_background_under_subheader"} |
|
scroll.style.padding = 0 |
|
scroll.style.maximal_height = (player.display_resolution.height * 0.5) / player.display_scale |
|
local upgrade_table = scroll.add{type = "table", column_count = 2} |
|
upgrade_table.style.horizontal_spacing = 0 |
|
upgrade_table.style.vertical_spacing = 0 |
|
script_data.gui_elements.upgrade_table[player.index] = upgrade_table |
|
update_upgrade_listing(player) |
|
end |
|
|
|
function update_upgrade_listing(player) |
|
local gui = script_data.gui_elements.upgrade_table[player.index] |
|
if not (gui and gui.valid) then return end |
|
local upgrades = script_data.team_upgrades |
|
deregister_gui(gui) |
|
gui.clear() |
|
for name, upgrade in pairs (get_upgrades()) do |
|
local level = upgrades[name] or 0 |
|
local sprite = gui.add{type = "sprite-button", name = name, sprite = upgrade.sprite, tooltip = {"purchase"}, style = "slot_sized_button"} |
|
sprite.style.minimal_height = 64 + 8 |
|
sprite.style.minimal_width = 64 + 8 |
|
sprite.style.margin = -1 |
|
sprite.number = upgrade.price(level) |
|
register_gui_action(sprite, {type = "purchase_button", name = name}) |
|
local flow = gui.add{type = "frame", name = name.."_flow", direction = "vertical", style = "subpanel_frame"} |
|
flow.style.horizontally_stretchable = true |
|
flow.style.vertically_stretchable = true |
|
local label = flow.add{type = "label", name = name.."_name", caption = {"", upgrade.caption, " "..upgrade.modifier}} |
|
label.style.font = "default-bold" |
|
local level = flow.add{type = "label", name = name.."_level", caption = {"upgrade-level", level}} |
|
end |
|
end |
|
|
|
function get_upgrades() |
|
return upgrades |
|
end |
|
|
|
function get_money() |
|
return format_number(script_data.money) |
|
end |
|
|
|
function update_label_list(list, caption) |
|
for k, label in pairs (list) do |
|
if label.valid then |
|
label.caption = caption |
|
else |
|
list[k] = nil |
|
end |
|
end |
|
end |
|
|
|
local admin_frame_param = |
|
{ |
|
type = "frame", |
|
style = mod_gui.frame_style, |
|
caption = {"admin"}, |
|
direction = "vertical" |
|
} |
|
|
|
local admin_buttons = |
|
{ |
|
{ |
|
param = {type = "button", caption = {"end-round"}}, |
|
action = {type = "end_round"} |
|
}, |
|
{ |
|
param = {type = "button", caption = {"restart-round"}}, |
|
action = {type = "restart_round"} |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
local toggle_admin_frame = function(player) |
|
if not (player and player.valid) then return end |
|
local frame = script_data.gui_elements.admin_frame[player.index] |
|
if (frame and frame.valid) then |
|
deregister_gui(frame) |
|
frame.destroy() |
|
script_data.gui_elements.admin_frame[player.index] = nil |
|
return |
|
end |
|
local gui = mod_gui.get_frame_flow(player) |
|
frame = gui.add(admin_frame_param) |
|
frame.style.vertically_stretchable = false |
|
frame.style.horizontally_stretchable = false |
|
script_data.gui_elements.admin_frame[player.index] = frame |
|
local inner = frame.add{type = "frame", direction = "vertical", style = "window_content_frame_deep"} |
|
for k, button in pairs (admin_buttons) do |
|
local butt = inner.add(button.param) |
|
butt.style.horizontally_stretchable = true |
|
register_gui_action(butt, button.action) |
|
end |
|
|
|
end |
|
|
|
local techs_to_disable = |
|
{ |
|
"physical-projectile-damage", |
|
"stronger-explosives", |
|
"refined-flammables", |
|
"energy-weapons-damage", |
|
"weapon-shooting-speed", |
|
"laser-shooting-speed", |
|
"follower-robot-count", |
|
"mining-productivity" |
|
} |
|
|
|
function set_research(force) |
|
force.research_all_technologies() |
|
local tech = force.technologies |
|
for k, name in pairs (techs_to_disable) do |
|
for i = 1, 20 do |
|
local full_name = name.."-"..i |
|
if tech[full_name] then |
|
tech[full_name].researched = false |
|
end |
|
end |
|
end |
|
force.reset_technology_effects() |
|
end |
|
|
|
function set_recipes(force) |
|
local recipes = force.recipes |
|
local disable = |
|
{ |
|
"automation-science-pack", |
|
"logistic-science-pack", |
|
"chemical-science-pack", |
|
"military-science-pack", |
|
"production-science-pack", |
|
"utility-science-pack", |
|
"lab" |
|
} |
|
|
|
for k, name in pairs (disable) do |
|
if recipes[name] then |
|
recipes[name].enabled = false |
|
else |
|
error(name.." is not a valid recipe") |
|
end |
|
end |
|
end |
|
|
|
local init_map_settings = function() |
|
local settings = game.map_settings |
|
|
|
settings.pollution.enabled = false |
|
settings.enemy_expansion.enabled = false |
|
|
|
|
|
|
|
|
|
|
|
settings.path_finder.use_path_cache = false |
|
|
|
|
|
|
|
settings.path_finder.general_entity_collision_penalty = 1 |
|
settings.path_finder.general_entity_subsequent_collision_penalty = 1 |
|
|
|
settings.path_finder.max_steps_worked_per_tick = 1000 |
|
settings.path_finder.max_clients_to_accept_any_new_request = 5000 |
|
settings.path_finder.ignore_moving_enemy_collision_distance = 0 |
|
settings.short_request_max_steps = 1000000 |
|
settings.short_request_ratio = 1 |
|
settings.max_failed_behavior_count = 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
settings.unit_group= |
|
{ |
|
|
|
min_group_gathering_time = 3600, |
|
max_group_gathering_time = 10 * 3600, |
|
|
|
|
|
max_wait_time_for_late_members = 2 * 3600, |
|
|
|
max_group_radius = 50.0, |
|
min_group_radius = 5.0, |
|
|
|
max_member_speedup_when_behind = 2, |
|
|
|
max_member_slowdown_when_ahead = 0.9, |
|
|
|
max_group_slowdown_factor = 0.9, |
|
|
|
max_group_member_fallback_factor = 2, |
|
|
|
member_disown_distance = 50, |
|
tick_tolerance_when_member_arrives = 60, |
|
|
|
|
|
max_gathering_unit_groups = 30, |
|
|
|
|
|
|
|
max_unit_group_size = 200 |
|
} |
|
end |
|
|
|
local on_init = function() |
|
init_map_settings() |
|
game.forces.player.disable_research() |
|
game.surfaces.nauvis.always_day = true |
|
end |
|
|
|
local spawner_died = function(event) |
|
local spawner = event.entity |
|
if not (spawner and spawner.valid) then return end |
|
script_data.spawners[spawner.unit_number] = nil |
|
end |
|
|
|
local bounty_color = {r = 0.2, g = 0.8, b = 0.2, a = 0.2} |
|
local on_entity_died = function(event) |
|
if script_data.state ~= game_state.in_round then return end |
|
|
|
local died = event.entity |
|
if not (died and died.valid) then return end |
|
|
|
local bounty = script_data.difficulty.bounties[died.name] |
|
if bounty and is_player_force(event.force) then |
|
local cash = floor(bounty * (script_data.difficulty.bounty_modifier or 1)) |
|
increment(script_data, "money", cash) |
|
died.surface.create_entity{name = "flying-text", position = died.position, text = "+"..cash, color = bounty_color} |
|
update_label_list(script_data.gui_labels.money_label, get_money()) |
|
end |
|
|
|
if died.type == "rocket-silo" then |
|
return rocket_died(event) |
|
end |
|
|
|
if died.type == "unit-spawner" then |
|
return spawner_died(event) |
|
end |
|
end |
|
|
|
local on_rocket_launched = function(event) |
|
round_won() |
|
end |
|
|
|
local on_player_joined_game = function(event) |
|
local player = players(event.player_index) |
|
if not (script_data.surface and script_data.surface.valid) then |
|
create_battle_surface(initial_seed) |
|
end |
|
set_up_player(player) |
|
end |
|
|
|
local on_player_respawned = function(event) |
|
give_respawn_equipment(players(event.player_index)) |
|
end |
|
|
|
local is_reasonable_seed = function(string) |
|
local number = tonumber(string) |
|
if not number then return end |
|
if number < 0 or number > max_seed then |
|
return |
|
end |
|
return true |
|
end |
|
|
|
local end_round = function(player) |
|
script_data.state = game_state.in_preview |
|
script_data.wave_tick = nil |
|
script_data.spawn_tick = nil |
|
local seed = script_data.surface.map_gen_settings.seed |
|
game.delete_surface(script_data.surface) |
|
create_battle_surface(script_data.surface.map_gen_settings.seed) |
|
set_up_players() |
|
end |
|
|
|
local gui_functions = |
|
{ |
|
upgrade_button = function(event) |
|
toggle_upgrade_frame(players(event.player_index)) |
|
end, |
|
admin_button = function(event) |
|
toggle_admin_frame(players(event.player_index)) |
|
end, |
|
purchase_button = function(event, param) |
|
local name = param.name |
|
local list = get_upgrades() |
|
local upgrades = script_data.team_upgrades |
|
local player = players(event.player_index) |
|
local upgrade = list[name] |
|
if not upgrade then |
|
|
|
toggle_upgrade_frame(player) |
|
return |
|
end |
|
local price = upgrade.price(upgrades[name]) |
|
|
|
if script_data.money < price then |
|
player.print({"not-enough-money"}) |
|
return |
|
end |
|
|
|
increment(script_data, "money", -price) |
|
for k, effect in pairs (upgrade.effect) do |
|
effect(player.force) |
|
end |
|
|
|
increment(script_data.team_upgrades, name) |
|
player.force.print({"purchased-team-upgrade", player.name, upgrade.caption, upgrades[name]}) |
|
for k, player in pairs (game.connected_players) do |
|
update_upgrade_listing(player) |
|
end |
|
update_label_list(script_data.gui_labels.money_label, get_money()) |
|
|
|
end, |
|
shuffle_button = function(event, param) |
|
create_battle_surface() |
|
end, |
|
check_seed_input = function(event, param) |
|
local gui = event.element |
|
if not (gui and gui.valid) then return end |
|
if not is_reasonable_seed(gui.text) then |
|
return |
|
end |
|
gui.style = "long_number_textfield" |
|
if event.name == defines.events.on_gui_confirmed then |
|
create_battle_surface(tonumber(gui.text)) |
|
end |
|
end, |
|
infinite_checkbox_input = function(event, param) |
|
local gui = event.element |
|
if not (gui and gui.valid) then return end |
|
script_data.config.infinite = gui.state |
|
create_battle_surface(script_data.surface.map_gen_settings.seed) |
|
end, |
|
start_round = function(event, param) |
|
start_round() |
|
end, |
|
send_wave = function(event, param) |
|
spawn_units() |
|
end, |
|
end_round = function(event, param) |
|
end_round() |
|
end, |
|
restart_round = function(event, param) |
|
restart_round() |
|
end, |
|
difficulty_changed = function(event, param) |
|
local gui = event.element |
|
if not (gui and gui.valid) then return end |
|
if not (event.name == defines.events.on_gui_selection_state_changed) then return end |
|
local selected = gui.selected_index |
|
local index = 1 |
|
for name, difficulty in pairs (script_data.config.difficulties) do |
|
if index == selected then |
|
script_data.difficulty = difficulty |
|
break |
|
end |
|
index = index + 1 |
|
end |
|
create_battle_surface(script_data.surface.map_gen_settings.seed) |
|
end |
|
} |
|
|
|
function generic_gui_event(event) |
|
local gui = event.element |
|
if not (gui and gui.valid) then return end |
|
|
|
local player_gui_actions = script_data.gui_actions[gui.player_index] |
|
if not player_gui_actions then return end |
|
|
|
local action = player_gui_actions[gui.index] |
|
if not action then return end |
|
|
|
gui_functions[action.type](event, action) |
|
end |
|
|
|
local chart_base_area = function() |
|
if script_data.state ~= game_state.in_round then return end |
|
local surface = script_data.surface |
|
if not (surface and surface.valid) then return end |
|
local force = game.forces.player |
|
local origin = force.get_spawn_position(surface) |
|
local size = get_base_radius() |
|
force.chart(surface, |
|
{ |
|
{ |
|
origin.x - (size + 32), |
|
origin.y - (size + 32) |
|
}, |
|
{ |
|
origin.x + size, |
|
origin.y + size |
|
} |
|
}) |
|
end |
|
|
|
local collision_mask = {"colliding-with-tiles-only", "water-tile"} |
|
local bounding_box = {{0,0},{0,0}} |
|
local flags = |
|
{ |
|
cache = false, |
|
low_priority = false, |
|
no_break = true |
|
} |
|
|
|
local max_pending_paths = 15 |
|
local process_path_queue = function() |
|
|
|
local queue = script_data.path_request_queue |
|
if not queue then return end |
|
|
|
local requests = script_data.spawner_path_requests |
|
local current_count = table_size(requests) |
|
|
|
for k = 1, (max_pending_paths - current_count) do |
|
|
|
local unit_number, spawner = next(queue) |
|
|
|
if not unit_number then |
|
return |
|
end |
|
|
|
queue[unit_number] = nil |
|
|
|
if not (spawner and spawner.valid) then |
|
return |
|
end |
|
|
|
local key = spawner.surface.request_path |
|
{ |
|
bounding_box = bounding_box, |
|
collision_mask = collision_mask, |
|
start = spawner.position, |
|
goal = get_starting_point(), |
|
radius = get_base_radius(), |
|
force = spawner.force, |
|
path_resolution_modifier = -1, |
|
pathfind_flags = flags |
|
} |
|
|
|
requests[key] = spawner |
|
|
|
end |
|
|
|
end |
|
|
|
local on_tick = function(event) |
|
local tick = event.tick |
|
|
|
if script_data.state == game_state.in_round then |
|
check_next_wave(tick) |
|
check_spawn_units(tick) |
|
check_dawn(tick) |
|
process_path_queue() |
|
return |
|
end |
|
|
|
if script_data.state == game_state.in_preview then |
|
if script_data.surface and script_data.surface.valid then |
|
script_data.surface.force_generate_chunk_requests() |
|
end |
|
end |
|
|
|
end |
|
|
|
local oh_no_you_dont = {game_finished = false} |
|
|
|
local on_player_died = function(event) |
|
if not game.is_multiplayer() then |
|
game.set_game_state(oh_no_you_dont) |
|
end |
|
end |
|
|
|
local request_path_for_spawner = function(spawner) |
|
|
|
if not (spawner and spawner.valid) then return end |
|
|
|
local unit_number = spawner.unit_number |
|
|
|
if script_data.spawners[unit_number] then |
|
|
|
return |
|
end |
|
|
|
script_data.path_request_queue[unit_number] = spawner |
|
|
|
end |
|
|
|
local on_chunk_generated = function(event) |
|
local surface = event.surface |
|
if not (surface and surface.valid and surface == script_data.surface) then return end |
|
|
|
for k, spawner in pairs (surface.find_entities_filtered{area = event.area, type = "unit-spawner"}) do |
|
request_path_for_spawner(spawner) |
|
end |
|
|
|
end |
|
|
|
local refresh_player_gui_event = function(event) |
|
return gui_init(players(event.player_index)) |
|
end |
|
|
|
local add_remote_interface = function() |
|
remote.add_interface("wave_defense", |
|
{ |
|
set_config = function(data) |
|
if type(data) ~= "table" then |
|
error("Data type for 'set_config' must be a table") |
|
end |
|
log("Wave defense config set by remote call, can expect script errors after this point.") |
|
script_data.config = data |
|
end, |
|
get_config = function() |
|
return script_data.config |
|
end, |
|
get_events = function() |
|
return script_events |
|
end |
|
}) |
|
end |
|
|
|
local on_script_path_request_finished = function(event) |
|
local id = event.id |
|
local spawner = script_data.spawner_path_requests[id] |
|
if not (spawner and spawner.valid) then return end |
|
|
|
script_data.spawner_path_requests[id] = nil |
|
|
|
if event.try_again_later then |
|
request_path_for_spawner(spawner) |
|
return |
|
end |
|
|
|
if not event.path then |
|
|
|
return |
|
end |
|
|
|
script_data.spawners[spawner.unit_number] = spawner |
|
|
|
local path = event.path |
|
local distance = #path |
|
local spawners = script_data.spawner_distances |
|
local inserted = false |
|
|
|
for k, other_spawner in pairs (spawners) do |
|
if distance < other_spawner.distance then |
|
insert(spawners, k, {entity = spawner, distance = distance}) |
|
inserted = true |
|
break |
|
end |
|
end |
|
|
|
if not inserted then |
|
insert(spawners, {entity = spawner, distance = distance}) |
|
end |
|
|
|
end |
|
|
|
local on_ai_command_completed = function(event) |
|
|
|
local unit = script_data.units[event.unit_number] |
|
local silo = script_data.silo |
|
if not (silo and silo.valid) then return end |
|
if unit and unit.valid then |
|
unit.ai_settings.path_resolution_modifier = math.min(0, unit.ai_settings.path_resolution_modifier + 1) |
|
unit.set_command |
|
{ |
|
type = defines.command.attack, |
|
target = silo, |
|
distraction = defines.distraction.by_damage |
|
} |
|
end |
|
end |
|
|
|
local on_pre_player_died = function(event) |
|
|
|
|
|
|
|
local player = game.get_player(event.player_index) |
|
if not (player and player.valid) then return end |
|
if not is_player_force(player.force) then return end |
|
local remove_item = player.remove_item |
|
for name, count in pairs (script_data.difficulty.respawn_items) do |
|
remove_item{name = name, count = count} |
|
end |
|
end |
|
|
|
local on_player_changed_force = function(event) |
|
local player = players(event.player_index) |
|
set_up_player(player) |
|
end |
|
|
|
local on_technology_effects_reset = function(event) |
|
local force = event.force |
|
if force.name ~= "player" then return end |
|
|
|
local upgrades = script_data.team_upgrades |
|
|
|
for name, upgrade in pairs (get_upgrades()) do |
|
local count = upgrades[name] or 0 |
|
if count > 1 then |
|
for k, effect in pairs (upgrade.effect) do |
|
for j = 1, count do |
|
effect(force) |
|
end |
|
end |
|
end |
|
end |
|
|
|
end |
|
|
|
local lib = {} |
|
|
|
lib.events = |
|
{ |
|
[defines.events.on_chunk_generated] = on_chunk_generated, |
|
[defines.events.on_entity_died] = on_entity_died, |
|
|
|
[defines.events.on_gui_click] = generic_gui_event, |
|
[defines.events.on_gui_selection_state_changed] = generic_gui_event, |
|
[defines.events.on_gui_text_changed] = generic_gui_event, |
|
[defines.events.on_gui_confirmed] = generic_gui_event, |
|
[defines.events.on_gui_checked_state_changed] = generic_gui_event, |
|
|
|
[defines.events.on_player_died] = on_player_died, |
|
[defines.events.on_pre_player_died] = on_pre_player_died, |
|
|
|
[defines.events.on_player_demoted] = refresh_player_gui_event, |
|
[defines.events.on_player_display_resolution_changed] = refresh_player_gui_event, |
|
[defines.events.on_player_display_scale_changed] = refresh_player_gui_event, |
|
[defines.events.on_player_promoted] = refresh_player_gui_event, |
|
|
|
[defines.events.on_player_joined_game] = on_player_joined_game, |
|
[defines.events.on_player_changed_force] = on_player_changed_force, |
|
[defines.events.on_player_respawned] = on_player_respawned, |
|
[defines.events.on_rocket_launched] = on_rocket_launched, |
|
[defines.events.on_script_path_request_finished] = on_script_path_request_finished, |
|
|
|
[defines.events.on_tick] = on_tick, |
|
|
|
[defines.events.on_technology_effects_reset] = on_technology_effects_reset |
|
|
|
} |
|
|
|
lib.on_nth_tick = |
|
{ |
|
[13] = chart_base_area |
|
} |
|
|
|
lib.on_event = function(event) |
|
local action = events[event.name] |
|
if not action then return end |
|
return action(event) |
|
end |
|
|
|
lib.on_load = function() |
|
script_data = global.wave_defense or script_data |
|
add_remote_interface() |
|
end |
|
|
|
lib.on_init = function() |
|
global.wave_defense = global.wave_defense or script_data |
|
on_init() |
|
add_remote_interface() |
|
end |
|
|
|
lib.on_configuration_changed = function(data) |
|
game.forces.player.reset_technology_effects() |
|
for name, upgrade in pairs (get_upgrades()) do |
|
script_data.team_upgrades[name] = script_data.team_upgrades[name] or 0 |
|
end |
|
|
|
for k, player in pairs (game.players) do |
|
update_upgrade_listing(player) |
|
end |
|
|
|
init_map_settings() |
|
set_recipes(game.forces.player) |
|
game.forces.player.disable_research() |
|
|
|
if script_data.surface and script_data.surface.valid then |
|
script_data.path_request_queue = {} |
|
script_data.spawner_path_requests = {} |
|
for k, spawner in pairs (script_data.surface.find_entities_filtered{type = "unit-spawner"}) do |
|
request_path_for_spawner(spawner) |
|
end |
|
end |
|
|
|
if type(script_data.difficulty.wave_power_function) ~= "string" then |
|
script_data.difficulty.wave_power_function = "default" |
|
end |
|
|
|
if type(script_data.difficulty.speed_multiplier_function) ~= "string" then |
|
script_data.difficulty.speed_multiplier_function = "default" |
|
end |
|
|
|
end |
|
|
|
return lib |
|
|