local mod_gui = require("mod-gui") local util = require("util") local balance = require("balance") local config = require("config") local production_score = require("production-score") local kill_score = require("kill-score") local insert = table.insert local script_data = { gui_actions = {}, team_players = {}, elements = { config = {}, balance = {}, import = {}, admin = {}, admin_button = {}, spectate_button = {}, join = {}, progress_bar = {}, team_frame = {}, team_list_button = {}, production_score_frame = {}, production_score_inner_frame = {}, recipe_frame = {}, recipe_button = {}, inventory = {}, space_race_frame = {}, kill_score_frame = {}, oil_harvest_frame = {} }, timers = {}, setup_finished = false, ready_players = {}, config = {}, round_number = 0, selected_recipe = {} } local statistics_period = 150 -- Seconds local events = { on_round_end = script.generate_event_name(), on_round_start = script.generate_event_name(), on_team_lost = script.generate_event_name(), on_team_won = script.generate_event_name(), on_player_joined_team = script.generate_event_name() } get_starting_area_radius = function(as_tiles) local surface = game.surfaces[1] local radius = math.ceil(surface.get_starting_area_radius() / 32) if as_tiles then return radius * 32 end return radius end function set_config_visibility(player) local index = player.index local elements = script_data.elements local config = elements.config[index] if not (config and config.valid) then return end local visible = true for k, name in pairs({"balance", "import", "inventory"}) do local guis = elements[name] local blocking_gui = guis[index] if blocking_gui and blocking_gui.valid and blocking_gui.visible then visible = false break end end config.visible = visible 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_spawn_positions() local settings = game.surfaces[1].map_gen_settings local width = settings.width local height = settings.height local height_scale = height / width local radius = get_starting_area_radius() local diameter = radius * 32 * 2 * math.sqrt(2) local count = #script_data.config.teams local angle = math.pi / count local hypotenuse = math.abs(math.sin(angle) * diameter) if hypotenuse < 0.01 then hypotenuse = diameter end --local min_distance = math.ceil((radius - 1) * 32) * (#script_data.config.teams) local min_distance = math.ceil(diameter * (diameter/hypotenuse)) local displacement = script_data.config.team_config.average_team_displacement if displacement < min_distance then script_data.config.team_config.average_team_displacement = min_distance displacement = min_distance end local mag = displacement + (radius * 32) local y_scale = 1 if mag > (height / 2) then --circle too tall y_scale = height_scale end local x_scale = 1 if mag > (width / 2) then --circle too wide x_scale = 1 / height_scale end local horizontal_offset = (width > 10000 and (width / displacement) * 10) or 0 local vertical_offset = (height > 10000 and (height / displacement) * 10) or 0 script_data.spawn_offset = {x = math.floor(0.5 + math.random(math.floor(-horizontal_offset), math.floor(horizontal_offset)) / 32) * 32, y = math.floor(0.5 + math.random(math.floor(-vertical_offset), math.floor(vertical_offset)) / 32) * 32} local distance = 0.5 * displacement local positions = {} local rotation_offset = ((count - 1) * math.pi / 4) for k = 1, count do local rotation = rotation_offset + ((k * 2 * math.pi) / count) local X = (math.cos(rotation) * distance * x_scale) / 32 if X > 0 then X = 32 * (math.floor(X)) else X = 32 * (math.ceil(X)) end local Y = (math.sin(rotation) * distance * y_scale) / 32 if Y > 0 then Y = 32 * (math.floor(Y)) else Y = 32 * (math.ceil(Y)) end positions[k] = {x = X + script_data.spawn_offset.x, y = Y + script_data.spawn_offset.y} end script_data.spawn_positions = positions return positions end function create_next_surface() local name = "battle_surface_1" if game.surfaces[name] ~= nil then name = "battle_surface_2" end script_data.round_number = script_data.round_number + 1 local settings = game.surfaces[1].map_gen_settings settings.starting_points = create_spawn_positions() script_data.surface = game.create_surface(name, settings) script_data.surface.always_day = script_data.config.team_config.always_day end function destroy_player_gui(player) local elements = script_data.elements local index = player.index for name, guis in pairs (elements) do local frame = guis[index] if frame and frame.valid then deregister_gui(frame) frame.destroy() end guis[index] = nil end for k, timer in pairs (script_data.timers) do if timer.valid and (timer.player_index == index) then timer.destroy() end end 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 local red = function(str) return "[color=1,0.2,0.2]"..str.."[/color]" end local green = function(str) return "[color=0.2,1,0.2]"..str.."[/color]" end function add_team_to_new_flow(team, flow, current_team, admin) local frame = flow.add{type = "frame", direction = "vertical", style = "bordered_frame"} frame.style.horizontally_stretchable = true local title_flow = frame.add{type = "flow", direction = "horizontal"} local show_flow = title_flow.add{type = "flow", style = "slot_table_spacing_horizontal_flow"} local label = show_flow.add{type = "label", caption = team.name, style = "caption_label"} local pusher = show_flow.add{type = "flow"} pusher.style.horizontally_stretchable = true label.style.font_color = get_color(team, true) if admin then local edit_button = show_flow.add{type = "sprite-button", style = "slot_button", sprite = "utility/rename_icon_small"} edit_button.style.height = 20 edit_button.style.width = 20 local edit_flow = title_flow.add{type = "flow", style = "slot_table_spacing_horizontal_flow", visible = false} edit_flow.style.horizontally_squashable = true local textfield = edit_flow.add{type = "textfield", text = team.name} textfield.style.width = 100 local color_drop = edit_flow.add{type = "drop-down"} local index = 1 for k, color in pairs (script_data.config.colors) do color_drop.add_item({"color."..color.name}) if color.name == team.color then index = k end end color_drop.selected_index = index local pusher = edit_flow.add{type = "flow"} pusher.style.horizontally_stretchable = true local textfield_confirm = edit_flow.add{type = "sprite-button", style = "green_slot_button", sprite = "utility/confirm_slot"} local textfield_cancel = edit_flow.add{type = "sprite-button", style = "not_available_slot_button", sprite = "utility/reset"} register_gui_action(textfield_cancel, {type = "cancel_rename", edit_flow = edit_flow, show_flow = show_flow}) register_gui_action(edit_button, {type = "rename_team", edit_flow = edit_flow, show_flow = show_flow}) local delete_button = show_flow.add{type = "sprite-button", style = "not_available_slot_button", sprite = "utility/trash"} delete_button.style.height = 20 delete_button.style.width = 20 register_gui_action(delete_button, {type = "remove_team", team = team, frame = frame}) register_gui_action(textfield_confirm, {type = "confirm_rename", textfield = textfield, team = team, dropdown = color_drop}) end local team_number = frame.add{type = "flow", direction = "horizontal"} team_number.add{type = "label", caption = {"", {"team"}, {"colon"}}, style = "description_label"} if admin then local button = team_number.add{type = "button", caption = team.team, style = "small_slot_button"} register_gui_action(button, {type = "team_button_press", team = team}) else team_number.add{type = "label", caption = team.team} end local ready = "" local first_ready = true local ready_data = script_data.ready_players local limit = script_data.config.team_config.max_players limit = (limit > 0 and limit) or math.huge local player_count = 0 for k, member in pairs (team.members or {}) do player_count = player_count + 1 if player_count > limit then team.members[k] = nil ready_data[k] = nil script_data.team_players[k] = nil else if first_ready then first_ready = false else ready = ready..", " end if ready_data[k] then ready = ready .. green(member.name) else ready = ready .. red(member.name) end end end if first_ready then ready = {"none"} end local label = frame.add{type = "label", caption = {"members", ready}, style = "description_label"} label.style.single_line = false label.style.maximal_width = 400 local within_limit = player_count < limit if within_limit and (not current_team or current_team ~= team) then local join_team = frame.add{type = "button", caption = {"join-team"}} join_team.style.font = "default" join_team.style.height = 24 join_team.style.top_padding = 0 join_team.style.bottom_padding = 0 register_gui_action(join_team, {type = "join_team", team = team}) end if current_team == team then local leave_team = frame.add{type = "button", caption = {"leave-team"}} leave_team.style.font = "default" leave_team.style.height = 24 leave_team.style.top_padding = 0 leave_team.style.bottom_padding = 0 register_gui_action(leave_team, {type = "leave_team", team = team}) end end refresh_config = function(excluded_player_index) for k, player in pairs (game.connected_players) do if player.index ~= excluded_player_index then create_config_gui(player) end end end refresh_balance = function(excluded_player_index) for k, player in pairs (game.connected_players) do if player.index ~= excluded_player_index then toggle_balance_options_gui(player) toggle_balance_options_gui(player) end end end refresh_inventory = function(excluded_player_index) for k, player in pairs (game.connected_players) do if player.index ~= excluded_player_index then toggle_starting_chest_gui(player) toggle_starting_chest_gui(player) end end end local name_allowed = function(name, team) if name == "" then return false end for k, other_team in pairs (script_data.config.teams) do if other_team.name == name then if other_team ~= team then return false end end end return true end local is_text_valid = function(text, no_below_100) if text == "" then return false end local number = tonumber(text) if not number then return false end if number < 0 then return false end if number > 4294967295 then return false end if no_below_100 then return number >= 100 end return true end local check_all_ready = function() local all_ready = true for k, player in pairs (game.connected_players) do if not script_data.ready_players[player.index] then all_ready = false break end end if all_ready then start_all_ready_preparations() elseif script_data.ready_start_tick then script_data.ready_start_tick = nil game.print({"ready-cancelled"}) end end toggle_starting_chest_gui = function(player) if not (player and player.valid and player.connected) then return end local gui = player.gui.center local frame = script_data.elements.inventory[player.index] if frame then deregister_gui(frame) frame.destroy() script_data.elements.inventory[player.index] = nil set_config_visibility(player) return end local frame = gui.add{type = "frame", caption = {"change-starting-items"}, direction = "vertical"} local group = frame.add{type = "frame", style = "window_content_frame"} script_data.elements.inventory[player.index] = frame local admin = player.admin local config = script_data.config local types = { {name = {"equipment"}, list = config.equipment_list, option = config.starting_equipment}, {name = {"chest"}, list = config.inventory_list, option = config.starting_chest} } for k, param in pairs (types) do local data = param.list local options = param.option if data and options then local inner = group.add{type = "frame", caption = name, style = "bordered_frame", direction = "vertical"} inner.add{type = "label", caption = param.name, style = "caption_label"} local selected = options.selected if admin then local dropdown = inner.add{type = "drop-down"} dropdown.style.horizontally_stretchable = true register_gui_action(dropdown, {type = "starting_item_dropdown_changed", options = options}) local index = 1 for k, option in pairs (options.options) do if option == selected then index = k end dropdown.add_item({option}) end dropdown.selected_index = index else inner.add{type = "label", caption = {selected}} end local scroll = inner.add{type = "scroll-pane"} local items = data[selected] if not items then return end local prototypes = game.item_prototypes local table = scroll.add{type = "flow", direction = "horizontal"} local table1 = table.add{type = "table", column_count = 3} table1.style.vertically_stretchable = true local spacer = table.add{type = "flow"} spacer.style.width = 16 local table2 = table.add{type = "table", column_count = 3} table2.style.vertically_stretchable = true local which for name, count in pairs (items) do local prototype = prototypes[name] if prototype then if which == table1 then which = table2 else which = table1 end if admin then local elem = which.add{type = "choose-elem-button", elem_type = "item"} elem.elem_value = name register_gui_action(elem, {type = "starting_item_elem_changed", items = items, previous = name}) else local sprite = which.add{type = "sprite", sprite = "item/"..name} sprite.style.width = 32 sprite.style.height = 32 end which.add{type = "label", caption = prototype.localised_name} if admin then local text = which.add{type = "textfield", text = count} text.style.maximal_width = 60 register_gui_action(text, {type = "starting_item_textfield_changed", items = items, key = name}) else which.add{type = "label", caption = count} end end end if admin then local elem = table1.add{type = "choose-elem-button", elem_type = "item"} local pusher = table.add{type = "flow"} pusher.style.vertically_stretchable = true local pusher = table.add{type = "flow"} pusher.style.vertically_stretchable = true register_gui_action(elem, {type = "starting_item_elem_changed", items = items}) end end end register_gui_action(frame.add{type = "button", caption = {"gui.close"}, style = "dialog_button"}, {type = "starting_chest"}) set_config_visibility(player) end local gui_functions = { new_team = function(event, param) local name repeat name = game.backer_names[math.random(#game.backer_names)] until name_allowed(name) local team = { name = name, color = script_data.config.colors[math.random(#script_data.config.colors)].name, members = {}, team = "-" } insert(script_data.config.teams, team) refresh_config() end, remove_team = function(event, param) if #script_data.config.teams == 1 then game.players[event.player_index].print("Can't remove last team") return end for k, team in pairs (script_data.config.teams) do if team == param.team then table.remove(script_data.config.teams, k) for k, members in pairs (team.members) do script_data.ready_players[k] = nil script_data.team_players[k] = nil end break end end refresh_config() end, rename_team = function(event, param) param.edit_flow.visible = true param.show_flow.visible = false end, cancel_rename = function(event, param) param.edit_flow.visible = false param.show_flow.visible = true end, confirm_rename = function(event, param) local name = param.textfield.text if not name_allowed(name, param.team) then game.players[event.player_index].print("Name not allowed") --[[TODO locale]] return end param.team.name = name param.team.color = script_data.config.colors[param.dropdown.selected_index].name refresh_config() end, join_team = function(event, param) local player = game.players[event.player_index] local current_team = script_data.team_players[player.index] if current_team then current_team.members[player.index] = nil end param.team.members[player.index] = player script_data.team_players[player.index] = param.team script_data.ready_players[player.index] = nil refresh_config() check_all_ready() end, leave_team = function(event, param) local player = game.players[event.player_index] local current_team = param.team if current_team then current_team.members[player.index] = nil end script_data.team_players[player.index] = nil script_data.ready_players[player.index] = nil refresh_config() check_all_ready() end, team_button_press = function(event, param) local gui = event.element if not (gui and gui.valid) then return end local left_click = (event.button == defines.mouse_button_type.left) local index = gui.caption if index == "-" then if left_click then index = 1 else index = "?" end elseif index == "?" then if left_click then index = "-" else index = #script_data.config.teams end elseif index == tostring(#script_data.config.teams) then if left_click then index = "?" else index = index -1 end else if left_click then index = tonumber(index) + 1 elseif index == tostring(1) then index = "-" else index = index -1 end end gui.caption = index param.team.team = index refresh_config(event.player_index) end, config_text_value_changed = function(event, param) if event.name ~= defines.events.on_gui_text_changed then return end local textfield = event.element if not (textfield and textfield.valid) then return end local text = textfield.text local valid = is_text_valid(text) if not valid then textfield.style = "invalid_value_textfield" return end textfield.style = "textbox" param.config[param.key] = tonumber(text) refresh_config(event.player_index) end, config_dropdown_value_changed = function(event, param) if event.name ~= defines.events.on_gui_selection_state_changed then return end local dropdown = event.element if not (dropdown and dropdown.valid) then return end param.value.selected = param.value.options[dropdown.selected_index] refresh_config(event.player_index) end, config_boolean_changed = function(event, param) if event.name ~= defines.events.on_gui_checked_state_changed then return end local check = event.element if not (check and check.valid) then return end param.config[param.key] = check.state refresh_config(event.player_index) end, start_round = function(event, param) start_round() end, ready_up = function(event, param) if event.name ~= defines.events.on_gui_checked_state_changed then return end local checkbox = event.element if not (checkbox and checkbox.valid) then return end local player = game.players[event.player_index] if checkbox.state then script_data.ready_players[event.player_index] = true game.print({"player-is-ready", player.name}) else script_data.ready_players[event.player_index] = nil game.print({"player-is-not-ready", player.name}) end refresh_config() check_all_ready() end, toggle_balance_options = function(event, param) toggle_balance_options_gui(game.players[event.player_index]) end, reset_balance_options = function(event, param) for name, modifiers in pairs (script_data.config.modifier_list) do for key, value in pairs (modifiers) do modifiers[key] = 0 end end refresh_balance() end, balance_textfield_changed = function(event, param) if event.name ~= defines.events.on_gui_text_changed then return end local textfield = event.element if not (textfield and textfield.valid) then return end local text = textfield.text local valid = is_text_valid(text, param.no_below_100) if not valid then textfield.style = "invalid_value_textfield" return end textfield.style = "textbox" local value = (text - 100) / 100 script_data.config.modifier_list[param.modifier][param.key] = value refresh_balance(event.player_index) end, balance_options_confirm = function(event, param) local player = game.players[event.player_index] end, pvp_import = function(event, param) local player = game.players[event.player_index] if not (player and player.valid) then return end local gui = player.gui.center local frame = gui.add{type = "frame", caption = {"gui-blueprint-library.import-string"}, direction = "vertical"} script_data.elements.import[player.index] = frame local textfield = frame.add{type = "text-box"} textfield.word_wrap = true textfield.style.height = player.display_resolution.height * 0.6 / player.display_scale textfield.style.width = player.display_resolution.width * 0.6 / player.display_scale local flow = frame.add{type = "flow", direction = "horizontal"} register_gui_action ( flow.add{type = "button", caption = {"gui.close"}, style = "dialog_button"}, {type = "import_export_close", frame = frame} ) local pusher = flow.add{type = "flow"} pusher.style.horizontally_stretchable = true register_gui_action ( flow.add{type = "button", caption = {"gui-blueprint-library.import"}, style = "confirm_button"}, {type = "import_confirm", frame = frame, textfield = textfield} ) set_config_visibility(player) end, import_confirm = function(event, param) local player = game.players[event.player_index] if not (player and player.valid) then return end local gui = player.gui.center local frame = param.frame if not (frame and frame.valid) then return end local textfield = param.textfield if not (textfield and textfield.valid) then return end local text = textfield.text if text == "" then player.print({"import-failed"}) return end local new_config = game.json_to_table(util.decode(text)) if not new_config then player.print({"import-failed"}) return end for k, v in pairs (new_config) do script_data.config[k] = v end refresh_config() deregister_gui(frame) frame.destroy() script_data.elements.import[player.index] = nil player.print({"import-success"}) log("Pvp config import success") set_config_visibility(player) end, pvp_export = function(event, param) local player = game.players[event.player_index] if not (player and player.valid) then return end local gui = player.gui.center local frame = gui.add{type = "frame", caption = {"gui.export-to-string"}, direction = "vertical"} script_data.elements.import[player.index] = frame local textfield = frame.add{type = "text-box"} textfield.word_wrap = true textfield.read_only = true textfield.style.height = player.display_resolution.height * 0.6 / player.display_scale textfield.style.width = player.display_resolution.width * 0.6 / player.display_scale local config = script_data.config local data = { game_config = config.game_config, team_config = config.team_config, modifier_list = config.modifier_list, teams = config.teams, disabled_items = config.disabled_items, inventory_list = config.inventory_list, equipment_list = config.equipment_list, victory = config.victory, starting_equipment = config.starting_equipment, starting_chest = config.starting_chest } textfield.text = util.encode(game.table_to_json(data)) register_gui_action ( frame.add{type = "button", caption = {"gui.close"}, style = "dialog_button"}, {type = "import_export_close", frame = frame} ) set_config_visibility(player) end, import_export_close = function(event, param) local frame = param.frame if not (frame and frame.valid) then return end deregister_gui(frame) frame.destroy() script_data.elements.import[event.player_index] = nil set_config_visibility(game.players[event.player_index]) end, starting_chest = function(event, param) toggle_starting_chest_gui(game.players[event.player_index]) end, starting_item_textfield_changed = function(event, param) if event.name ~= defines.events.on_gui_text_changed then return end local textfield = event.element if not (textfield and textfield.valid) then return end local text = textfield.text local valid = is_text_valid(text) if not valid then textfield.style = "invalid_value_textfield" return end textfield.style = "textbox" param.items[param.key] = tonumber(text) refresh_config(event.player_index) end, starting_item_elem_changed = function(event, param) if event.name ~= defines.events.on_gui_elem_changed then return end local element = event.element if not (element and element.valid) then return end local items = param.items local previous = param.previous if previous then items[param.previous] = nil end local name = element.elem_value if name then if items[name] then game.players[event.player_index].print("No doofus, its already there") else items[name] = game.item_prototypes[name].stack_size end end refresh_inventory() end, starting_item_dropdown_changed = function(event, param) if event.name ~= defines.events.on_gui_selection_state_changed then return end local dropdown = event.element if not (dropdown and dropdown.valid) then return end local data = param.options data.selected = data.options[dropdown.selected_index] refresh_inventory() end, disable_elem_changed = function(event, param) if event.name ~= defines.events.on_gui_elem_changed then return end local gui = event.element local player = game.players[event.player_index] if not (player and player.valid and gui and gui.valid) then return end local parent = gui.parent if not script_data.config.disabled_items then script_data.config.disabled_items = {} end local items = script_data.config.disabled_items local value = gui.elem_value if not value then local map = {} for k, child in pairs (parent.children) do if child.elem_value then map[child.elem_value] = true end end for item, bool in pairs (items) do if not map[item] then items[item] = nil end end deregister_gui(gui) gui.destroy() return end if items[value] then if items[value] ~= gui.index then gui.elem_value = nil player.print({"duplicate-disable"}) end else items[value] = gui.index register_gui_action(parent.add{type = "choose-elem-button", elem_type = "item"}, {type = "disable_elem_changed"}) end script_data.config.disabled_items = items refresh_config(event.player_index) end, join_spectator = function(event, param) local frame = param.frame if (frame and frame.valid) then deregister_gui(frame) frame.destroy() end spectator_join(game.players[event.player_index]) end, admin_button = function(event, param) local gui = event.element local player = game.players[event.player_index] local frame = script_data.elements.admin[event.player_index] if (frame and frame.valid) then frame.visible = not frame.visible return end local flow = mod_gui.get_frame_flow(player) local frame = flow.add{type = "frame", caption = {"admin"}, direction = "vertical"} script_data.elements.admin[player.index] = frame frame.visible = true register_gui_action(frame.add{type = "button", caption = {"end-round"}, tooltip = {"end-round-tooltip"}, style = "dialog_button"}, {type = "admin_end_round"}) register_gui_action(frame.add{type = "button", caption = {"reroll-round"}, tooltip = {"reroll-round-tooltip"}, style = "dialog_button"}, {type = "admin_reroll_round"}) register_gui_action(frame.add{type = "button", caption = {"admin-change-team"}, tooltip = {"admin-change-team-tooltip"}, style = "dialog_button"}, {type = "spectator_join_team_button"}) end, admin_end_round = function(event, param) end_round(game.players[event.player_index]) end, admin_reroll_round = function(event, param) end_round() start_round() return end, spectator_join_team_button = function(event, param) choose_joining_gui(game.players[event.player_index]) end, pick_team = function(event, param) local gui = event.element local player = game.players[event.player_index] if not (gui and gui.valid and player and player.valid) then return end local team = param.team if not team then return end set_player(player, team) for k, other_player in pairs (game.connected_players) do choose_joining_gui(other_player) choose_joining_gui(other_player) update_team_list_frame(other_player) end end, list_teams_button = function(event, param) local player = game.players[event.player_index] if not (player and player.valid) then return end local frame = script_data.elements.team_frame[player.index] if frame and frame.valid then frame.destroy() script_data.elements.team_frame[player.index] = nil return end local flow = mod_gui.get_frame_flow(player) frame = flow.add{type = "frame", caption = {"teams"}, direction = "vertical"} frame.style.vertically_stretchable = false script_data.elements.team_frame[player.index] = frame update_team_list_frame(player) end, production_score_button = function(event, param) local gui = event.element local player = game.players[event.player_index] local frame = script_data.elements.production_score_frame[player.index] if frame and frame.valid then deregister_gui(frame) script_data.elements.production_score_frame[player.index] = nil frame.destroy() return end local flow = mod_gui.get_frame_flow(player) frame = flow.add{type = "frame", caption = {"production_score"}, direction = "vertical"} script_data.elements.production_score_frame[player.index] = frame frame.style.vertically_stretchable = false if script_data.config.victory.required_production_score > 0 then frame.add{type = "label", caption = {"", {"required_production_score"}, {"colon"}, " ", util.format_number(script_data.config.victory.required_production_score)}} end if script_data.config.game_config.time_limit > 0 then insert(script_data.timers, frame.add{type = "label", caption = {"time_left", get_time_left()}}) end local inner_frame = frame.add{type = "frame", style = "image_frame", direction = "vertical"} script_data.elements.production_score_inner_frame[player.index] = inner_frame inner_frame.style.left_padding = 8 inner_frame.style.top_padding = 8 inner_frame.style.right_padding = 8 inner_frame.style.bottom_padding = 8 local flow = frame.add{type = "flow", direction = "horizontal"} flow.add{type = "label", caption = {"", {"recipe-calculator"}, {"colon"}}} local recipe_button = flow.add{type = "choose-elem-button", elem_type = "recipe"} register_gui_action(recipe_button, {type = "recipe_picker_elem_changed", frame = frame}) script_data.elements.recipe_button[player.index] = recipe_button flow.style.vertical_align = "center" update_production_score_frame(player) recipe_picker_elem_update(player) end, recipe_picker_elem_changed = function(event, param) if event.name ~= defines.events.on_gui_elem_changed then return end local elem_button = event.element if not (elem_button and elem_button.valid) then return end local player = game.players[event.player_index] if not (player and player.valid) then return end script_data.selected_recipe[player.index] = elem_button.elem_value recipe_picker_elem_update(player) end, calculator_button_press = function(event, param) on_calculator_button_press(event, param) end, space_race_button = function(event, param) local player = game.players[event.player_index] local frame = script_data.elements.space_race_frame[player.index] if frame and frame.valid then frame.destroy() script_data.elements.space_race_frame[player.index] = nil return end local flow = mod_gui.get_frame_flow(player) frame = flow.add{type = "frame", caption = {"space_race"}, direction = "vertical"} frame.style.vertically_stretchable = false script_data.elements.space_race_frame[player.index] = frame update_space_race_frame(player) end, kill_score_button = function(event, param) local player = game.players[event.player_index] local frame = script_data.elements.kill_score_frame[player.index] if frame and frame.valid then frame.destroy() script_data.elements.kill_score_frame[player.index] = nil return end local flow = mod_gui.get_frame_flow(player) frame = flow.add{type = "frame", caption = {"kill_score"}, direction = "vertical"} frame.style.vertically_stretchable = false script_data.elements.kill_score_frame[player.index] = frame update_kill_score_frame(player) end, oil_harvest_button = function(event, param) local player = game.players[event.player_index] local frame = script_data.elements.oil_harvest_frame[player.index] if (frame and frame.valid) then frame.destroy() script_data.elements.oil_harvest_frame[player.index] = nil return end local flow = mod_gui.get_frame_flow(player) frame = flow.add{type = "frame", caption = {"oil_harvest"}, direction = "vertical"} frame.style.vertically_stretchable = false script_data.elements.oil_harvest_frame[player.index] = frame update_oil_harvest_frame(player) end } function start_all_ready_preparations() local seconds = 10 game.print({"everybody-ready", seconds}) script_data.ready_start_tick = game.tick + (seconds * 60) end function add_new_config_gui(config_data, flow, admin) local bool_flow = flow.add{type = "flow", direction = "vertical"} --local bottom_frame = flow.add{type = "frame", style = "bordered_frame_bottom"} local other_flow = flow.add{type = "table", column_count = 2} other_flow.style.column_alignments[2] = "right" --other_flow.style.column_alignments[1] = "right" local items = game.item_prototypes for name, value in pairs (config_data) do if type(value) == "boolean" then local check = bool_flow.add{type = "checkbox", state = value, caption = config.localised_names[name] or {name}, ignored_by_interaction = not admin, tooltip = config.localised_tooltips[name] or {name.."_tooltip"}} register_gui_action(check, {type = "config_boolean_changed", config = config_data, key = name}) end if tonumber(value) then other_flow.add{type = "label", caption = config.localised_names[name] or {name}, tooltip = config.localised_tooltips[name] or {name.."_tooltip"}} if admin then text = other_flow.add{type = "textfield", text = value} text.style.maximal_width = 80 register_gui_action(text, {type = "config_text_value_changed", config = config_data, key = name}) else other_flow.add{type = "label", caption = value} end end if type(value) == "table" then other_flow.add{type = "label", caption = config.localised_names[name] or {name}, tooltip = config.localised_tooltips[name] or {name.."_tooltip"}} if admin then local menu = other_flow.add{type = "drop-down", enabled = admin} register_gui_action(menu, {type = "config_dropdown_value_changed", value = value}) local index for j, option in pairs (value.options) do if items[option] then menu.add_item(items[option].localised_name) else menu.add_item({option}) end if option == value.selected then index = j end end menu.selected_index = index or 1 else other_flow.add{type = "label", caption = (items[value.selected] and items[value.selected].localised_name) or {value.selected}} end end end end function add_victory_gui(config_data, flow, admin) local flow = flow.add{type = "table", column_count = 2} flow.style.column_alignments[1] = "right" local items = game.item_prototypes for name, value in pairs (config_data) do if type(value) == "boolean" then local check = flow.add{type = "checkbox", state = value, caption = config.localised_names[name] or {name}, ignored_by_interaction = not admin, tooltip = config.localised_tooltips[name] or {name.."_tooltip"}} check.style.horizontally_stretchable = true register_gui_action(check, {type = "config_boolean_changed", config = config_data, key = name}) local spacer = flow.add{type = "flow"} end if tonumber(value) then flow.add{type = "label", caption = config.localised_names[name] or {name}, tooltip = config.localised_tooltips[name] or {name.."_tooltip"}} if admin then text = flow.add{type = "textfield", text = value} text.style.maximal_width = 80 register_gui_action(text, {type = "config_text_value_changed", config = config_data, key = name}) else flow.add{type = "label", caption = value} end end if type(value) == "table" then flow.add{type = "label", caption = config.localised_names[name] or {name}, tooltip = config.localised_tooltips[name] or {name.."_tooltip"}} if admin then local menu = flow.add{type = "drop-down", enabled = admin} register_gui_action(menu, {type = "config_dropdown_value_changed", value = value}) local index for j, option in pairs (value.options) do if items[option] then menu.add_item(items[option].localised_name) else menu.add_item({option}) end if option == value.selected then index = j end end menu.selected_index = index or 1 else flow.add{type = "label", caption = {value.selected}} end end end end function create_config_gui(player) if not (player and player.valid and player.connected) then return end local old = script_data.elements.config[player.index] if (old and old.valid) then deregister_gui(old) old.destroy() end local admin = player.admin local gui = player.gui.center local upper_frame = gui.add{type = "frame", caption = {"pvp-configuration"}, direction = "vertical"} upper_frame.style.minimal_width = player.display_resolution.width * 0.75 / player.display_scale script_data.elements.config[player.index] = upper_frame upper_frame.style.vertically_stretchable = false local holding_table = upper_frame.add{type = "frame", direction = "horizontal", style = "window_content_frame"} local team_lobby = holding_table.add{type = "frame", direction = "vertical", style = "bordered_frame"} team_lobby.style.vertically_stretchable = true team_lobby.add{type = "label", caption = {"teams"}, style = "caption_label"} if admin then local button = team_lobby.add{type = "button", caption = {"add-team"}, tooltip = {"add-team-tooltip"}, enabled = #script_data.config.teams < 24, style = "dialog_button"} register_gui_action(button, {type = "new_team", frame = flow}) end local scroll = team_lobby.add{type = "scroll-pane", direction = "vertical"} local current_team = script_data.team_players[player.index] for k, team in pairs (script_data.config.teams) do add_team_to_new_flow(team, scroll, current_team, admin) end local ready_data = script_data.ready_players local str = "" local first = true for k, player in pairs (game.connected_players) do if not script_data.team_players[player.index] then if first then first = false else str = str.. ", " end if ready_data[player.index] then str = str .. green(player.name) else str = str .. red(player.name) end end end if first then str = {"none"} end team_lobby.add{type = "label", caption = {"unassigned-players", str}} local team_settings = holding_table.add{type = "frame", direction = "vertical", style = "bordered_frame"} team_settings.add{type = "label", caption = {"team-settings"}, style = "caption_label"} team_settings.style.vertically_stretchable = true team_settings.style.horizontally_stretchable = false add_new_config_gui(script_data.config.team_config, team_settings, admin) local game_settings = holding_table.add{type = "frame", direction = "vertical", style = "bordered_frame"} game_settings.add{type = "label", caption = {"game-settings"}, style = "caption_label"} game_settings.style.vertically_stretchable = true game_settings.style.horizontally_stretchable = false add_new_config_gui(script_data.config.game_config, game_settings, admin) local other_flow = holding_table.add{type = "flow", direction = "vertical"} other_flow.style.vertically_stretchable = true other_flow.style.horizontally_stretchable = false local victory = other_flow.add{type = "frame", direction = "vertical", style = "bordered_frame"} victory.add{type = "label", caption = {"victory-conditions"}, style = "caption_label"} victory.style.vertically_stretchable = true add_victory_gui(script_data.config.victory, victory, admin) local disable_items = other_flow.add{type = "frame", style = "bordered_frame", direction = "vertical"} disable_items.add{type = "label", caption = {"disabled-items"}, style = "caption_label"} disable_items.style.vertically_stretchable = true disable_items.style.horizontally_stretchable = true create_disable_frame(disable_items) local button_flow = upper_frame.add{type = "flow"} button_flow.style.vertical_align = "center" register_gui_action( button_flow.add { type = "button", caption = {"balance-options"}, style = "dialog_button" }, { type = "toggle_balance_options" } ) register_gui_action(button_flow.add{type = "button", caption = {"starting-items"}, style = "dialog_button"}, {type = "starting_chest"}) register_gui_action(button_flow.add{type = "sprite-button", sprite = "utility/export_slot", tooltip = {"gui.export-to-string"}, style = "slot_button"}, {type = "pvp_export"}) if admin then register_gui_action(button_flow.add{type = "sprite-button", sprite = "utility/import_slot", tooltip = {"gui-blueprint-library.import-string"}, style = "slot_button"}, {type = "pvp_import"}) end local pusher = button_flow.add{type = "flow"} pusher.style.horizontally_stretchable = true local ready = script_data.ready_players[player.index] or false local ready_up = button_flow.add{type = "checkbox", caption = {"ready"}, state = ready} register_gui_action(ready_up, {type = "ready_up"}) local start_button = button_flow.add{type = "button", style = "confirm_button", caption = {"start-round"}, enabled = admin} register_gui_action(start_button, {type = "start_round"}) set_config_visibility(player) end function end_round(admin) destroy_config_for_all() for k, player in pairs (game.players) do player.force = game.forces.player player.tag = "" destroy_player_gui(player) if player.connected then if player.ticks_to_respawn then player.ticks_to_respawn = nil end local character = player.character player.character = nil if character then character.destroy() end player.set_controller{type = defines.controllers.spectator} player.teleport({0,1000}, game.surfaces.Lobby) create_config_gui(player) end end if script_data.surface and script_data.surface.valid then game.delete_surface(script_data.surface) end if admin then game.print({"admin-ended-round", admin.name}) end script_data.setup_finished = false script_data.check_starting_area_generation = false script_data.average_score = nil script_data.scores = nil script_data.exclusion_map = nil script_data.protected_teams = nil script_data.check_base_exclusion = nil script_data.oil_harvest_scores = nil script_data.production_scores = nil script_data.rocket_scores = nil script_data.kill_scores = nil script_data.last_defcon_tick = nil script_data.next_defcon_tech = nil script_data.silos = nil script.raise_event(events.on_round_end, {}) end function prepare_next_round() for k, config in pairs (script_data.elements.config) do if config and config.valid then config.destroy() end end script_data.elements.config = {} script_data.setup_finished = false script_data.team_won = false create_next_surface() setup_teams() chart_starting_area_for_force_spawns() set_evolution_factor() set_difficulty() end game_mode_buttons = function() return { ["production_score"] = {type = "button", caption = {"production_score"}, action = "production_score_button", style = mod_gui.button_style}, ["oil_harvest"] = {type = "button", caption = {"oil_harvest"}, action = "oil_harvest_button", style = mod_gui.button_style}, ["kill_score"] = {type = "button", caption = {"kill_score"}, action = "kill_score_button", style = mod_gui.button_style}, ["space_race"] = {type = "button", caption = {"space_race"}, action = "space_race_button", style = mod_gui.button_style} } end function init_player_gui(player) destroy_player_gui(player) if script_data.progress then update_progress_bar() return end if not script_data.setup_finished then create_config_gui(player) return end if player.force.name == "player" then choose_joining_gui(player) return end local button_flow = mod_gui.get_button_flow(player) local list_teams_button = button_flow.add{type = "button", caption = {"teams"}, style = mod_gui.button_style} register_gui_action(list_teams_button, {type = "list_teams_button"}) script_data.elements.team_list_button[player.index] = list_teams_button for name, button in pairs (game_mode_buttons()) do if not script_data.elements[name] then script_data.elements[name] = {} end if script_data.config.victory[name] then local element = button_flow.add(button) register_gui_action(element, {type = button.action}) script_data.elements[name][player.index] = element end end if player.admin then local admin_button = button_flow.add{type = "button", caption = {"admin"}, style = mod_gui.button_style} register_gui_action(admin_button, {type = "admin_button"}) script_data.elements.admin_button[player.index] = admin_button end if player.force.name == "spectator" then local spectate_button = button_flow.add{type = "button", caption = {"join-team"}, style = mod_gui.button_style} register_gui_action(spectate_button, {type = "spectator_join_team_button"}) script_data.elements.spectate_button[player.index] = spectate_button end end function get_color(team, lighten) local c = script_data.config.colors[script_data.config.color_map[team.color]].color if lighten then return {r = 1 - (1 - c.r) * 0.5, g = 1 - (1 - c.g) * 0.5, b = 1 - (1 - c.b) * 0.5, a = 1} end return c end function add_player_list_gui(force, gui) if not (force and force.valid) then return end if #force.players == 0 then gui.add{type = "label", caption = {"none"}} return end local scroll = gui.add{type = "scroll-pane"} scroll.style.maximal_height = 120 local name_table = scroll.add{type = "table", column_count = 1} name_table.style.vertical_spacing = 0 local added = {} local first = true if #force.connected_players > 0 then local online_names = "" for k, player in pairs (force.connected_players) do if not first then online_names = online_names..", " end first = false online_names = online_names..player.name added[player.name] = true end local online_label = name_table.add{type = "label", caption = {"online", online_names}} online_label.style.single_line = false online_label.style.maximal_width = 180 end first = true if #force.players > #force.connected_players then local offline_names = "" for k, player in pairs (force.players) do if not added[player.name] then if not first then offline_names = offline_names..", " end first = false offline_names = offline_names..player.name added[player.name] = true end end local offline_label = name_table.add{type = "label", caption = {"offline", offline_names}} offline_label.style.single_line = false offline_label.style.font_color = {r = 0.7, g = 0.7, b = 0.7} offline_label.style.maximal_width = 180 end end function set_player(player, team, mute) local force = game.forces[team.name] local old_force = player.force local surface = script_data.surface if not surface.valid then return end local position = surface.find_non_colliding_position("player", force.get_spawn_position(surface), 0, 2) if not position then player.print({"cant-find-position"}) choose_joining_gui(player) return end local old_character = player.character local character_name = old_character and old_character.name or "player" player.character = nil if old_character then old_character.die() end player.force = force player.teleport(position, surface) player.set_controller { type = defines.controllers.character, character = surface.create_entity{name = character_name, position = position, force = force} } player.color = get_color(team) player.chat_color = get_color(team, true) player.tag = "["..force.name.."]" init_player_gui(player) for k, other_player in pairs (game.connected_players) do choose_joining_gui(other_player) choose_joining_gui(other_player) update_team_list_frame(other_player) end local artillery_remote = script_data.config.prototypes.artillery_remote if script_data.config.game_config.team_artillery and script_data.config.game_config.give_artillery_remote and game.item_prototypes[artillery_remote] then player.insert(artillery_remote) end config.give_equipment(player) balance.apply_character_modifiers(player) if not mute then game.print({"joined", player.name, player.force.name}) end check_force_protection(force) check_force_protection(old_force) script.raise_event(events.on_player_joined_team, {player_index = player.index, team = team, force = force}) end function choose_joining_gui(player) local frame = script_data.elements.join[player.index] if (frame and frame.valid) then deregister_gui(frame) frame.destroy() return end local teams = get_eligible_teams(player) if not teams then return end local gui = player.gui.center local frame = gui.add{type = "frame", caption = {"pick-join"}, direction = "vertical"} script_data.elements.join[player.index] = frame local inner_frame = frame.add{type = "frame", style = "image_frame", direction = "vertical"} inner_frame.style.left_padding = 8 inner_frame.style.top_padding = 8 inner_frame.style.right_padding = 8 inner_frame.style.bottom_padding = 8 local pick_join_table = inner_frame.add{type = "table", column_count = 4} pick_join_table.style.horizontal_spacing = 16 pick_join_table.style.vertical_spacing = 8 pick_join_table.draw_horizontal_lines = true pick_join_table.draw_vertical_lines = true pick_join_table.style.column_alignments[3] = "right" pick_join_table.add{type = "label", caption = {"team-name"}}.style.font = "default-semibold" pick_join_table.add{type = "label", caption = {"players"}}.style.font = "default-semibold" pick_join_table.add{type = "label", caption = {"team-number"}}.style.font = "default-semibold" pick_join_table.add{type = "label"} for k, team in pairs (teams) do local force = game.forces[team.name] if force then local name = pick_join_table.add{type = "label", caption = force.name} name.style.font = "default-semibold" name.style.font_color = get_color(team, true) add_player_list_gui(force, pick_join_table) local caption if tonumber(team.team) then caption = team.team elseif team.team:find("?") then caption = team.team:gsub("?", "") else caption = team.team end pick_join_table.add{type = "label", caption = caption} local join_button = pick_join_table.add{type = "button", caption = {"join"}, style = "dialog_button"} register_gui_action(join_button, {type = "pick_team", team = team}) end end register_gui_action(frame.add{type = "button", caption = {"join-spectator"}, style = "dialog_button"}, {type = "join_spectator", frame = frame}) end function toggle_balance_options_gui(player) if not (player and player.valid and player.connected) then return end local gui = player.gui.center local frame = script_data.elements.balance[player.index] if frame then deregister_gui(frame) frame.destroy() script_data.elements.balance[player.index] = nil set_config_visibility(player) return end frame = gui.add{type = "frame", direction = "vertical", caption = {"balance-options"}} script_data.elements.balance[player.index] = frame local inner = frame.add{type = "frame", style = "window_content_frame"} local scrollpane = inner.add{type = "scroll-pane"} local big_table = scrollpane.add{type = "table", column_count = 5, direction = "horizontal"} --big_table.style.horizontal_spacing = 32 --big_table.draw_vertical_lines = true local entities = game.entity_prototypes local ammos = game.ammo_category_prototypes local admin = player.admin for modifier_name, array in pairs (script_data.config.modifier_list) do local flow = big_table.add{type = "frame", style = "bordered_frame", direction = "vertical"} flow.style.vertically_stretchable = true flow.add{type = "label", style = "caption_label", caption = {modifier_name}} local table = flow.add{type = "table", column_count = 3} table.style.column_alignments[1] = "right" table.style.column_alignments[2] = "right" for name, modifier in pairs (array) do if modifier_name == "ammo_damage_modifier" then local string = "ammo-category-name."..name table.add{type = "label", caption = {"", ammos[name].localised_name, {"colon"}}} elseif modifier_name == "gun_speed_modifier" then table.add{type = "label", caption = {"", ammos[name].localised_name, {"colon"}}} elseif modifier_name == "turret_attack_modifier" then table.add{type = "label", caption = {"", entities[name].localised_name, {"colon"}}} elseif modifier_name == "character_modifiers" then table.add{type = "label", caption = {"", {name}, {"colon"}}} elseif modifier_name == "force_modifiers" then table.add{type = "label", caption = {"", {name}, {"colon"}}} end if admin then local input = table.add{type = "textfield"} register_gui_action(input, {type = "balance_textfield_changed", modifier = modifier_name, key = name, no_below_100 = (modifier_name == "force_modifiers")}) input.text = tostring((modifier * 100) + 100) input.style.maximal_width = 60 else table.add{type = "label", caption = tostring((modifier * 100) + 100)} end table.add{type = "label", caption = "%"} end end local flow = frame.add{type = "flow", direction = "horizontal"} flow.style.horizontally_stretchable = true flow.style.horizontal_align = "right" register_gui_action(flow.add{type = "button", caption = {"gui.close"}, style = "dialog_button"}, {type = "toggle_balance_options"}) if admin then add_pusher(flow) register_gui_action(flow.add{type = "button", caption = {"gui.reset"}, style = "dialog_button"}, {type = "reset_balance_options"}) end set_config_visibility(player) end function create_disable_frame(gui) local disable_table = gui.add{type = "table", column_count = 5} disable_table.style.horizontal_spacing = 2 disable_table.style.vertical_spacing = 2 local items = game.item_prototypes local player = game.players[gui.player_index] local admin = player.admin if script_data.config.disabled_items then for item, bool in pairs (script_data.config.disabled_items) do local prototype = items[item] if prototype then if admin then local choose = disable_table.add{type = "choose-elem-button", elem_type = "item"} choose.elem_value = item register_gui_action(choose, {type = "disable_elem_changed"}) else local icon = disable_table.add{type = "sprite", sprite = "item/"..item, tooltip = prototype.localised_name} icon.style.width = 32 icon.style.height = 32 end end end end if admin then local choose = disable_table.add{type = "choose-elem-button", elem_type = "item"} register_gui_action(choose, {type = "disable_elem_changed"}) end end function start_round() script_data.ready_start_tick = nil destroy_config_for_all() prepare_next_round() end function get_eligible_teams(player) local limit = script_data.config.team_config.max_players local teams = {} for k, team in pairs (script_data.config.teams) do local force = game.forces[team.name] if force then if limit <= 0 or #force.connected_players < limit or player.admin then insert(teams, team) end end end if #teams == 0 then spectator_join(player) player.print({"no-space-available"}) return end return teams end function destroy_config_for_all() for name, frames in pairs (script_data.elements) do for k, frame in pairs (frames) do if (frame and frame.valid) then deregister_gui(frame) frame.destroy() end end script_data.elements[name] = {} end script_data.ready_players = {} end function set_evolution_factor() local n = script_data.config.team_config.evolution_factor if n >= 1 then n = 1 end if n <= 0 then n = 0 end for k, force in pairs (game.forces) do force.evolution_factor = n end script_data.config.team_config.evolution_factor = n end function set_difficulty() game.difficulty_settings.technology_price_multiplier = script_data.config.team_config.technology_price_multiplier or 1 end function spectator_join(player) local character = player.character player.set_controller{type = defines.controllers.spectator} if character then character.die() end player.force = "spectator" player.teleport(script_data.spawn_offset, script_data.surface) player.tag = "" player.chat_color = {r = 1, g = 1, b = 1, a = 1} init_player_gui(player) game.print({"joined-spectator", player.name}) end function update_team_list_frame(player) if not (player and player.valid) then return end local frame = script_data.elements.team_frame[player.index] if not (frame and frame.valid) then return end frame.clear() local inner = frame.add{type = "frame", style = "image_frame"} inner.style.left_padding = 8 inner.style.right_padding = 8 inner.style.top_padding = 8 inner.style.bottom_padding = 8 local scroll = inner.add{type = "scroll-pane"} local team_table = scroll.add{type = "table", column_count = 2} team_table.style.vertical_spacing = 8 team_table.style.horizontal_spacing = 16 team_table.draw_horizontal_lines = true team_table.draw_vertical_lines = true team_table.add{type = "label", caption = {"team-name"}, style = "bold_label"} team_table.add{type = "label", caption = {"players"}, style = "bold_label"} for k, team in pairs (script_data.config.teams) do local force = game.forces[team.name] if force then local label = team_table.add{type = "label", caption = team.name, style = "description_label"} label.style.font_color = get_color(team, true) add_player_list_gui(force, team_table) end end end function format_time(ticks) local hours = math.floor(ticks / (60 * 60 * 60)) ticks = ticks - hours * (60 * 60 * 60) local minutes = math.floor(ticks / (60 * 60)) ticks = ticks - minutes * (60 * 60) local seconds = math.floor(ticks / 60) if hours > 0 then return string.format("%d:%02d:%02d", hours, minutes, seconds) else return string.format("%d:%02d", minutes, seconds) end end function get_time_left() if not script_data.round_start_tick then return "Invalid" end if not script_data.config.game_config.time_limit then return "Invalid" end return format_time((math.max(script_data.round_start_tick + (script_data.config.game_config.time_limit * 60 * 60) - game.tick, 0))) end function update_production_score_frame(player) local frame = script_data.elements.production_score_inner_frame[player.index] if not (frame and frame.valid) then return end frame.clear() local information_table = frame.add{type = "table", column_count = 4} information_table.draw_horizontal_line_after_headers = true information_table.draw_vertical_lines = true information_table.style.horizontal_spacing = 16 information_table.style.vertical_spacing = 8 information_table.style.column_alignments[3] = "right" information_table.style.column_alignments[4] = "right" for k, caption in pairs ({"", "team-name", "score", "score_per_minute"}) do local label = information_table.add{type = "label", caption = {caption}, tooltip = {caption.."_tooltip"}} label.style.font = "default-bold" end local team_map = {} for k, team in pairs (script_data.config.teams) do team_map[team.name] = team end local average_score = script_data.average_score if not average_score then return end local rank = 1 for name, score in spairs (script_data.production_scores, function(t, a, b) return t[b] < t[a] end) do if not average_score[name] then average_score = nil return end if team_map[name] then local position = information_table.add{type = "label", caption = "#"..rank} if name == player.force.name then position.style.font = "default-semibold" position.style.font_color = {r = 1, g = 1} end local label = information_table.add{type = "label", caption = name} label.style.font = "default-semibold" label.style.font_color = get_color(team_map[name], true) information_table.add{type = "label", caption = util.format_number(score)} local delta_score = (score - (average_score[name] / statistics_period)) * (60 / statistics_period) * 2 local delta_label = information_table.add{type = "label", caption = util.format_number(math.floor(delta_score))} if delta_score < 0 then delta_label.style.font_color = {r = 1, g = 0.2, b = 0.2} end rank = rank + 1 end end end function update_oil_harvest_frame(player) local frame = script_data.elements.oil_harvest_frame[player.index] if not (frame and frame.valid) then return end frame.clear() if script_data.config.victory.required_oil > 0 then frame.add{type = "label", caption = {"", {"required_oil"}, {"colon"}, " ", util.format_number(script_data.config.victory.required_oil)}} end if script_data.config.game_config.time_limit > 0 then frame.add{type = "label", caption = {"time_left", get_time_left()}} end local inner_frame = frame.add{type = "frame", style = "image_frame", direction = "vertical"} inner_frame.style.left_padding = 8 inner_frame.style.top_padding = 8 inner_frame.style.right_padding = 8 inner_frame.style.bottom_padding = 8 local information_table = inner_frame.add{type = "table", column_count = 3} information_table.draw_horizontal_line_after_headers = true information_table.draw_vertical_lines = true information_table.style.horizontal_spacing = 16 information_table.style.vertical_spacing = 8 information_table.style.column_alignments[3] = "right" for k, caption in pairs ({"", "team-name", "oil_harvest"}) do local label = information_table.add{type = "label", caption = {caption}} label.style.font = "default-bold" end local team_map = {} for k, team in pairs (script_data.config.teams) do team_map[team.name] = team end if not script_data.oil_harvest_scores then script_data.oil_harvest_scores = {} end local rank = 1 for name, score in spairs (script_data.oil_harvest_scores, function(t, a, b) return t[b] < t[a] end) do if team_map[name] then local position = information_table.add{type = "label", caption = "#"..rank} if name == player.force.name then position.style.font = "default-semibold" position.style.font_color = {r = 1, g = 1} end local label = information_table.add{type = "label", caption = name} label.style.font = "default-semibold" label.style.font_color = get_color(team_map[name], true) information_table.add{type = "label", caption = util.format_number(math.floor(score))} rank = rank + 1 end end end function update_kill_score_frame(player) local frame = script_data.elements.kill_score_frame[player.index] if not (frame and frame.valid) then return end frame.clear() if script_data.config.victory.required_kill_score > 0 then frame.add{type = "label", caption = {"", {"required_kill_score"}, {"colon"}, " ", util.format_number(script_data.config.victory.required_kill_score)}} end if script_data.config.game_config.time_limit > 0 then frame.add{type = "label", caption = {"time_left", get_time_left()}} end local inner_frame = frame.add{type = "frame", style = "image_frame", direction = "vertical"} inner_frame.style.left_padding = 8 inner_frame.style.top_padding = 8 inner_frame.style.right_padding = 8 inner_frame.style.bottom_padding = 8 local information_table = inner_frame.add{type = "table", column_count = 3} information_table.draw_horizontal_line_after_headers = true information_table.draw_vertical_lines = true information_table.style.horizontal_spacing = 16 information_table.style.vertical_spacing = 8 information_table.style.column_alignments[3] = "right" for k, caption in pairs ({"", "team-name", "kill_score"}) do local label = information_table.add{type = "label", caption = {caption}} label.style.font = "default-bold" end local team_map = {} for k, team in pairs (script_data.config.teams) do team_map[team.name] = team end local scores = script_data.kill_scores local rank = 1 for name, score in spairs (scores, function(t, a, b) return t[b] < t[a] end) do if team_map[name] then local position = information_table.add{type = "label", caption = "#"..rank} if name == player.force.name then position.style.font = "default-semibold" position.style.font_color = {r = 1, g = 1} end local label = information_table.add{type = "label", caption = name} label.style.font = "default-semibold" label.style.font_color = get_color(team_map[name], true) information_table.add{type = "label", caption = util.format_number(math.floor(score))} rank = rank + 1 end end end function update_space_race_frame(player) local frame = script_data.elements.space_race_frame[player.index] if not (frame and frame.valid) then return end frame.clear() if script_data.config.victory.required_rockets_sent > 0 then frame.add{type = "label", caption = {"", {"required_rockets_sent"}, {"colon"}, " ", util.format_number(script_data.config.victory.required_rockets_sent)}} end if script_data.config.game_config.time_limit > 0 then frame.add{type = "label", caption = {"time_left", get_time_left()}} end local inner_frame = frame.add{type = "frame", style = "image_frame", direction = "vertical"} inner_frame.style.left_padding = 8 inner_frame.style.top_padding = 8 inner_frame.style.right_padding = 8 inner_frame.style.bottom_padding = 8 local information_table = inner_frame.add{type = "table", column_count = 3} information_table.draw_horizontal_line_after_headers = true information_table.draw_vertical_lines = true information_table.style.horizontal_spacing = 16 information_table.style.vertical_spacing = 8 information_table.style.column_alignments[3] = "right" for k, caption in pairs ({"", "team-name", "rockets_sent"}) do local label = information_table.add{type = "label", caption = {caption}} label.style.font = "default-bold" end local colors = {} local team_map = {} for k, team in pairs (script_data.config.teams) do colors[team.name] = get_color(team, true) team_map[team.name] = team end local rank = 1 for name, score in spairs (script_data.rocket_scores, function(t, a, b) return t[b] < t[a] end) do if team_map[name] then local position = information_table.add{type = "label", caption = "#"..rank} if name == player.force.name then position.style.font = "default-semibold" position.style.font_color = {r = 1, g = 1} end local label = information_table.add{type = "label", caption = name} label.style.font = "default-semibold" label.style.font_color = colors[name] information_table.add{type = "label", caption = util.format_number(score)} rank = rank + 1 end end end function setup_teams() local spectator = game.forces["spectator"] if not (spectator and spectator.valid) then spectator = game.create_force("spectator") end local names = {} for k, team in pairs (script_data.config.teams) do names[team.name] = true end for name, force in pairs (game.forces) do if not (is_ignored_force(name) or names[name]) then game.merge_forces(name, "player") end end for k, team in pairs (script_data.config.teams) do local new_team if game.forces[team.name] then new_team = game.forces[team.name] else new_team = game.create_force(team.name) end new_team.reset() new_team.set_spawn_position(script_data.spawn_positions[k], script_data.surface) set_random_team(team) end for k, team in pairs (script_data.config.teams) do local force = game.forces[team.name] force.set_friend(spectator, true) spectator.set_friend(force, true) set_diplomacy(team) setup_research(force) balance.disable_combat_technologies(force) force.reset_technology_effects() balance.apply_combat_modifiers(force) end disable_items_for_all() end function disable_items_for_all() if not script_data.config.disabled_items then return end local items = game.item_prototypes local recipes = game.recipe_prototypes local product_map = {} for k, recipe in pairs (recipes) do for k, product in pairs (recipe.products) do if not product_map[product.name] then product_map[product.name] = {} end insert(product_map[product.name], recipe) end end local recipes_to_disable = {} for name, k in pairs (script_data.config.disabled_items) do local mapping = product_map[name] if mapping then for k, recipe in pairs (mapping) do recipes_to_disable[recipe.name] = true end end end for k, force in pairs (game.forces) do for name, bool in pairs (recipes_to_disable) do force.recipes[name].enabled = false end end end function check_technology_for_disabled_items(event) if not script_data.config.disabled_items then return end local disabled_items = script_data.config.disabled_items local technology = event.research local recipes = technology.force.recipes for k, effect in pairs (technology.effects) do if effect.type == "unlock-recipe" then for k, product in pairs (recipes[effect.recipe].products) do if disabled_items[product.name] then recipes[effect.recipe].enabled = false end end end end end function set_random_team(team) if tonumber(team.team) then return end if team.team == "-" then return end team.team = "?"..math.random(#script_data.config.teams) end function set_diplomacy(team) local force = game.forces[team.name] if not force or not force.valid then return end local team_number if tonumber(team.team) then team_number = team.team elseif team.team:find("?") then team_number = team.team:gsub("?", "") team_number = tonumber(team_number) else team_number = "Don't match me" end for k, other_team in pairs (script_data.config.teams) do if game.forces[other_team.name] then local other_number if tonumber(other_team.team) then other_number = other_team.team elseif other_team.team:find("?") then other_number = other_team.team:gsub("?", "") other_number = tonumber(other_number) else other_number = "Okay i won't match" end if other_number == team_number then force.set_cease_fire(other_team.name, true) force.set_friend(other_team.name, true) else force.set_cease_fire(other_team.name, false) force.set_friend(other_team.name, false) end end end end function set_team_together_spawns(surface) local grouping = {} for k, team in pairs (script_data.config.teams) do local team_number if tonumber(team.team) then team_number = team.team elseif team.team:find("?") then team_number = team.team:gsub("?", "") team_number = tonumber(team_number) else team_number = "-" end if tonumber(team_number) then if not grouping[team_number] then grouping[team_number] = {} end insert(grouping[team_number], team.name) else if not grouping.no_group then grouping.no_group = {} end insert(grouping.no_group, team.name) end end local count = 1 for k, group in pairs (grouping) do for j, team_name in pairs (group) do local force = game.forces[team_name] if force then local position = script_data.spawn_positions[count] if position then force.set_spawn_position(position, surface) count = count + 1 end end end end end function chart_starting_area_for_force_spawns() --Delay by 1 tick so the GUI can update script_data.chart_chunks = 1 + game.tick + (#script_data.config.teams) script_data.progress = 0 update_progress_bar() end function clear_biters(surface, area) for k, entity in pairs(surface.find_entities_filtered{force = "enemy", area = area}) do entity.destroy() end end function check_starting_area_chunks_are_generated() if not script_data.chart_chunks then return end local index = script_data.chart_chunks - game.tick local surface = script_data.surface if index == 0 then script_data.progress = 0.99 script_data.chart_chunks = nil script_data.finish_setup = game.tick + (#script_data.config.teams) update_progress_bar() return end local team = script_data.config.teams[index] if not team then return end local name = team.name local force = game.forces[name] if not force then return end script_data.progress = (#script_data.config.teams - index) / #script_data.config.teams update_progress_bar() local surface = script_data.surface local radius = get_starting_area_radius() + 3 local size = radius * 32 local origin = force.get_spawn_position(surface) local area = {{origin.x - size, origin.y - size},{origin.x + (size - 32), origin.y + (size - 32)}} surface.request_to_generate_chunks(origin, radius) surface.force_generate_chunk_requests() clear_biters(surface, area) end function check_player_color() for k, team in pairs (script_data.config.teams) do local force = game.forces[team.name] if force then local color = get_color(team) for k, player in pairs (force.connected_players) do local player_color = player.color for c, v in pairs (color) do if math.abs(player_color[c] - v) > 0.1 then game.print({"player-color-changed-back", player.name}) player.color = color player.chat_color = get_color(team, true) break end end end end end end function check_no_rush() if not script_data.end_no_rush then return end if game.tick > script_data.end_no_rush then if script_data.config.game_config.no_rush_time > 0 then game.print({"no-rush-ends"}) end script_data.end_no_rush = nil script_data.surface.peaceful_mode = script_data.peaceful_mode game.forces.enemy.kill_all_units() return end end function check_player_no_rush(player) if not script_data.end_no_rush then return end local force = player.force if not is_ignored_force(force.name) then local origin = force.get_spawn_position(player.surface) local Xo = origin.x local Yo = origin.y local position = player.position local radius = get_starting_area_radius(true) local Xp = position.x local Yp = position.y if Xp > (Xo + radius) then Xp = Xo + radius elseif Xp < (Xo - radius) then Xp = Xo - radius end if Yp > (Yo + radius) then Yp = Yo + radius elseif Yp < (Yo - radius) then Yp = Yo - radius end if position.x ~= Xp or position.y ~= Yp then local new_position = {x = Xp, y = Yp} local vehicle = player.vehicle if vehicle then if not vehicle.teleport(new_position) then player.driving = false end vehicle.orientation = vehicle.orientation + 0.5 else player.teleport(new_position) end local time_left = math.ceil((script_data.end_no_rush-game.tick) / 3600) player.print({"no-rush-teleport", time_left}) end end end function check_update_production_score() if not script_data.config.victory.production_score then return end local tick = game.tick if script_data.team_won then return end local new_scores = production_score.get_production_scores(script_data.price_list) local scale = statistics_period / 60 local index = tick % (60 * statistics_period) if not (script_data.scores and script_data.average_score) then local average_score = {} local scores = {} for name, score in pairs (new_scores) do scores[name] = {} average_score[name] = score * statistics_period for k = 0, statistics_period do scores[name][k * 60] = score end end script_data.scores = scores script_data.average_score = average_score end local scores = script_data.scores local average_score = script_data.average_score for name, score in pairs (new_scores) do local old_amount = scores[name][index] if not old_amount then --Something went wrong, reinitialize it next update script_data.scores = nil script_data.average_score = nil return end average_score[name] = (average_score[name] + score) - old_amount scores[name][index] = score end script_data.production_scores = new_scores for k, player in pairs (game.connected_players) do update_production_score_frame(player) end local required = script_data.config.victory.required_production_score if required > 0 then for team_name, score in pairs (script_data.production_scores) do if score >= required then team_won(team_name) end end end if script_data.config.game_config.time_limit > 0 and tick > script_data.round_start_tick + (script_data.config.game_config.time_limit * 60 * 60) then local winner = {"none"} local winning_score = 0 for team_name, score in pairs (script_data.production_scores) do if score > winning_score then winner = team_name winning_score = score end end team_won(winner) end end function check_update_oil_harvest_score() if script_data.team_won then return end if not script_data.config.victory.oil_harvest then return end local fluid_to_check = script_data.config.prototypes.oil or "" if not game.fluid_prototypes[fluid_to_check] then log("Disabling oil harvest check as "..fluid_to_check.." is not a valid fluid") script_data.config.victory.oil_harvest = false return end local scores = {} for force_name, force in pairs (game.forces) do local statistics = force.fluid_production_statistics local input = statistics.get_input_count(fluid_to_check) local output = statistics.get_output_count(fluid_to_check) scores[force_name] = input - output end script_data.oil_harvest_scores = scores for k, player in pairs (game.connected_players) do update_oil_harvest_frame(player) end local required = script_data.config.victory.required_oil if required > 0 then for team_name, score in pairs (script_data.oil_harvest_scores) do if score >= required then team_won(team_name) end end end if script_data.config.game_config.time_limit > 0 and game.tick > (script_data.round_start_tick + (script_data.config.game_config.time_limit * 60 * 60)) then local winner = {"none"} local winning_score = 0 for team_name, score in pairs (script_data.oil_harvest_scores) do if score > winning_score then winner = team_name winning_score = score end end team_won(winner) end end function check_update_kill_score() if script_data.team_won then return end if not script_data.config.victory.kill_score then return end local scores = script_data.kill_scores for k, player in pairs (game.connected_players) do update_kill_score_frame(player) end local required = script_data.config.victory.required_kill_score if required > 0 then for team_name, score in pairs (scores) do if score >= required then team_won(team_name) end end end if script_data.config.game_config.time_limit > 0 and game.tick > (script_data.round_start_tick + (script_data.config.game_config.time_limit * 60 * 60)) then local winner = {"none"} local winning_score = 0 for team_name, score in pairs (scores) do if score > winning_score then winner = team_name winning_score = score end end team_won(winner) end end function check_update_space_race_score() if script_data.team_won then return end if not script_data.config.victory.space_race then return end local scores = script_data.rocket_scores for k, player in pairs (game.connected_players) do update_space_race_frame(player) end local required = script_data.config.victory.required_rockets_sent if required > 0 then for team_name, score in pairs (scores) do if score >= required then team_won(team_name) end end end if script_data.config.game_config.time_limit > 0 and game.tick > (script_data.round_start_tick + (script_data.config.game_config.time_limit * 60 * 60)) then local winner = {"none"} local winning_score = 0 for team_name, score in pairs (scores) do if score > winning_score then winner = team_name winning_score = score end end team_won(winner) end end function finish_setup() if not script_data.finish_setup then return end local index = script_data.finish_setup - game.tick local surface = script_data.surface if index == 0 then final_setup_step() return end local name = script_data.config.teams[index].name if not name then return end local force = game.forces[name] if not force then return end duplicate_starting_area_entities(index) force.chart(surface, get_force_area(force)) if script_data.config.game_config.reveal_team_positions then for name, other_force in pairs (game.forces) do if not is_ignored_force(name) then force.chart(surface, get_force_area(other_force)) end end end create_silo_for_force(force) create_wall_for_force(force) create_starting_chest(force) create_starting_turrets(force) create_starting_artillery(force) protect_force_area(force) force.friendly_fire = script_data.config.team_config.friendly_fire force.share_chart = true end function init_kill_scores() if not script_data.config.victory.kill_score then return end local scores = {} for k, force in pairs (game.forces) do scores[force.name] = 0 end script_data.kill_scores = scores end function init_rocket_scores() if not script_data.config.victory.space_race then return end local scores = {} for k, force in pairs (game.forces) do scores[force.name] = 0 end script_data.rocket_scores = scores end function final_setup_step() script_data.progress = 1 update_progress_bar() create_exclusion_map() script_data.progress = nil local surface = script_data.surface script_data.finish_setup = nil game.print({"map-ready"}) script_data.setup_finished = true script_data.round_start_tick = game.tick for k, player in pairs (game.connected_players) do destroy_player_gui(player) player.teleport({0, 1000}, "Lobby") local team = script_data.team_players[player.index] if team then set_player(player, team, true) else choose_joining_gui(player) end end if script_data.config.game_config.no_rush_time then script_data.end_no_rush = game.tick + (script_data.config.game_config.no_rush_time * 60 * 60) if script_data.config.game_config.no_rush_time > 0 then script_data.peaceful_mode = script_data.surface.peaceful_mode script_data.surface.peaceful_mode = true game.forces.enemy.kill_all_units() game.print({"no-rush-begins", script_data.config.game_config.no_rush_time}) end end if script_data.config.game_config.base_exclusion_time then if script_data.config.game_config.base_exclusion_time > 0 then script_data.check_base_exclusion = true game.print({"base-exclusion-begins", script_data.config.game_config.base_exclusion_time}) end end if script_data.config.game_config.reveal_map_center then local radius = (script_data.config.team_config.average_team_displacement / 2) + get_starting_area_radius(true) local origin = script_data.spawn_offset local area = {{origin.x - radius, origin.y - radius}, {origin.x + (radius - 32), origin.y + (radius - 32)}} for k, force in pairs (game.forces) do force.chart(surface, area) end end script_data.oil_harvest_scores = {} script_data.production_scores = {} init_rocket_scores() init_kill_scores() if script_data.config.victory.production_score then script_data.price_list = script_data.price_list or production_score.generate_price_list() end if script_data.config.team_config.defcon_mode then defcon_research() end script.raise_event(events.on_round_start, {}) end function check_force_protection(force) if not script_data.config.game_config.protect_empty_teams then return end if not (force and force.valid) then return end if is_ignored_force(force.name) then return end if not script_data.protected_teams then script_data.protected_teams = {} end local protected = script_data.protected_teams[force.name] ~= nil local should_protect = #force.connected_players == 0 if protected and should_protect then return end if (not protected) and (not should_protect) then return end if protected and (not should_protect) then unprotect_force_area(force) return end if (not protected) and should_protect then protect_force_area(force) check_base_exclusion() return end end function protect_force_area(force) if not script_data.config.game_config.protect_empty_teams then return end local surface = script_data.surface if not (surface and surface.valid) then return end local non_destructible = {} for k, entity in pairs (surface.find_entities_filtered{force = force, area = get_force_area(force)}) do if entity.destructible == false and entity.unit_number then non_destructible[entity.unit_number] = true end entity.destructible = false end if not script_data.protected_teams then script_data.protected_teams = {} end script_data.protected_teams[force.name] = non_destructible end function unprotect_force_area(force) if not script_data.config.game_config.protect_empty_teams then return end local surface = script_data.surface if not (surface and surface.valid) then return end if not script_data.protected_teams then script_data.protected_teams = {} end local entities = script_data.protected_teams[force.name] or {} for k, entity in pairs (surface.find_entities_filtered{force = force, area = get_force_area(force)}) do if (not entity.unit_number) or (not entities[entity.unit_number]) then entity.destructible = true end end script_data.protected_teams[force.name] = nil end function get_force_area(force) if not (force and force.valid) then return end local surface = script_data.surface if not (surface and surface.valid) then return end local radius = get_starting_area_radius(true) local origin = force.get_spawn_position(surface) return {{origin.x - radius, origin.y - radius}, {origin.x + (radius - 1), origin.y + (radius - 1)}} end function update_progress_bar() if not script_data.progress then return end local percent = script_data.progress local finished = (percent >=1) function update_bar_gui(player) local frame = script_data.elements.progress_bar[player.index] if frame and frame.valid then script_data.elements.progress_bar[player.index] = nil frame.destroy() end if finished then return end local frame = player.gui.center.add{type = "frame", caption = {"progress-bar"}} script_data.elements.progress_bar[player.index] = frame frame.add{type = "progressbar", size = 100, value = percent} end for k, player in pairs (game.connected_players) do update_bar_gui(player) end if finished then script_data.progress = nil script_data.setup_duration = nil script_data.finish_tick = nil end end function create_silo_for_force(force) if not script_data.config.victory.last_silo_standing then return end if not (force and force.valid) then return end local surface = script_data.surface local origin = force.get_spawn_position(surface) local offset = script_data.config.silo_offset local silo_position = {x = origin.x + (offset.x or offset[1]), y = origin.y + (offset.y or offset[2])} local silo_name = script_data.config.prototypes.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} --Event is sent, so some mod could kill the silo if not (silo and silo.valid) then return end silo.minable = false if silo.supports_backer_name() then silo.backer_name = force.name end if not script_data.silos then script_data.silos = {} end script_data.silos[force.name] = silo local tile_name = script_data.config.prototypes.tile_2 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 = math.floor(box.left_top.x) - 1, math.floor(box.right_bottom.x) + 1, math.floor(box.left_top.y) - 1, math.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 function setup_research(force) if not script_data.config.team_config.research_level then return end if not (force and force.valid) then return end local tier = script_data.config.team_config.research_level.selected local index local set = (tier ~= "none") for k, name in pairs (script_data.config.team_config.research_level.options) do if script_data.config.research_ingredient_list[name] ~= nil then script_data.config.research_ingredient_list[name] = set end if name == tier then set = false end end --[[Unlocks all research, and then unenables them based on a blacklist]] force.research_all_technologies() for k, technology in pairs (force.technologies) do for j, ingredient in pairs (technology.research_unit_ingredients) do if not script_data.config.research_ingredient_list[ingredient.name] then technology.researched = false break end end end end function create_starting_turrets(force) if not script_data.config.game_config.team_turrets then return end if not (force and force.valid) then return end local turret_name = script_data.config.prototypes.turret if not game.entity_prototypes[turret_name] then return end local ammo_name if script_data.config.game_config.turret_ammunition then ammo_name = script_data.config.game_config.turret_ammunition.selected end local insert = insert 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 = force.get_spawn_position(surface) local radius = get_starting_area_radius(true) - 18 --[[radius in tiles]] 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 = script_data.config.prototypes.tile_2 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 = 20} end local find_entities_filtered = surface.find_entities_filtered local neutral = game.forces.neutral local destroy_params = {do_cliff_correction = true} local floor = math.floor local create_entity = surface.create_entity for k, position in pairs (positions) do if is_in_map(width, height, position) 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 function create_starting_artillery(force) if not script_data.config.game_config.team_artillery then return end if not (force and force.valid) then return end local turret_name = script_data.config.prototypes.artillery if not (turret_name and game.entity_prototypes[turret_name]) then return end local ammo_name = script_data.config.prototypes.artillery_ammo if not (ammo_name and game.item_prototypes[ammo_name]) then return end local surface = script_data.surface local height = surface.map_gen_settings.height / 2 local width = surface.map_gen_settings.width / 2 local origin = force.get_spawn_position(surface) local radius = get_starting_area_radius() - 1 --[[radius in chunks]] if radius < 1 then return end local positions = {} local tile_positions = {} for x = -radius, 0 do if x == -radius then for y = -radius, 0 do insert(positions, {x = 1 + origin.x + 32 * x, y = 1 + origin.y + 32 * y}) end else insert(positions, {x = 1 + origin.x + 32 * x, y = 1 + origin.y - radius * 32}) end end for x = 1, radius do if x == radius then for y = -radius, -1 do insert(positions, {x = -2 + origin.x + 32 * x, y = 1 + origin.y + 32 * y}) end else insert(positions, {x = -2 + origin.x + 32 * x, y = 1 + origin.y - radius * 32}) end end for x = -radius, -1 do if x == -radius then for y = 1, radius do insert(positions, {x = 1 + origin.x + 32 * x, y = -2 + origin.y + 32 * y}) end else insert(positions, {x = 1 + origin.x + 32 * x, y = -2 + origin.y + radius * 32}) end end for x = 0, radius do if x == radius then for y = 0, radius do insert(positions, {x = -2 + origin.x + 32* x, y = -2 + origin.y + 32 * y}) end else insert(positions, {x = -2 + origin.x + 32 * x, y = -2 + origin.y + radius * 32}) end end local stack = {name = ammo_name, count = 20} local tiles = {} local tile_name = script_data.config.prototypes.tile_2 if not game.tile_prototypes[tile_name] then tile_name = get_walkable_tile() end local floor = math.floor for k, position in pairs (positions) do if is_in_map(width, height, position) then local turret = surface.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 (surface.find_entities_filtered{area = turret.bounding_box, force = "neutral"}) do entity.destroy({do_cliff_correction = true}) end turret.insert(stack) 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 function create_wall_for_force(force) if not script_data.config.game_config.team_walls then return end if not force.valid then return end local surface = script_data.surface local height = surface.map_gen_settings.height / 2 local width = surface.map_gen_settings.width / 2 local origin = force.get_spawn_position(surface) local radius = get_starting_area_radius(true) - 11 if radius < 2 then return end local perimeter_top = {} local perimeter_bottom = {} local perimeter_left = {} local perimeter_right = {} local tiles = {} local insert = insert 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 = script_data.config.prototypes.tile_1 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 = script_data.config.prototypes.wall local gate_name = script_data.config.prototypes.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) 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) 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) 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) 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 spairs(t, order) local keys = {} for k in pairs(t) do keys[#keys+1] = k end if order then table.sort(keys, function(a, b) return order(t, a, b) end) else table.sort(keys) end local i = 0 return function() i = i + 1 if keys[i] then return keys[i], t[keys[i]] end end end function areas_overlap(area_1, area_2) local left_top = area_1[1] local right_bottom = area_1[2] local x1 = area_2[1][1] local x2 = area_2[2][1] local y1 = area_2[1][2] local y2 = area_2[2][2] if x1 > left_top[1] and x1 < right_bottom[1] then return true end if x2 > left_top[1] and x2 < right_bottom[1] then return true end if y1 > left_top[2] and y1 < right_bottom[2] then return true end if y2 > left_top[2] and y2 < right_bottom[2] then return true end return false end function duplicate_starting_area_entities(index) if not script_data.config.team_config.duplicate_starting_area_entities then return end if index == 1 then return end --Index 1 is the copy force... so we don't copy anything... local copy_team = script_data.config.teams[1] if not copy_team then return end local force = game.forces[copy_team.name] if not force then return end local destination_team = script_data.config.teams[index] local destination_force = game.forces[destination_team.name] if not destination_force then return end local surface = script_data.surface local origin_spawn = force.get_spawn_position(surface) local radius = get_starting_area_radius(true) --[[radius in tiles]] local copy_area = {{origin_spawn.x - radius, origin_spawn.y - radius}, {origin_spawn.x + radius, origin_spawn.y + radius}} local destination_spawn = destination_force.get_spawn_position(surface) local destination_area = {{destination_spawn.x - radius, destination_spawn.y - radius}, {destination_spawn.x + radius, destination_spawn.y + radius}} local tile_name = get_walkable_tile() local top_count = 0 for name, tile in pairs (game.tile_prototypes) do if not tile.collision_mask["resource-layer"] then local count = surface.count_tiles_filtered{name = name, area = destination_area} if count > top_count then top_count = count tile_name = name end end end local offset_x = destination_spawn.x - origin_spawn.x local offset_y = destination_spawn.y - origin_spawn.y --Fill in current water in destination area local set_tiles = {} local tile_count = 0 for k, tile in pairs (surface.find_tiles_filtered{area = destination_area, collision_mask = "resource-layer"}) do tile_count = tile_count + 1 set_tiles[tile_count] = {name = tile_name, position = {x = tile.position.x, y = tile.position.y}} end surface.set_tiles(set_tiles) --Copy water from copy area local set_water = {} local water_count = 0 for k, tile in pairs (surface.find_tiles_filtered{area = copy_area, collision_mask = "resource-layer"}) do water_count = water_count + 1 set_water[water_count] = {name = tile.name, position = {x = tile.position.x + offset_x, y = tile.position.y + offset_y}} end surface.set_tiles(set_water) local success = pcall(surface.clone_area, { source_area = copy_area, destination_area = destination_area, clone_entities = true, clear_destination = true, clone_tiles = false, clone_decoratives = true }) if not success then game.print({"duplicate-failed"}) log("Duplicating failed, probably due to poor map conditions") end end function create_starting_chest(force) if not (force and force.valid) then return end if not script_data.config.starting_chest then return end local value = script_data.config.starting_chest.selected local multiplier = script_data.config.team_config.starting_chest_multiplier if not (multiplier > 0) then return end local inventory = script_data.config.inventory_list[value] if not inventory then return end if not (table_size(inventory) > 0) then return end local surface = script_data.surface local chest_name = script_data.config.prototypes.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 = force.get_spawn_position(surface) local offset = script_data.config.chest_offset origin.x = origin.x + offset.x origin.y = origin.y + offset.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 = script_data.config.prototypes.tile_1 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 * multiplier) 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 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 get_walkable_tile() for name, tile in pairs (game.tile_prototypes) do if tile.collision_mask["player-layer"] == nil and not tile.items_to_place_this then return name end end error("No walkable tile in prototype list") 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 function create_exclusion_map() local surface = script_data.surface if not (surface and surface.valid) then return end local exclusion_map = {} local radius = get_starting_area_radius() --[[radius in chunks]] for k, team in pairs (script_data.config.teams) do local name = team.name local force = game.forces[name] if force then local origin = force.get_spawn_position(surface) local Xo = math.floor(origin.x / 32) local Yo = math.floor(origin.y / 32) for X = -radius, radius - 1 do Xb = X + Xo if not exclusion_map[Xb] then exclusion_map[Xb] = {} end for Y = -radius, radius - 1 do local Yb = Y + Yo exclusion_map[Xb][Yb] = name end end end end script_data.exclusion_map = exclusion_map end function check_base_exclusion() if not (script_data.check_base_exclusion or script_data.protected_teams) then return end if script_data.check_base_exclusion and game.tick > (script_data.round_start_tick + (script_data.config.game_config.base_exclusion_time * 60 * 60)) then script_data.check_base_exclusion = nil game.print({"base-exclusion-ends"}) end end function check_player_base_exclusion(player) if not (script_data.check_base_exclusion or script_data.protected_teams) then return end if not is_ignored_force(player.force.name) then check_player_exclusion(player, get_chunk_map_position(player.position)) end end function get_chunk_map_position(position) local map = script_data.exclusion_map local chunk_x = math.floor(position.x / 32) local chunk_y = math.floor(position.y / 32) if map[chunk_x] then return map[chunk_x][chunk_y] end end local disallow = { ["player"] = true, ["enemy"] = true, ["neutral"] = true, ["spectator"] = true } function is_ignored_force(name) return disallow[name] end function check_player_exclusion(player, force_name) if not force_name then return end local force = game.forces[force_name] if not (force and force.valid and player and player.valid) then return end if force == player.force or force.get_friend(player.force) then return end if not (script_data.check_base_exclusion or (script_data.protected_teams and script_data.protected_teams[force_name])) then return end local surface = script_data.surface local origin = force.get_spawn_position(surface) local radius = get_starting_area_radius(true) --[[radius in tiles]] local position = {x = player.position.x, y = player.position.y} local vector = {x = 0, y = 0} if position.x < origin.x then vector.x = (origin.x - radius) - position.x elseif position.x > origin.x then vector.x = (origin.x + radius) - position.x end if position.y < origin.y then vector.y = (origin.y - radius) - position.y elseif position.y > origin.y then vector.y = (origin.y + radius) - position.y end if math.abs(vector.x) < math.abs(vector.y) then vector.y = 0 else vector.x = 0 end local new_position = {x = position.x + vector.x, y = position.y + vector.y} local vehicle = player.vehicle if vehicle then if not vehicle.teleport(new_position) then player.driving = false end vehicle.orientation = vehicle.orientation + 0.5 else player.teleport(new_position) end if script_data.check_base_exclusion then local time_left = math.ceil((script_data.round_start_tick + (script_data.config.game_config.base_exclusion_time * 60 * 60) - game.tick) / 3600) player.print({"base-exclusion-teleport", time_left}) else player.print({"protected-base-area"}) end end local should_start = function() if script_data.ready_start_tick and script_data.ready_start_tick <= game.tick then return true end if not script_data.team_won then return false end local time = script_data.config.game_config.auto_new_round_time if not (time > 0) then return false end if game.tick < (script_data.config.game_config.auto_new_round_time * 60 * 60) + script_data.team_won then return false end return true end function check_restart_round() if not should_start() then return end end_round() start_round() end function team_won(name) script_data.team_won = game.tick if script_data.config.game_config.auto_new_round_time > 0 then game.print({"team-won-auto", name, script_data.config.game_config.auto_new_round_time}) else game.print({"team-won", name}) end script.raise_event(events.on_team_won, {name = name}) end function offset_respawn_position(player) --This is to help the spawn camping situations. if not (player and player.valid and player.character) then return end local surface = player.surface local origin = player.force.get_spawn_position(surface) local radius = get_starting_area_radius(true) - 32 if not (radius > 0) then return end local random_position = {origin.x + math.random(-radius, radius), origin.y + math.random(-radius, radius)} local position = surface.find_non_colliding_position(player.character.name, random_position, 32, 1) if not position then return end player.teleport(position) end function recursive_data_check(new_data, old_data) for k, data in pairs (new_data) do if old_data[k] == nil then old_data[k] = data elseif type(data) == "table" then recursive_data_check(new_data[k], old_data[k]) end end end function check_cursor_for_disabled_items(event) if not script_data.config.disabled_items then return end local player = game.players[event.player_index] if not (player and player.valid) then return end local stack = player.cursor_stack if (stack and stack.valid_for_read) then if script_data.config.disabled_items[stack.name] then stack.clear() end end end function recipe_picker_elem_update(player) if not (player and player.valid) then return end local production_score_frame = script_data.elements.production_score_frame[player.index] if not (production_score_frame and production_score_frame.valid) then return end local recipe_frame = script_data.elements.recipe_frame[player.index] if recipe_frame and recipe_frame.valid then deregister_gui(recipe_frame) recipe_frame.destroy() script_data.elements.recipe_frame[player.index] = nil end local elem_value = script_data.selected_recipe[player.index] local elem_button = script_data.elements.recipe_button[player.index] if (elem_button and elem_button.valid) then elem_button.elem_value = elem_value end if not elem_value then return end local recipe = player.force.recipes[elem_value] local recipe_frame = production_score_frame.add{type = "frame", direction = "vertical", style = "image_frame"} script_data.elements.recipe_frame[player.index] = recipe_frame local title_flow = recipe_frame.add{type = "flow"} title_flow.style.horizontal_align = "center" title_flow.style.horizontally_stretchable = true title_flow.add{type = "label", caption = recipe.localised_name, style = "caption_label"} local table = recipe_frame.add{type = "table", column_count = 2} table.draw_horizontal_line_after_headers = true table.draw_vertical_lines = true table.style.horizontal_spacing = 16 table.style.vertical_spacing = 2 table.style.left_padding = 4 table.style.right_padding = 4 table.style.top_padding = 4 table.style.bottom_padding = 4 table.style.column_alignments[1] = "center" table.style.column_alignments[2] = "center" table.add{type = "label", caption = {"ingredients"}, style = "bold_label"} table.add{type = "label", caption = {"products"}, style = "bold_label"} local ingredients = recipe.ingredients local products = recipe.products local prices = script_data.price_list local cost = 0 local gain = 0 local prototypes = { fluid = game.fluid_prototypes, item = game.item_prototypes } for k = 1, math.max(#ingredients, #products) do local ingredient = ingredients[k] local flow = table.add{type = "flow", direction = "horizontal"} if k == 1 then flow.style.top_padding = 8 end flow.style.vertical_align = "center" if ingredient then local ingredient_price = prices[ingredient.name] or 0 local calculator_button = flow.add { type = "sprite-button", sprite = ingredient.type.."/"..ingredient.name, number = ingredient.amount, style = "slot_button", tooltip = {"", "1 ", prototypes[ingredient.type][ingredient.name].localised_name, " = ", util.format_number(math.floor(ingredient_price * 100) / 100)} } register_gui_action(calculator_button, {type = "calculator_button_press", elem_type = ingredient.type, elem_name = ingredient.name}) local price = ingredient.amount * ingredient_price or 0 add_pusher(flow) flow.add{type = "label", caption = util.format_number(math.floor(price * 100) / 100)} cost = cost + price end local product = products[k] flow = table.add{type = "flow", direction = "horizontal"} if k == 1 then flow.style.top_padding = 8 end flow.style.vertical_align = "center" if product then local amount = product.amount or product.probability * (product.amount_max + product.amount_min) / 2 or 0 local product_price = prices[product.name] or 0 local calculator_button = flow.add { type = "sprite-button", sprite = product.type.."/"..product.name, number = amount, style = "slot_button", tooltip = {"", "1 ", prototypes[product.type][product.name].localised_name, " = ", util.format_number(math.floor(product_price * 100) / 100)}, show_percent_for_small_numbers = true } register_gui_action(calculator_button, {type = "calculator_button_press", elem_type = product.type, elem_name = product.name}) add_pusher(flow) local price = amount * product_price or 0 flow.add{type = "label", caption = util.format_number(math.floor(price * 100) / 100)} gain = gain + price end end local line = table.add{type = "table", column_count = 1} line.draw_horizontal_lines = true add_pusher(line) add_pusher(line) line.style.top_padding = 8 line.style.bottom_padding = 4 local line = table.add{type = "table", column_count = 1} line.draw_horizontal_lines = true add_pusher(line) add_pusher(line) line.style.top_padding = 8 line.style.bottom_padding = 4 local cost_flow = table.add{type = "flow"} cost_flow.add{type = "label", caption = {"", {"cost"}, {"colon"}}} add_pusher(cost_flow) cost_flow.add{type = "label", caption = util.format_number(math.floor(cost * 100) / 100)} local gain_flow = table.add{type = "flow"} gain_flow.add{type = "label", caption = {"", {"gain"}, {"colon"}}} add_pusher(gain_flow) gain_flow.add{type = "label", caption = util.format_number(math.floor(gain * 100) / 100)} table.add{type = "flow"} local total_flow = table.add{type = "flow"} total_flow.add{type = "label", caption = {"", {"total"}, {"colon"}}, style = "bold_label"} add_pusher(total_flow) local total = total_flow.add{type = "label", caption = util.format_number(math.floor((gain-cost) * 100) / 100), style = "bold_label"} if cost > gain then total.style.font_color = {r = 1, g = 0.3, b = 0.3} end end function add_pusher(gui) local pusher = gui.add{type = "flow"} pusher.style.horizontally_stretchable = true end function check_on_built_protection(event) if not script_data.config.game_config.enemy_building_restriction then return end local entity = event.created_entity local player = game.players[event.player_index] if not (entity and entity.valid and player and player.valid) then return end local force = entity.force local name = get_chunk_map_position(entity.position) if not name then return end if force.name == name then return end local other_force = game.forces[name] if not other_force then return end if other_force.get_friend(force) then return end if not player.mine_entity(entity, true) then entity.destroy() end player.print({"enemy-building-restriction"}) end function check_defcon() if not script_data.config.team_config.defcon_mode then return end local defcon_tick = script_data.last_defcon_tick if not defcon_tick then script_data.last_defcon_tick = game.tick return end local duration = math.max(60, (script_data.config.team_config.defcon_timer * 60 * 60)) local tick_of_defcon = defcon_tick + duration local current_tick = game.tick local progress = math.max(0, math.min(1, 1 - (tick_of_defcon - current_tick) / duration)) local tech = script_data.next_defcon_tech if tech and tech.valid then for k, team in pairs (script_data.config.teams) do local force = game.forces[team.name] if force then if force.current_research ~= tech.name then force.current_research = tech.name end force.research_progress = progress end end end if current_tick >= tick_of_defcon then defcon_research() script_data.last_defcon_tick = current_tick end end recursive_technology_prerequisite = function(tech) for name, prerequisite in pairs (tech.prerequisites) do if not prerequisite.researched then return recursive_technology_prerequisite(prerequisite) end end return tech end function defcon_research() local tech = script_data.next_defcon_tech if tech and tech.valid then for k, team in pairs (script_data.config.teams) do local force = game.forces[team.name] if force then local tech = force.technologies[tech.name] if tech then tech.researched = true end end end local sound = "utility/research_completed" if game.is_valid_sound_path(sound) then game.play_sound({path = sound}) end game.print({"defcon-unlock", tech.localised_name}, {r = 1, g = 0.5, b = 0.5}) end local force for k, team in pairs (script_data.config.teams) do force = game.forces[team.name] if force and force.valid then break end end if not force then return end local available_techs = {} for name, tech in pairs (force.technologies) do if tech.enabled and tech.researched == false then insert(available_techs, tech) end end if #available_techs == 0 then return end local random_tech = available_techs[math.random(#available_techs)] if not random_tech then return end random_tech = recursive_technology_prerequisite(random_tech) script_data.next_defcon_tech = game.technology_prototypes[random_tech.name] for k, team in pairs (script_data.config.teams) do local force = game.forces[team.name] if force then force.current_research = random_tech.name end end end function check_neutral_chests(event) if not script_data.config.game_config.neutral_chests then return end local entity = event.created_entity if not (entity and entity.valid) then return end if entity.type == "container" then entity.force = "neutral" end end function on_calculator_button_press(event, param) local gui = event.element if not (gui and gui.valid) then return end local player = game.players[event.player_index] if not (player and player.valid) then return end local type = param.elem_type local elem_name = param.elem_name local items = game.item_prototypes local fluids = game.fluid_prototypes local recipes = game.recipe_prototypes if type == "item" then if not items[elem_name] then return end elseif type == "fluid" then if not fluids[elem_name] then return end else return end local selected = script_data.selected_recipe[player.index] local candidates = {} for name, recipe in pairs (recipes) do for k, product in pairs (recipe.products) do if product.type == type and product.name == elem_name then insert(candidates, name) end end end if #candidates == 0 then return end local index = 0 for k, name in pairs (candidates) do if name == selected then index = k break end end local recipe_name = candidates[index + 1] or candidates[1] if not recipe_name then return end script_data.selected_recipe[player.index] = recipe_name recipe_picker_elem_update(player) 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 action then gui_functions[action.type](event, action) return true end end function update_timers() local caption = {"time_left", get_time_left()} for k, timer in pairs (script_data.timers) do if timer.valid then timer.caption = caption end end end local on_rocket_launched = function(event) if not script_data.config.victory.space_race then return end local sent = script_data.rocket_scores local name = event.rocket.force.name sent[name] = sent[name] + 1 check_update_space_race_score() end local check_last_silo_standing_victory = function(event) if not script_data.config.victory.last_silo_standing then return end local silo = event.entity if not (silo and silo.valid and silo.name == (script_data.config.prototypes.silo or "") ) then return end local killing_force = event.force local force = silo.force if not script_data.silos then return end script_data.silos[force.name] = nil if killing_force then game.print({"silo-destroyed", force.name, killing_force.name}) else game.print({"silo-destroyed", force.name, {"neutral"}}) end script.raise_event(events.on_team_lost, {name = force.name}) for k, player in pairs (force.players) do player.force = "neutral" player.set_controller{type = defines.controllers.spectator} end game.merge_forces(force, "neutral") if not script_data.team_won then local index = 0 local winner_name = {"none"} for name, listed_silo in pairs (script_data.silos) do if listed_silo ~= nil then index = index + 1 winner_name = name end end if index == 1 then team_won(winner_name) end end end local update_kill_score = function(event) if not script_data.config.victory.kill_score then return end local entity = event.entity if not (entity and entity.valid) then return end local cause_force = event.force if not (cause_force and cause_force.valid) then return end local scores = script_data.kill_scores local prices = script_data.entity_prices if not prices then prices = kill_score.generate_entity_prices() script_data.entity_prices = prices end local force_name = cause_force.name scores[force_name] = scores[force_name] + (prices[entity.name] or 0) end local on_entity_died = function(event) update_kill_score(event) check_last_silo_standing_victory(event) end local on_player_joined_game = function(event) local player = game.players[event.player_index] if not (player and player.valid) then return end player.spectator = true init_player_gui(player) if player.force.name ~= "player" then --If they are not on the player force, they have already picked a team this round. check_force_protection(player.force) for k, other_player in pairs (game.connected_players) do update_team_list_frame(other_player) end return end local character = player.character player.character = nil if character then character.destroy() end player.set_controller{type = defines.controllers.spectator} player.teleport({0, 1000}, game.surfaces.Lobby) end local on_player_left_game = function(event) local player = game.players[event.player_index] if script_data.setup_finished then for k, player in pairs (game.connected_players) do local gui = player.gui.center choose_joining_gui(player) choose_joining_gui(player) update_team_list_frame(player) end check_force_protection(force) else refresh_config(event.player_index) end destroy_player_gui(player) end local on_tick = function(event) if script_data.setup_finished == false then check_starting_area_chunks_are_generated() finish_setup() end end local on_player_respawned = function(event) local player = game.players[event.player_index] if not (player and player.valid) then return end if script_data.setup_finished == true then config.give_equipment(player) offset_respawn_position(player) balance.apply_character_modifiers(player) else if player.character then player.character.destroy() end end end local on_player_display_resolution_changed = function(event) local player = game.players[event.player_index] init_player_gui(player) end local on_research_finished = function(event) check_technology_for_disabled_items(event) end local on_player_cursor_stack_changed = function(event) check_cursor_for_disabled_items(event) end local on_built_entity = function(event) check_on_built_protection(event) check_neutral_chests(event) end local on_robot_built_entity = function(event) check_neutral_chests(event) end local on_research_started = function(event) if script_data.config.team_config.defcon_mode then local tech = script_data.next_defcon_tech if tech and tech.valid and event.research.name ~= tech.name then event.research.force.current_research = nil end end end local on_player_promoted = function(event) local player = game.players[event.player_index] init_player_gui(player) end local on_player_demoted = function(event) local player = game.players[event.player_index] init_player_gui(player) end local on_forces_merged = function (event) create_exclusion_map() end local on_player_changed_position = function(event) local player = game.players[event.player_index] check_player_base_exclusion(player) check_player_no_rush(player) end local check_spectator_chart = function() local force = game.forces.spectator if not (force and force.valid) then return end local surface = script_data.surface if not (surface and surface.valid) then return end force.chart_all(script_data.surface) end local pvp = {} pvp.add_remote_interface = function() remote.add_interface("pvp", { get_event_name = function(name) return events[name] end, get_events = function() return events end, get_teams = function() return script_data.config.teams end, get_config = function() return script_data.config end, set_config = function(array) log("PvP global config set by remote call - Can expect script errors after this point.") for k, v in pairs (array) do script_data.config[k] = v end end }) end pvp.on_nth_tick = { [60] = function(event) if script_data.setup_finished == true then check_no_rush() check_update_production_score() check_update_oil_harvest_score() check_update_space_race_score() check_update_kill_score() check_base_exclusion() check_defcon() update_timers() end check_restart_round() end, [300] = function(event) if script_data.setup_finished == true then check_player_color() check_spectator_chart() end end } pvp.on_init = function() script_data.config = config.get_config() global.pvp = script_data balance.script_data = script_data config.script_data = script_data balance.init() local surface = game.create_surface("Lobby",{width = 1, height = 1}) surface.set_tiles({{name = "out-of-map",position = {1,1}}}) for k, force in pairs (game.forces) do force.disable_all_prototypes() force.disable_research() end end pvp.on_load = function() script_data = global.pvp or script_data balance.script_data = script_data config.script_data = script_data end pvp.on_configuration_changed = function(event) recursive_data_check(config.get_config(), script_data.config) end local script_events = { [defines.events.on_built_entity] = on_built_entity, [defines.events.on_chunk_generated] = on_chunk_generated, [defines.events.on_entity_died] = on_entity_died, [defines.events.on_forces_merged] = on_forces_merged, [defines.events.on_gui_checked_state_changed] = generic_gui_event, [defines.events.on_gui_click] = generic_gui_event, [defines.events.on_gui_closed] = generic_gui_event, [defines.events.on_gui_elem_changed] = 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_player_changed_position] = on_player_changed_position, [defines.events.on_player_cursor_stack_changed] = on_player_cursor_stack_changed, [defines.events.on_player_demoted] = on_player_demoted, [defines.events.on_player_display_resolution_changed] = on_player_display_resolution_changed, [defines.events.on_player_display_scale_changed] = on_player_display_resolution_changed, [defines.events.on_player_joined_game] = on_player_joined_game, [defines.events.on_player_left_game] = on_player_left_game, [defines.events.on_player_promoted] = on_player_promoted, [defines.events.on_player_respawned] = on_player_respawned, [defines.events.on_research_finished] = on_research_finished, [defines.events.on_research_started] = on_research_started, [defines.events.on_robot_built_entity] = on_robot_built_entity, [defines.events.on_rocket_launched] = on_rocket_launched, [defines.events.on_tick] = on_tick } pvp.on_event = function(event) local action = script_events[event.name] if not action then return end return action(event) end pvp.get_events = function() return script_events end return pvp