enemy7's picture
Upload 1553 files
898c672
local mod_gui = require("mod-gui")
local util = require("util")
local get_walkable_tile = util.get_walkable_tile
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 = {},
team_tab = {},
game_tab = {}
},
setup_finished = false,
ready_players = {},
config = {},
round_number = 0,
selected_recipe = {},
random = nil,
team_names = {}
}
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
local lobby_name = "Lobby"
local get_lobby_surface = function()
if game.surfaces[lobby_name] then return game.surfaces[lobby_name] end
local surface = game.create_surface(lobby_name, {width = 1, height = 1})
surface.set_tiles({{name = "out-of-map", position = {1,1}}})
return surface
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() + 1
local diameter = radius * 32 * 2 * math.sqrt(2)
local count = #script_data.config.teams
if count == 1 then
local positions =
{
{x = 0, y = 0}
}
script_data.spawn_offset = positions[1]
script_data.spawn_positions = positions
return positions
end
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 random = script_data.random
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 + random(math.floor(-horizontal_offset), math.floor(horizontal_offset)) / 32) * 32,
y = math.floor(0.5 + random(math.floor(-vertical_offset), math.floor(vertical_offset)) / 32) * 32
}
local distance = 0.5 * displacement
local positions = {}
local rotation_offset = (script_data.random() * (math.pi * 2))
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()
settings.seed = script_data.config.game_config.seed
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
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"}
title_flow.style.vertical_align = "center"
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"}
label.style.font_color = get_color(team, true)
add_pusher(show_flow)
if admin then
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 = 150
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
add_pusher(edit_flow)
local textfield_confirm = edit_flow.add{type = "sprite-button", style = "item_and_count_select_confirm", sprite = "utility/confirm_slot"}
textfield_confirm.style.padding = 2
textfield_confirm.style.margin = 0
local textfield_cancel = edit_flow.add{type = "sprite-button", style = "tool_button_red", sprite = "utility/reset"}
local edit_button = title_flow.add{type = "sprite-button", style = "tool_button", sprite = "utility/rename_icon_small_black"}
local line = title_flow.add{type = "line", direction = "vertical"}
line.style.height = 32
line.style.vertically_stretchable = false
local delete_button = title_flow.add{type = "sprite-button", style = "tool_button_red", sprite = "utility/trash", enabled = #script_data.config.teams > 1}
register_gui_action(textfield_cancel, {type = "cancel_rename", edit_flow = edit_flow, show_flow = show_flow, buttons = {edit_button, delete_button}})
register_gui_action(edit_button, {type = "rename_team", edit_flow = edit_flow, show_flow = show_flow, buttons = {edit_button, delete_button}})
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 line = frame.add{type = "line", direction = "horizontal"}
local team_table = frame.add{type = "table", column_count = 2}
team_table.add{type = "label", caption = {"", {"team"}, {"colon"}}, style = "description_label"}
if admin then
local drop_down = team_table.add{type = "drop-down"}
local selected_index = 1
drop_down.add_item({"no-team"})
drop_down.add_item({"random-team"})
for k = 1, #script_data.config.teams do
drop_down.add_item(k)
if k == team.team then
selected_index = k + 2
end
end
drop_down.selected_index = selected_index
register_gui_action(drop_down, {type = "team_drop_down", team = team})
else
local caption
if team.team == "-" then
caption = {"no-team"}
elseif team.team == "?" then
caption = {"random-team"}
else
caption = team.team
end
team_table.add{type = "label", caption = caption}
end
local label = team_table.add{type = "label", caption = {"members"}, style = "description_label"}
label.style.minimal_width = 150
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 ready == "" then
ready = {"none"}
end
local label = team_table.add{type = "label", caption = 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"})
end
end
refresh_config = function(excluded_player_index)
for k, player in pairs (game.connected_players) do
if player.index ~= excluded_player_index then
update_game_tab(player)
update_team_tab(player)
update_balance_tab(player)
update_inventory_tab(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, strict)
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 strict then return number >= 100 and number <= 254000 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
update_inventory_tab = function(player)
local group = script_data.elements.inventory[player.index]
if not (group and group.valid) then return end
group.clear()
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", style = "bordered_frame", direction = "vertical"}
inner.style.minimal_width = 500
local top_flow = inner.add{type = "flow"}
top_flow.add{type = "label", caption = param.name, style = "caption_label"}
top_flow.style.vertical_align = "center"
add_pusher(top_flow)
local selected = options.selected
if admin then
local dropdown = top_flow.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
top_flow.add{type = "label", caption = {selected}}
end
local scroll = inner.add{type = "scroll-pane", style = "scroll_pane_in_shallow_frame"}
scroll.style.margin = 4
local items = data[selected]
if not items then return end
if next(items) then
local prototypes = game.item_prototypes
local item_table = scroll.add{type = "table", column_count = 2, style = "bordered_table"}
item_table.style.horizontally_stretchable = true
for name, count in pairs (items) do
local prototype = prototypes[name]
if prototype then
local flow = item_table.add{type = "flow"}
flow.style.vertical_align = "center"
if admin then
local elem = flow.add{type = "choose-elem-button", elem_type = "item", style = "slot_button_in_shallow_frame"}
elem.elem_value = name
register_gui_action(elem, {type = "starting_item_elem_changed", items = items, previous = name})
else
local sprite = flow.add{type = "sprite", sprite = "item/"..name, style = "small_text_image"}
end
flow.add{type = "label", caption = prototype.localised_name}
add_pusher(flow)
if admin then
local text = flow.add{type = "textfield", text = count, numeric = true, allow_decimal = false, allow_negative = false, style = "slider_value_textfield"}
register_gui_action(text, {type = "starting_item_textfield_changed", items = items, key = name})
else
flow.add{type = "label", caption = count}
end
end
end
end
if admin then
local elem = inner.add{type = "choose-elem-button", elem_type = "item", style = "slot_button_in_shallow_frame"}
--local pusher = scroll.add{type = "empty-widget"}
--pusher.style.vertically_stretchable = true
register_gui_action(elem, {type = "starting_item_elem_changed", items = items})
end
end
end
end
add_starting_chest_tab = function(tab_pane)
local tab = tab_pane.add{type = "tab", caption = {"starting-items"}}
local group = tab_pane.add{type = "flow"}
tab_pane.add_tab(tab, group)
local player = game.get_player(tab_pane.player_index)
script_data.elements.inventory[player.index] = group
update_inventory_tab(player)
end
local set_team = function(player_index, team)
local current_team = script_data.team_players[player_index]
if current_team then
current_team.members[player_index] = nil
end
if team then
team.members[player_index] = game.get_player(player_index)
script_data.team_players[player_index] = team
else
script_data.team_players[player_index] = nil
end
script_data.ready_players[player_index] = nil
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
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
for k, button in pairs (param.buttons) do
button.enabled = false
end
end,
cancel_rename = function(event, param)
param.edit_flow.visible = false
param.show_flow.visible = true
for k, button in pairs (param.buttons) do
button.enabled = true
end
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)
set_team(event.player_index, param.team)
refresh_config()
check_all_ready()
end,
leave_team = function(event, param)
set_team(event.player_index)
refresh_config()
check_all_ready()
end,
team_drop_down = function(event, param)
local gui = event.element
if not (gui and gui.valid) then return end
if event.name ~= defines.events.on_gui_selection_state_changed then return end
local index
if gui.selected_index == 1 then
index = "-"
elseif gui.selected_index == 2 then
index = "?"
else
index = gui.selected_index - 2
end
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"
textfield.style.horizontal_align = "center"
return
end
textfield.style = "slider_value_textfield"
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,
victory_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].active = 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_config()
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
text = text:gsub("%%", "")
local valid = is_text_valid(text, param.no_below_100)
if not valid then
textfield.style = "invalid_value_textfield"
textfield.style.horizontal_align = "center"
textfield.style.maximal_width = 60
return
end
textfield.style = "slider_value_textfield"
textfield.style.maximal_width = 60
local value = (text - 100) / 100
script_data.config.modifier_list[param.modifier][param.key] = value
refresh_config(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.screen
local frame = gui.add{type = "frame", caption = {"gui-blueprint-library.import-string"}, direction = "vertical"}
frame.auto_center = true
local old = script_data.elements.import[player.index]
if (old and old.valid) then old.destroy() end
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", style = "dialog_buttons_horizontal_flow"}
register_gui_action
(
flow.add{type = "button", caption = {"gui.close"}, style = "dialog_button"},
{type = "import_export_close", frame = frame}
)
local pusher = flow.add{type = "empty-widget", style = "draggable_space"}
pusher.style.horizontally_stretchable = true
pusher.style.vertically_stretchable = true
pusher.drag_target = frame
local confirm_button = flow.add{type = "button", caption = {"gui-blueprint-library.import"}, style = "confirm_button"}
confirm_button.style.minimal_width = 250
register_gui_action
(
confirm_button,
{type = "import_confirm", frame = frame, textfield = textfield}
)
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(game.decode_string(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
local default_config = config.get_config()
--We don't want to always append the new starting items to the default ones, so just clear them here.
default_config.inventory_list = {}
default_config.equipment_list = {}
recursive_data_check(default_config, script_data.config)
refresh_config()
deregister_gui(frame)
frame.destroy()
script_data.elements.import[player.index] = nil
player.print({"import-success"})
log("Pvp config import success")
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.screen
local frame = gui.add{type = "frame", caption = {"gui.export-to-string"}, direction = "vertical"}
frame.auto_center = true
local old = script_data.elements.import[player.index]
if (old and old.valid) then old.destroy() end
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 = game.encode_string(game.table_to_json(data))
local flow = frame.add{type = "flow", style = "dialog_buttons_horizontal_flow"}
register_gui_action
(
flow.add{type = "button", caption = {"gui.close"}, style = "dialog_button"},
{type = "import_export_close", frame = frame}
)
local pusher = flow.add{type = "empty-widget", style = "draggable_space_with_no_right_margin"}
pusher.style.horizontally_stretchable = true
pusher.style.vertically_stretchable = true
pusher.drag_target = frame
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
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"
textfield.style.horizontal_align = "center"
return
end
textfield.style = "slider_value_textfield"
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_config()
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_config()
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", style = "recipe_slot_button"}, {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,
join_random = function(event, param)
local frame = param.frame
if (frame and frame.valid) then
deregister_gui(frame)
frame.destroy()
end
local player = game.get_player(event.player_index)
local teams = get_eligible_teams(player)
if not teams then return end
local team = teams[math.random(#teams)]
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,
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", style = mod_gui.frame_style, caption = {"admin"}, direction = "vertical"}
script_data.elements.admin[player.index] = frame
frame.visible = true
local inner = frame.add{type = "frame", direction = "vertical", style = "window_content_frame_deep"}
register_gui_action(inner.add{type = "button", caption = {"end-round"}, tooltip = {"end-round-tooltip"}}, {type = "admin_end_round"})
register_gui_action(inner.add{type = "button", caption = {"reroll-round"}, tooltip = {"reroll-round-tooltip"}}, {type = "admin_reroll_round"})
register_gui_action(inner.add{type = "button", caption = {"restart-round"}, tooltip = {"restart-round-tooltip"}}, {type = "admin_restart_round"})
register_gui_action(inner.add{type = "button", caption = {"admin-change-team"}, tooltip = {"admin-change-team-tooltip"}}, {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)
game.print({"round-rerolled"})
end_round()
script_data.config.game_config.seed = math.random(2^32) - 1
start_round()
return
end,
admin_restart_round = function(event, param)
game.print({"round-restarted"})
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", style = mod_gui.frame_style, 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", style = mod_gui.frame_style, caption = {"production_score"}, direction = "vertical"}
script_data.elements.production_score_frame[player.index] = frame
frame.style.vertically_stretchable = false
local inner_frame = frame.add{type = "frame", style = "inside_shallow_frame", direction = "vertical"}
script_data.elements.production_score_inner_frame[player.index] = inner_frame
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", style = "slot_button"}
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", style = mod_gui.frame_style, 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", style = mod_gui.frame_style, 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", style = mod_gui.frame_style, 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"}
bool_flow.style.horizontally_stretchable = true
--local bottom_frame = flow.add{type = "frame", style = "bordered_frame_bottom"}
local other_flow = flow.add{type = "table", column_count = 1, style = "bordered_table"}
--other_flow.style.column_alignments[2] = "right"
--other_flow.style.column_alignments[1] = "right"
--other_flow.style.maximal_width = 350
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
local flow = other_flow.add{type = "table", column_count = 2}
flow.style.column_alignments[2] = "right"
local label = flow.add{type = "label", caption = config.localised_names[name] or {name}, tooltip = config.localised_tooltips[name] or {name.."_tooltip"}}
label.style.horizontally_stretchable = true
--local pusher = flow.add{type = "empty-widget"}
--pusher.style.horizontally_stretchable = true
if admin then
text = flow.add{type = "textfield", text = value, numeric = true, allow_negative = false, allow_decimal = true, style = "slider_value_textfield"}
text.style.maximal_width = 100
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
local flow = other_flow.add{type = "table", column_count = 2}
flow.style.column_alignments[2] = "right"
local label = flow.add{type = "label", caption = config.localised_names[name] or {name}, tooltip = config.localised_tooltips[name] or {name.."_tooltip"}}
label.style.horizontally_stretchable = true
--local pusher = flow.add{type = "empty-widget"}
--pusher.style.horizontally_stretchable = true
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 = (items[value.selected] and items[value.selected].localised_name) or {value.selected}}
end
end
end
local pusher = other_flow.add{type = "empty-widget"}
pusher.style.vertically_stretchable = true
end
function add_victory_gui(config_data, flow, admin)
local flow = flow.add{type = "table", column_count = 1, style = "bordered_table"}
flow.style.width = 500
flow.style.column_alignments[1] = "left"
flow.add{type = "label", caption = {"victory-conditions"}, style = "caption_label"}
for name, victory in pairs (config_data) do
local inner_flow = flow.add{type = "flow"}
inner_flow.style.height = 28
inner_flow.style.vertical_align = "center"
local check = inner_flow.add{type = "checkbox", state = victory.active, caption = config.localised_names[name] or {name}, ignored_by_interaction = not admin, tooltip = config.localised_tooltips[name] or {name.."_tooltip"}}
check.style.width = 150
check.style.vertical_align = "center"
register_gui_action(check, {type = "victory_config_boolean_changed", config = config_data, key = name})
for extra_name, extra in pairs (victory) do
if extra_name ~= "active" then
add_pusher(inner_flow)
local line = inner_flow.add{type = "line", direction = "vertical"}
line.style.vertically_stretchable = true
local label = inner_flow.add{type = "label", caption = config.localised_names[extra_name] or {extra_name}, tooltip = config.localised_tooltips[extra_name] or {extra_name.."_tooltip"}}
label.style.width = 180
if admin then
text = inner_flow.add{type = "textfield", text = extra, numeric = true, allow_negative = false, allow_decimal = false, style = "slider_value_textfield"}
register_gui_action(text, {type = "config_text_value_changed", config = victory, key = extra_name})
else
inner_flow.add{type = "label", caption = extra}
end
end
end
end
end
function add_team_tab(tab_pane)
local tab = tab_pane.add{type = "tab", caption = {"team-settings"}}
local group = tab_pane.add{type = "flow"}
tab_pane.add_tab(tab, group)
local player = game.get_player(tab_pane.player_index)
script_data.elements.team_tab[player.index] = group
update_team_tab(player)
end
function update_team_tab(player)
local admin = player.admin
local holding_table_1 = script_data.elements.team_tab[player.index]
if not (holding_table_1 and holding_table_1.valid) then return end
holding_table_1.clear()
local team_lobby = holding_table_1.add{type = "flow", direction = "vertical"}
team_lobby.style.vertically_stretchable = true
local title_flow = team_lobby.add{type = "frame", style = "bordered_frame"}
title_flow.style.vertical_align = "center"
local label = title_flow.add{type = "label", caption = {"teams"}, style = "caption_label"}
label.style.height = 28
label.style.vertical_align = "center"
if admin then
add_pusher(title_flow)
local button = title_flow.add{type = "button", caption = {"add-team"}, tooltip = {"add-team-tooltip"}, enabled = #script_data.config.teams < 24}
register_gui_action(button, {type = "new_team", frame = flow})
end
local scroll = team_lobby.add{type = "scroll-pane", direction = "vertical", style = "scroll_pane_in_shallow_frame"}
scroll.style.maximal_height = 440 + 20
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
local pusher = team_lobby.add{type = "empty-widget"}
pusher.style.vertically_stretchable = true
local bottom_frame = team_lobby.add{type = "frame", style = "bordered_frame"}
bottom_frame.add{type = "label", caption = {"unassigned-players", str}}
bottom_frame.style.horizontally_stretchable = true
local team_settings = holding_table_1.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 = true
local line = team_settings.add{type = "line", direction = "horizontal"}
line.style.horizontally_stretchable = true
add_new_config_gui(script_data.config.team_config, team_settings, admin)
end
function add_game_tab(tab_pane)
local tab = tab_pane.add{type = "tab", caption = {"game-settings"}}
local group = tab_pane.add{type = "flow"}
tab_pane.add_tab(tab, group)
local player = game.get_player(tab_pane.player_index)
script_data.elements.game_tab[player.index] = group
update_game_tab(player)
end
function update_game_tab(player)
local admin = player.admin
local holding_table_2 = script_data.elements.game_tab[player.index]
if not (holding_table_2 and holding_table_2.valid) then return end
holding_table_2.clear()
local game_settings = holding_table_2.add{type = "table", column_count = 1, style = "bordered_table"}
game_settings.add{type = "label", caption = {"game-settings"}, style = "caption_label"}
game_settings.style.vertically_stretchable = true
game_settings.style.horizontally_stretchable = true
local inner_table = game_settings.add{type = "flow", column_count = 2}
add_new_config_gui(script_data.config.game_config, inner_table, admin)
local other_flow = holding_table_2.add{type = "flow", direction = "vertical"}
other_flow.style.vertically_stretchable = true
other_flow.style.horizontally_stretchable = false
local victory = other_flow.add{type = "flow", direction = "vertical"}
--victory.style.vertically_stretchable = true
add_victory_gui(script_data.config.victory, victory, admin)
local disable_items = other_flow.add{type = "flow"}
disable_items.style.vertically_stretchable = true
disable_items.style.horizontally_stretchable = true
create_disable_frame(disable_items)
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.screen
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 deep = upper_frame.add{type = "frame", style = "inside_deep_frame_for_tabs", direction = "vertical"}
local tab_pane = deep.add{type = "tabbed-pane"}
tab_pane.style.horizontally_stretchable = true
tab_pane.selected_tab_index = 1
tab_pane.style.maximal_height = 1080 * 0.8
add_team_tab(tab_pane)
add_game_tab(tab_pane)
add_balance_tab(tab_pane)
add_starting_chest_tab(tab_pane)
local footer = deep.add{type = "frame", style = "subfooter_frame"}
footer.style.horizontally_stretchable = true
if admin then
register_gui_action(footer.add{type = "sprite-button", sprite = "utility/import", tooltip = {"gui-blueprint-library.import-string"}, style = "tool_button"}, {type = "pvp_import"})
end
register_gui_action(footer.add{type = "sprite-button", sprite = "utility/export", tooltip = {"gui.export-to-string"}, style = "tool_button"}, {type = "pvp_export"})
local button_flow = upper_frame.add{type = "flow", style = "dialog_buttons_horizontal_flow"}
button_flow.style.vertical_align = "center"
local pusher = button_flow.add{type = "empty-widget", style = "draggable_space_with_no_left_margin"}
pusher.style.horizontally_stretchable = true
pusher.style.vertically_stretchable = true
pusher.drag_target = upper_frame
local ready = script_data.ready_players[player.index] or false
local ready_up = button_flow.add{type = "checkbox", caption = {"ready"}, state = ready}
ready_up.style.right_padding = 8
register_gui_action(ready_up, {type = "ready_up"})
local start_button = button_flow.add{type = "button", style = "confirm_button", caption = {"start-round"}, enabled = admin}
start_button.style.minimal_width = 250
register_gui_action(start_button, {type = "start_round"})
upper_frame.auto_center = true
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}, get_lobby_surface())
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
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 script_data.setup_finished == false 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].active 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 == "neutral" 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 index = script_data.config.color_map[team.color]
if not index then
--Unknown color
team.color = script_data.config.colors[math.random(#script_data.config.colors)].name
index = script_data.config.color_map[team.color]
end
local c = script_data.config.colors[index].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", style = "scroll_pane_in_shallow_frame"}
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("character", force.get_spawn_position(surface), get_starting_area_radius(true), 2)
if not position then
player.print({"cant-find-position"})
choose_joining_gui(player)
return
end
local character = player.surface == surface and player.character
if character then
character.teleport(position)
else
character = surface.create_entity{name = "character", position = position, force = force}
end
player.force = force
player.show_on_map = true
player.teleport(position, surface)
player.character = nil
player.set_controller
{
type = defines.controllers.character,
character = character
}
player.color = get_color(team)
player.chat_color = get_color(team, true)
player.tag = "["..force.name.."]"
init_player_gui(player)
set_team(player.index, 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
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.screen
local frame = gui.add{type = "frame", direction = "vertical"}
local title_flow = frame.add{type = "flow", direction = "horizontal"}
title_flow.style.horizontally_stretchable = true
title_flow.style.horizontal_spacing = 8
local title_label = title_flow.add{type = "label", caption = {"pick-join"}, style = "frame_title"}
title_label.drag_target = frame
local title_pusher = title_flow.add{type = "empty-widget", style = "draggable_space_header"}
title_pusher.style.height = 24
title_pusher.style.horizontally_stretchable = true
title_pusher.drag_target = frame
--If they are on player force, it means they aren't on a proper team already, don't let them close the choose team frame.
if player.force.name ~= "player" then
local title_close_button = title_flow.add{type = "sprite-button", style = "frame_action_button", sprite = "utility/close_white"}
register_gui_action(title_close_button, {type = "spectator_join_team_button"})
end
script_data.elements.join[player.index] = frame
local inner_frame = frame.add{type = "frame", style = "inside_shallow_frame", direction = "vertical"}
local pick_join_table = inner_frame.add{type = "table", column_count = 4, style = "bordered_table"}
pick_join_table.style.margin = 4
pick_join_table.style.column_alignments[2] = "center"
pick_join_table.style.column_alignments[3] = "center"
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"}}
register_gui_action(join_button, {type = "pick_team", team = team})
end
end
local button_flow = frame.add{type = "flow", direction = "horizontal", style = "dialog_buttons_horizontal_flow"}
register_gui_action(button_flow.add{type = "button", caption = {"join-spectator"}}, {type = "join_spectator", frame = frame})
register_gui_action(button_flow.add{type = "button", caption = {"join-random"}}, {type = "join_random", frame = frame})
local drag = button_flow.add{type = "empty-widget", style = "draggable_space_with_no_right_margin"}
drag.style.horizontally_stretchable = true
drag.style.vertically_stretchable = true
drag.drag_target = frame
frame.auto_center = true
end
function update_balance_tab(player)
local inner = script_data.elements.balance[player.index]
if not (inner and inner.valid) then return end
inner.clear()
local scrollpane = inner.add{type = "scroll-pane", style = "scroll_pane_in_shallow_frame"}
local big_table = scrollpane.add{type = "table", column_count = 5, direction = "horizontal"}
big_table.style.horizontally_stretchable = true
local entities = game.entity_prototypes
local ammos = game.ammo_category_prototypes
local admin = player.admin
local modifier_list = script_data.config.modifier_list
if not modifier_list then
balance.init()
modifier_list = script_data.config.modifier_list
end
for modifier_name, array in pairs (modifier_list) do
local flow = big_table.add{type = "table", style = "bordered_table", column_count = 1}
flow.style.vertically_stretchable = true
flow.style.horizontally_stretchable = true
flow.add{type = "label", style = "caption_label", caption = {modifier_name}}
local inner = flow.add{type = "flow", direction = "vertical"}
inner.style.vertically_stretchable = true
local table = inner.add{type = "table", column_count = 2}
table.style.column_alignments[1] = "left"
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}
elseif modifier_name == "gun_speed_modifier" then
table.add{type = "label", caption = ammos[name].localised_name}
elseif modifier_name == "turret_attack_modifier" then
table.add{type = "label", caption = entities[name].localised_name}
elseif modifier_name == "character_modifiers" then
table.add{type = "label", caption = {name}}
elseif modifier_name == "force_modifiers" then
table.add{type = "label", caption = {name}}
end
if admin then
local input = table.add{type = "textfield", numeric = true, allow_decimal = true, allow_negative = false, style = "slider_value_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
end
end
end
function add_balance_tab(tab_pane)
local tab = tab_pane.add{type = "tab", caption = {"balance-options"}}
local inner = tab_pane.add{type = "flow"}
inner.style.horizontally_stretchable = true
tab_pane.add_tab(tab, inner)
local player = game.get_player(tab_pane.player_index)
script_data.elements.balance[player.index] = inner
update_balance_tab(player)
end
function create_disable_frame(gui)
local inner = gui.add{type = "table", style = "bordered_table", column_count = 1}
inner.add{type = "label", caption = {"disabled-items"}, style = "caption_label"}
local frame = inner.add{type = "frame", style = "filter_scroll_pane_background_frame"}
frame.style.width = 12 * 40
local disable_table = frame.add{type = "table", column_count = 12, style = "filter_slot_table"}
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", style = "recipe_slot_button"}
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", style = "recipe_slot_button"}
register_gui_action(choose, {type = "disable_elem_changed"})
end
end
function start_round()
game.reset_time_played()
destroy_config_for_all()
script_data.random = game.create_random_generator(script_data.config.game_config.seed)
script_data.ready_start_tick = nil
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
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 = "neutral"
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})
set_team(player.index)
player.show_on_map = false;
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 = "inside_shallow_frame"}
local team_table = inner.add{type = "table", column_count = 2, style = "bordered_table"}
team_table.style.margin = 4
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 subheader = frame.add{type = "frame", style = "subheader_frame"}
subheader.style.horizontally_stretchable = true
subheader.style.vertical_align = "center"
if script_data.config.victory.production_score.required_production_score > 0 then
subheader.add{type = "label", style = "subheader_label", caption = {"", {"required_production_score"}, {"colon"}, " ", util.format_number(script_data.config.victory.production_score.required_production_score)}}
end
if script_data.config.game_config.time_limit > 0 then
if next(subheader.children) then
subheader.add{type = "line", direction = "vertical"}
end
subheader.add{type = "label", style = not next(subheader.children) and "subheader_label" or nil, caption = {"time_left", get_time_left()}}
end
if not next(subheader.children) then subheader.destroy() end
local information_table = frame.add{type = "table", column_count = 4, style = "bordered_table"}
information_table.style.margin = 4
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()
local inner_frame = frame.add{type = "frame", style = "inside_shallow_frame", direction = "vertical"}
local subheader = inner_frame.add{type = "frame", style = "subheader_frame"}
subheader.style.horizontally_stretchable = true
subheader.style.vertical_align = "center"
if script_data.config.victory.oil_harvest.required_oil > 0 then
subheader.add{type = "label", style = "subheader_label", caption = {"", {"required_oil"}, {"colon"}, " ", util.format_number(script_data.config.victory.oil_harvest.required_oil)}}
end
if script_data.config.game_config.time_limit > 0 then
if next(subheader.children) then
subheader.add{type = "line", direction = "vertical"}
end
subheader.add{type = "label", style = not next(subheader.children) and "subheader_label" or nil, caption = {"time_left", get_time_left()}}
end
if not next(subheader.children) then subheader.destroy() end
local information_table = inner_frame.add{type = "table", column_count = 3, style = "bordered_table"}
information_table.style.margin = 4
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()
local inner_frame = frame.add{type = "frame", style = "inside_shallow_frame", direction = "vertical"}
local subheader = inner_frame.add{type = "frame", style = "subheader_frame"}
subheader.style.horizontally_stretchable = true
if script_data.config.victory.kill_score.required_kill_score > 0 then
subheader.add{type = "label", style = "subheader_label", caption = {"", {"required_kill_score"}, {"colon"}, " ", util.format_number(script_data.config.victory.kill_score.required_kill_score)}}
end
if script_data.config.game_config.time_limit > 0 then
if next(subheader.children) then
subheader.add{type = "line", direction = "vertical"}
end
subheader.add{type = "label", style = not next(subheader.children) and "subheader_label" or nil, caption = {"time_left", get_time_left()}}
end
if not next(subheader.children) then subheader.destroy() end
local information_table = inner_frame.add{type = "table", column_count = 3, style = "bordered_table"}
information_table.style.margin = 4
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 = get_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()
local inner_frame = frame.add{type = "frame", style = "inside_shallow_frame", direction = "vertical"}
local subheader = inner_frame.add{type = "frame", style = "subheader_frame"}
subheader.style.horizontally_stretchable = true
subheader.style.vertical_align = "center"
if script_data.config.victory.space_race.required_rockets_sent > 0 then
subheader.add{type = "label", style = "subheader_label", caption = {"", {"required_rockets_sent"}, {"colon"}, " ", util.format_number(script_data.config.victory.space_race.required_rockets_sent)}}
end
if script_data.config.game_config.time_limit > 0 then
if next(subheader.children) then
subheader.add{type = "line", direction = "vertical"}
end
subheader.add{type = "label", style = not next(subheader.children) and "subheader_label" or nil, caption = {"time_left", get_time_left()}}
end
if not next(subheader.children) then subheader.destroy() end
local information_table = inner_frame.add{type = "table", column_count = 3, style = "bordered_table"}
information_table.style.margin = 4
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 (get_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 update_teams_names()
local names = {}
for k, team in pairs (script_data.config.teams) do
names[team.name] = true
end
script_data.team_names = names
end
function setup_teams()
local old_team_names = script_data.team_names
update_teams_names()
for name, bool in pairs (old_team_names) do
if not script_data.team_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]
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 clear_cliffs(surface, area)
for k, entity in pairs(surface.find_entities_filtered{type = "cliff", 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)
clear_cliffs(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.active 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 team_score = scores[name] or {}
local old_amount = team_score[index]
if old_amount then
average_score[name] = (average_score[name] + score) - old_amount
scores[name][index] = score
else
--Something went wrong, reinitialize it next update
script_data.scores = nil
script_data.average_score = nil
return check_update_production_score()
end
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.production_score.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.active 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.active = 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
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.oil_harvest.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.active then return end
local scores = get_kill_scores()
for k, player in pairs (game.connected_players) do
update_kill_score_frame(player)
end
local required = script_data.config.victory.kill_score.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.active then return end
local scores = get_rocket_scores()
for k, player in pairs (game.connected_players) do
update_space_race_frame(player)
end
local required = script_data.config.victory.space_race.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_moat_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 get_kill_scores()
if script_data.kill_scores then return script_data.kill_scores end
local scores = {}
for k, force in pairs (game.forces) do
scores[force.name] = 0
end
script_data.kill_scores = scores
return scores
end
function get_rocket_scores()
if script_data.rocket_scores then return script_data.rocket_scores end
local scores = {}
for k, force in pairs (game.forces) do
scores[force.name] = 0
end
script_data.rocket_scores = scores
return 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}, get_lobby_surface())
local team = script_data.team_players[player.index]
if team and script_data.team_names[team.name] and game.forces[team.name] then
set_player(player, team, true)
else
script_data.team_players[player.index] = nil
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 = {}
get_rocket_scores()
get_kill_scores()
if script_data.config.victory.production_score.active 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.active 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_moat_for_force(force)
if not script_data.config.game_config.team_moat then
return
end
local tile_name = script_data.config.prototypes.moat
if not game.tile_prototypes[tile_name] 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)
local tiles = {}
local water_radius = radius + 6
for X = -water_radius, water_radius - 1 do
if X >= 18 or X < -18 then
for k = 0, 11 do
insert(tiles, {name = tile_name, position = {x = origin.x + X, y = origin.y - water_radius + k}})
insert(tiles, {name = tile_name, position = {x = origin.x + X, y = origin.y + (water_radius-1) - k}})
insert(tiles, {name = tile_name, position = {x = origin.x - water_radius + k, y = origin.y + X}})
insert(tiles, {name = tile_name, position = {x = origin.x + (water_radius-1) - k, y = origin.y + X}})
end
end
end
surface.set_tiles(tiles)
local cliff_radius = radius - 6
--The corners
surface.create_entity{name = "cliff", position = {x = origin.x - cliff_radius, y = origin.y - cliff_radius}, cliff_orientation = "east-to-south"}
surface.create_entity{name = "cliff", position = {x = origin.x + cliff_radius, y = origin.y - cliff_radius}, cliff_orientation = "south-to-west"}
surface.create_entity{name = "cliff", position = {x = origin.x - cliff_radius, y = origin.y + cliff_radius}, cliff_orientation = "north-to-east"}
surface.create_entity{name = "cliff", position = {x = origin.x + cliff_radius, y = origin.y + cliff_radius}, cliff_orientation = "west-to-north"}
--The lengths
for k = -(cliff_radius - 4), (cliff_radius - 4), 4 do
if k >= 20 or k < -20 then
surface.create_entity{name = "cliff", position = {x = origin.x + k, y = origin.y - cliff_radius}, cliff_orientation = "east-to-west"}
surface.create_entity{name = "cliff", position = {x = origin.x + k, y = origin.y + cliff_radius}, cliff_orientation = "west-to-east"}
surface.create_entity{name = "cliff", position = {x = origin.x - cliff_radius, y = origin.y + k }, cliff_orientation = "north-to-south"}
surface.create_entity{name = "cliff", position = {x = origin.x + cliff_radius, y = origin.y + k}, cliff_orientation = "south-to-north"}
end
end
-- The openings
--Bottom
surface.create_entity{name = "cliff", position = {x = origin.x - 18, y = origin.y + cliff_radius}, cliff_orientation = "west-to-south"}
surface.create_entity{name = "cliff", position = {x = origin.x - 18, y = origin.y + cliff_radius + 4}, cliff_orientation = "north-to-south"}
surface.create_entity{name = "cliff", position = {x = origin.x - 18, y = origin.y + cliff_radius + 8}, cliff_orientation = "north-to-south"}
surface.create_entity{name = "cliff", position = {x = origin.x - 18, y = origin.y + cliff_radius + 12}, cliff_orientation = "north-to-none"}
surface.create_entity{name = "cliff", position = {x = origin.x + 18, y = origin.y + cliff_radius}, cliff_orientation = "south-to-east"}
surface.create_entity{name = "cliff", position = {x = origin.x + 18, y = origin.y + cliff_radius + 4}, cliff_orientation = "south-to-north"}
surface.create_entity{name = "cliff", position = {x = origin.x + 18, y = origin.y + cliff_radius + 8}, cliff_orientation = "south-to-north"}
surface.create_entity{name = "cliff", position = {x = origin.x + 18, y = origin.y + cliff_radius + 12}, cliff_orientation = "none-to-north"}
--Top
surface.create_entity{name = "cliff", position = {x = origin.x - 18, y = origin.y - cliff_radius}, cliff_orientation = "north-to-west"}
surface.create_entity{name = "cliff", position = {x = origin.x - 18, y = origin.y - cliff_radius - 4}, cliff_orientation = "north-to-south"}
surface.create_entity{name = "cliff", position = {x = origin.x - 18, y = origin.y - cliff_radius - 8}, cliff_orientation = "north-to-south"}
surface.create_entity{name = "cliff", position = {x = origin.x - 18, y = origin.y - cliff_radius - 12}, cliff_orientation = "none-to-south"}
surface.create_entity{name = "cliff", position = {x = origin.x + 18, y = origin.y - cliff_radius}, cliff_orientation = "east-to-north"}
surface.create_entity{name = "cliff", position = {x = origin.x + 18, y = origin.y - cliff_radius - 4}, cliff_orientation = "south-to-north"}
surface.create_entity{name = "cliff", position = {x = origin.x + 18, y = origin.y - cliff_radius - 8}, cliff_orientation = "south-to-north"}
surface.create_entity{name = "cliff", position = {x = origin.x + 18, y = origin.y - cliff_radius - 12}, cliff_orientation = "south-to-none"}
--Right
surface.create_entity{name = "cliff", position = {x = origin.x + cliff_radius, y = origin.y - 18}, cliff_orientation = "east-to-north"}
surface.create_entity{name = "cliff", position = {x = origin.x + cliff_radius + 4, y = origin.y - 18}, cliff_orientation = "east-to-west"}
surface.create_entity{name = "cliff", position = {x = origin.x + cliff_radius + 8, y = origin.y - 18}, cliff_orientation = "east-to-west"}
surface.create_entity{name = "cliff", position = {x = origin.x + cliff_radius + 12, y = origin.y - 18}, cliff_orientation = "none-to-west"}
surface.create_entity{name = "cliff", position = {x = origin.x + cliff_radius, y = origin.y + 18}, cliff_orientation = "south-to-east"}
surface.create_entity{name = "cliff", position = {x = origin.x + cliff_radius + 4, y = origin.y + 18}, cliff_orientation = "west-to-east"}
surface.create_entity{name = "cliff", position = {x = origin.x + cliff_radius + 8, y = origin.y + 18}, cliff_orientation = "west-to-east"}
surface.create_entity{name = "cliff", position = {x = origin.x + cliff_radius + 12, y = origin.y + 18}, cliff_orientation = "west-to-none"}
--Left
surface.create_entity{name = "cliff", position = {x = origin.x - cliff_radius, y = origin.y - 18}, cliff_orientation = "north-to-west"}
surface.create_entity{name = "cliff", position = {x = origin.x - cliff_radius - 4, y = origin.y - 18}, cliff_orientation = "east-to-west"}
surface.create_entity{name = "cliff", position = {x = origin.x - cliff_radius - 8, y = origin.y - 18}, cliff_orientation = "east-to-west"}
surface.create_entity{name = "cliff", position = {x = origin.x - cliff_radius - 12, y = origin.y - 18}, cliff_orientation = "east-to-none"}
surface.create_entity{name = "cliff", position = {x = origin.x - cliff_radius, y = origin.y + 18}, cliff_orientation = "west-to-south"}
surface.create_entity{name = "cliff", position = {x = origin.x - cliff_radius - 4, y = origin.y + 18}, cliff_orientation = "west-to-east"}
surface.create_entity{name = "cliff", position = {x = origin.x - cliff_radius - 8, y = origin.y + 18}, cliff_orientation = "west-to-east"}
surface.create_entity{name = "cliff", position = {x = origin.x - cliff_radius - 12, y = origin.y + 18}, cliff_orientation = "none-to-east"}
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 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
function is_ignored_force(name)
return not script_data.team_names[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) ~= type(old_data[k]) 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 = "inside_shallow_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, style = "bordered_table"}
table.style.margin = 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 = "transparent_slot",
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 = util.product_amount(product)
local product_price = prices[product.name] or 0
local calculator_button = flow.add
{
type = "sprite-button",
sprite = product.type.."/"..product.name,
number = amount,
style = "transparent_slot",
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 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
defcon_research()
return
end
local current_tick = game.tick
local duration = math.max(60, (script_data.config.team_config.defcon_timer * 60 * 60))
local tick_of_defcon = defcon_tick + duration
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 (not force.current_research) or (force.current_research.name ~= tech.name) then
force.cancel_current_research()
force.add_research(tech)
end
if force.current_research then
--If they have labs, it might research it between the defcon updates...
force.research_progress = progress
end
end
end
end
if current_tick >= tick_of_defcon then
defcon_research()
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()
script_data.last_defcon_tick = game.tick
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.add_research(script_data.next_defcon_tech)
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
local on_rocket_launched = function(event)
if not script_data.config.victory.space_race.active then return end
local sent = get_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 script_data.team_won then return end
if not script_data.config.victory.last_silo_standing.active 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
local character = player.character
if character then
player.character = nil
character.die()
end
player.force = "neutral"
player.set_controller{type = defines.controllers.spectator}
end
game.merge_forces(force, "neutral")
local index = 0
local winner_name = {"none"}
for name, listed_silo in pairs (script_data.silos) do
if (listed_silo and listed_silo.valid) then
index = index + 1
winner_name = name
end
end
if index == 1 then
team_won(winner_name)
return
end
if index == 0 then
-- All silos are destroyed, which can happen with only 1 team
-- So we just set the victory tick manually.
script_data.team_won = game.tick
return
end
end
local update_kill_score = function(event)
if not script_data.config.victory.kill_score.active 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 = get_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 not script_data.setup_finished then
refresh_config(player.index)
end
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}, get_lobby_surface())
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.get_player(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_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
local research = event.research
local force = research.force
if not is_ignored_force(force.name) then
if tech and tech.valid and research.name ~= tech.name then
force.cancel_current_research()
force.add_research(tech)
end
end
end
end
local on_player_event_refresh_gui = function(event)
init_player_gui(game.get_player(event.player_index))
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.neutral
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
function destroy_016_player_guis()
for k, player in pairs (game.players) do
local button_flow = mod_gui.get_button_flow(player)
for k, name in pairs (
{
"objective_button", "diplomacy_button", "admin_button",
"silo_gui_sprite_button", "production_score_button", "oil_harvest_button",
"space_race_button", "spectator_join_team_button", "list_teams_button"
}) do
if button_flow[name] then
button_flow[name].destroy()
end
end
local frame_flow = mod_gui.get_frame_flow(player)
for k, name in pairs (
{
"objective_frame", "admin_button", "admin_frame",
"silo_gui_frame", "production_score_frame", "oil_harvest_frame",
"space_race_frame", "team_list"
}) do
if frame_flow[name] then
frame_flow[name].destroy()
end
end
local center_gui = player.gui.center
for k, name in pairs ({"diplomacy_frame", "progress_bar", "random_join_frame", "pick_join_frame", "auto_assign_frame"}) do
if center_gui[name] then
center_gui[name].destroy()
end
end
end
end
local pvp = {}
pvp.events =
{
[defines.events.on_built_entity] = on_built_entity,
[defines.events.on_robot_built_entity] = on_robot_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_joined_game] = on_player_joined_game,
[defines.events.on_player_left_game] = on_player_left_game,
[defines.events.on_player_respawned] = on_player_respawned,
[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_event_refresh_gui,
[defines.events.on_player_display_resolution_changed] = on_player_event_refresh_gui,
[defines.events.on_player_display_scale_changed] = on_player_event_refresh_gui,
[defines.events.on_player_promoted] = on_player_event_refresh_gui,
[defines.events.on_player_changed_force] = on_player_event_refresh_gui,
[defines.events.on_research_finished] = on_research_finished,
[defines.events.on_research_started] = on_research_started,
[defines.events.on_rocket_launched] = on_rocket_launched,
[defines.events.on_tick] = on_tick
}
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()
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_load = function()
script_data = global.pvp or script_data
balance.script_data = script_data
config.script_data = script_data
end
pvp.on_init = function()
if global.pvp then
--Init was already run, just do the on_load.
pvp.on_load()
return
end
script_data.config = config.get_config()
global.pvp = script_data
balance.script_data = script_data
config.script_data = script_data
balance.init()
script_data.config.game_config.seed = game.surfaces[1].map_gen_settings.seed
for k, force in pairs (game.forces) do
force.disable_all_prototypes()
force.disable_research()
end
end
pvp.on_configuration_changed = function(data)
if not global.pvp and global.surface and global.teams then
--Was made in 0.16, do some basic data migration
local pvp = global
global = {pvp = pvp}
recursive_data_check(script_data, global.pvp)
script_data = global.pvp
script_data.config = config.get_config()
local config = script_data.config
config.teams = script_data.teams
script_data.teams = nil
destroy_016_player_guis()
balance.script_data = script_data
config.script_data = script_data
end
recursive_data_check(config.get_config(), script_data.config)
script_data.random = script_data.random or game.create_random_generator()
update_teams_names()
--[[
if game.forces.spectator then
game.merge_forces("spectator", "neutral")
end
]]
script_data.elements.team_tab = script_data.elements.team_tab or {}
script_data.elements.game_tab = script_data.elements.game_tab or {}
end
return pvp