| 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 |
|
|