-- I made a separate file for all the GUI related functions. Yay me. local SPAWN_GUI_MAX_WIDTH = 500 local SPAWN_GUI_MAX_HEIGHT = 1000 ---A display gui message. Meant to be display the first time a player joins. ---@param player LuaPlayer ---@return boolean function DisplayWelcomeTextGui(player) if ((player.gui.screen["join_shared_spawn_wait_menu"] ~= nil) or (player.gui.screen["buddy_wait_menu"] ~= nil) or (player.gui.screen["buddy_request_menu"] ~= nil) or (player.gui.screen["wait_for_spawn_dialog"] ~= nil)) then log("DisplayWelcomeTextGui called while some other dialog is already displayed!") return false end --Delete existing guis if (player.gui.screen.welcome_msg ~= nil) then player.gui.screen.welcome_msg.destroy() end if (player.gui.screen.spawn_opts ~= nil) then player.gui.screen.spawn_opts.destroy() end if (player.gui.screen.self_reset_confirm ~= nil) then player.gui.screen.self_reset_confirm.destroy() end local welcome_gui = player.gui.screen.add { name = "welcome_msg", type = "frame", direction = "vertical", caption = storage.ocfg.server_info.welcome_msg_title } welcome_gui.auto_center = true welcome_gui.style.maximal_width = SPAWN_GUI_MAX_WIDTH welcome_gui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT welcome_gui.style.padding = 5 local welcome_gui_if = welcome_gui.add { type = "frame", style = "inside_shallow_frame_with_padding", direction = "vertical" } -- Informational message about the scenario if (storage.ocfg.server_info.welcome_msg ~= " ") then AddLabel(welcome_gui_if, nil, storage.ocfg.server_info.welcome_msg, my_label_style) AddSpacer(welcome_gui_if) end -- Warnings about the scenario AddLabel(welcome_gui_if, nil, { "oarc-scenario-info-warn-msg" }, my_note_style) AddSpacer(welcome_gui_if) AddLabel(welcome_gui_if, nil, { "oarc-experimental-warning" }, my_label_style) -- Confirm button local button_flow = welcome_gui.add { type = "flow", style = "dialog_buttons_horizontal_flow", } button_flow.style.horizontally_stretchable = true local dragger = button_flow.add { type = "empty-widget", style = "draggable_space", } dragger.style.left_margin = 0 dragger.style.horizontally_stretchable = true dragger.style.height = 30 local confirm_button = button_flow.add { name = "welcome_okay_btn", tags = { action = "oarc_spawn_options", setting = "welcome_okay" }, type = "button", caption = { "oarc-i-understand" }, style = "confirm_button", } confirm_button.style.horizontal_align = "right" return true end ---Handle the gui click of the welcome msg ---@param event EventData.on_gui_click ---@return nil function WelcomeTextGuiClick(event) if not event.element.valid then return end local player = game.players[event.player_index] local buttonClicked = event.element.name if (buttonClicked == "welcome_okay_btn") then if (player.gui.screen.welcome_msg ~= nil) then player.gui.screen.welcome_msg.destroy() end DisplaySpawnOptions(player) end end ---Creates the spawn menu gui frame to hold all the spawn options. ---@param player LuaPlayer ---@return LuaGuiElement function CreateSpawnMenuGuiFrame(player) local spawn_opts_frame = player.gui.screen.add { name = "spawn_opts", type = "frame", direction = "vertical", caption = { "oarc-spawn-options" } } spawn_opts_frame.style.maximal_width = SPAWN_GUI_MAX_WIDTH spawn_opts_frame.style.maximal_height = SPAWN_GUI_MAX_HEIGHT spawn_opts_frame.auto_center = true spawn_opts_frame.style.padding = 5 local inside_frame = spawn_opts_frame.add { name = "spawn_opts_if", type = "frame", style = "inside_shallow_frame", direction = "vertical" } -- SUB HEADING w/ LABEL local subhead = inside_frame.add{ type="frame", name="sub_header", style = "subheader_frame" } subhead.style.height = 46 subhead.style.horizontally_stretchable = true -- changelog_subheader_frame = -- { -- type = "frame_style", -- parent = "subheader_frame", -- left_padding = 12, -- right_padding = 12, -- top_padding = 4, -- horizontally_stretchable = "on" -- } AddLabel(subhead, "warning_lbl1", { "oarc-click-info-btn-help" }, my_note_style) return inside_frame end ---Show the surface select dropdown ---@param parent_flow LuaGuiElement ---@return nil function CreateSurfaceSelectDropdown(parent_flow) local surfacesHorizontalFlow = parent_flow.add { name = "surfaces_horizontal_flow", type = "flow", direction = "horizontal" } local surface_list = GetAllowedSurfaces() -- Get the index of the default surface if it exists local default_surface_index = 1 for i,surface in ipairs(surface_list) do if (surface == storage.ocfg.gameplay.default_surface) then default_surface_index = i break end end AddLabel(surfacesHorizontalFlow, "surfacesHorizontalFlowLabel", "Select Surface: ", my_label_style) surfacesHorizontalFlow.add { name = "surface_select_dropdown", tags = { action = "oarc_spawn_options", setting = "surface_select" }, type = "drop-down", items = surface_list, selected_index = default_surface_index, tooltip = { "oarc-surface-select-tooltip" }, enabled = #surface_list > 1 } end ---Display the team select radio buttons ---@param parent_flow LuaGuiElement ---@param enable_main_team boolean ---@param enable_separate_teams boolean ---@return nil function DisplayTeamSelectRadioButtons(parent_flow, enable_main_team, enable_separate_teams) if enable_main_team then parent_flow.add { name = "isolated_spawn_main_team_radio", tags = { action = "oarc_spawn_options", setting = "team_select", value = SPAWN_TEAM_CHOICE.join_main_team }, type = "radiobutton", caption = { "oarc-join-main-team-radio" }, tooltip = { "oarc-join-main-team-tooltip" }, -- If separate teams are not enabled, default to joining the main team, and disable the radio buttons. state = true, -- ignored_by_interaction = not enable_separate_teams, -- enabled = enable_separate_teams } end if (enable_separate_teams) then parent_flow.add { name = "isolated_spawn_new_team_radio", tags = { action = "oarc_spawn_options", setting = "team_select", value = SPAWN_TEAM_CHOICE.join_own_team }, type = "radiobutton", caption = { "oarc-create-own-team-radio" }, tooltip = { "oarc-create-own-team-tooltip" }, -- If main team is not enabled, default to joining the a separate team, and disable the radio buttons. state = not enable_main_team, -- ignored_by_interaction = not enable_main_team, -- enabled = enable_main_team } end end ---Create a distance select slider ---@param parent_flow LuaGuiElement ---@param minimum_distance number ---@param maximum_distance number ---@return nil function CreateDistanceSelectSlider(parent_flow, minimum_distance, maximum_distance) local slider_flow = parent_flow.add { type = "flow", direction = "horizontal", style = "player_input_horizontal_flow" } slider_flow.style.horizontally_stretchable = true local label = slider_flow.add { type = "label", caption = { "oarc-spawn-distance-slider-label" }, tooltip = { "oarc-spawn-distance-slider-tooltip" } } label.style.horizontal_align = "left" local slider = slider_flow.add { name = "spawn_distance_slider", type = "slider", tags = { action = "oarc_spawn_options", setting = "distance_select" }, minimum_value = minimum_distance, maximum_value = maximum_distance, value = minimum_distance, discrete_slider = true, value_step = 1, tooltip = { "oarc-spawn-distance-slider-tooltip" } } slider.style.horizontally_stretchable = true local text_value = slider_flow.add { name = "spawn_distance_slider_value", type = "textfield", ignored_by_interaction = true, caption = minimum_distance, style = "slider_value_textfield", text = tostring(minimum_distance) } text_value.style.horizontal_align = "right" text_value.style.width = 50 end ---Create the spawn settings frame ---@param parent_flow LuaGuiElement ---@param gameplay OarcConfigGameplaySettings ---@return nil function CreateSpawnSettingsFrame(parent_flow, gameplay) local spawn_settings_frame = parent_flow.add { name = "spawn_settings_frame", type = "frame", direction = "vertical", style = "bordered_frame", } spawn_settings_frame.style.horizontally_stretchable = true spawn_settings_frame.style.padding = 5 spawn_settings_frame.style.margin = 4 spawn_settings_frame.style.bottom_margin = 0 AddLabel(spawn_settings_frame, nil, { "oarc-spawn-menu-settings-header" }, my_label_header_style) AddLabel(spawn_settings_frame, nil, { "oarc-spawn-menu-settings-info" }, my_label_style) -- Pick surface CreateSurfaceSelectDropdown(spawn_settings_frame) -- Radio buttons to pick your team. DisplayTeamSelectRadioButtons(spawn_settings_frame, gameplay.enable_main_team, gameplay.enable_separate_teams) -- Allow players to spawn with a moat around their area. if (gameplay.allow_moats_around_spawns) then spawn_settings_frame.add { name = "isolated_spawn_moat_option_checkbox", tags = { action = "oarc_spawn_options", setting = "moat_option" }, type = "checkbox", caption = { "oarc-moat-option" }, state = true, -- Default to true tooltip = { "oarc-moat-option-tooltip" } } end CreateDistanceSelectSlider(spawn_settings_frame, gameplay.near_spawn_distance, gameplay.far_spawn_distance) end ---Create a frame and a confim button for player to request SOLO spawn creation ---@param parent_flow LuaGuiElement ---@param enable_shared_spawns boolean ---@param max_shared_players integer ---@return nil function CreateSoloSpawnFrame(parent_flow, enable_shared_spawns, max_shared_players) solo_spawn_frame = parent_flow.add { name = "solo_spawn_frame", type = "frame", direction = "vertical", style = "bordered_frame" } solo_spawn_frame.style.horizontally_stretchable = true solo_spawn_frame.style.padding = 5 solo_spawn_frame.style.margin = 4 solo_spawn_frame.style.bottom_margin = 0 AddLabel(solo_spawn_frame, nil, { "oarc-spawn-menu-solo-header" }, my_label_header_style) AddLabel(solo_spawn_frame, nil, { "oarc-starting-area-normal" }, my_label_style) -- A note about sharing spawns if enable_shared_spawns and (max_shared_players > 1) then AddLabel(solo_spawn_frame, nil, { "oarc-max-players-shared-spawn", max_shared_players - 1 }, my_label_style) end local button_flow = solo_spawn_frame.add { type = "flow", direction = "horizontal" } button_flow.style.horizontal_align = "right" button_flow.style.horizontally_stretchable = true button_flow.add { name = "spawn_request", tags = { action = "oarc_spawn_options", setting = "spawn_request" }, type = "button", caption = { "oarc-solo-spawn" }, tooltip = { "oarc-solo-spawn-tooltip" }, style = "green_button" } end ---Creates the shared spawn frame for joining another player's base ---@param parent_flow LuaGuiElement ---@param enable_shared_spawns boolean ---@return nil function CreateSharedSpawnFrame(parent_flow, enable_shared_spawns) local shared_spawn_frame = parent_flow.shared_spawn_frame local prev_selected_host = nil ---@type string? local prev_selected_spawn = nil ---@type OarcUniqueSpawn? -- Create the shared spawn frame if it doesn't exist if shared_spawn_frame == nil then shared_spawn_frame = parent_flow.add { name = "shared_spawn_frame", type = "frame", direction = "vertical", style = "bordered_frame" } shared_spawn_frame.style.horizontally_stretchable = true shared_spawn_frame.style.padding = 5 shared_spawn_frame.style.margin = 4 shared_spawn_frame.style.bottom_margin = 0 --- Let's us refresh the frame if it already exists instead of recreating it else -- Save the previous selected host so we can reselect it after the frame is recreated if (shared_spawn_frame.shared_spawn_horizontal_flow ~= nil) then local dropdown = shared_spawn_frame.shared_spawn_horizontal_flow.shared_spawn_select_dropdown local index = dropdown.selected_index if index > 0 then prev_selected_host = dropdown.get_item(index) --[[@as string]] end end for _,child in pairs(shared_spawn_frame.children) do child.destroy() end end AddLabel(shared_spawn_frame, nil, { "oarc-spawn-menu-shared-header" }, my_label_header_style) if not enable_shared_spawns then AddLabel(shared_spawn_frame, nil, { "oarc-shared-spawn-disabled" }, my_warning_style) return end local avail_spawns = GetAvailableSharedSpawns() local num_avail_spawns = #avail_spawns.hosts if (num_avail_spawns > 0) then AddLabel(shared_spawn_frame, nil, { "oarc-join-someone-info" }, my_label_style) local new_selected_index = 0 if prev_selected_host then for i,host in ipairs(avail_spawns.hosts) do if host == prev_selected_host then new_selected_index = i break end end end local horizontal_flow = shared_spawn_frame.add { name = "shared_spawn_horizontal_flow", type = "flow", direction = "horizontal", style = "dialog_buttons_horizontal_flow" } horizontal_flow.style.horizontally_stretchable = true local label = AddLabel(horizontal_flow, nil, { "oarc-join-someone-dropdown-label" }, my_label_style) label.style.horizontal_align = "left" local dropdown = horizontal_flow.add { name = "shared_spawn_select_dropdown", tags = { action = "oarc_spawn_options", setting = "shared_spawn_select" }, type = "drop-down", items = avail_spawns.hosts, selected_index = new_selected_index, tooltip = { "oarc-join-someone-dropdown-tooltip" } } dropdown.style.horizontal_align = "left" local dragger = horizontal_flow.add { type = "empty-widget", style = "draggable_space", } dragger.style.horizontally_stretchable = true local button = horizontal_flow.add { name = "join_other_spawn", tags = { action = "oarc_spawn_options", setting = "join_other_spawn" }, type = "button", tooltip = { "oarc-join-shared-button-tooltip" }, enabled = new_selected_index > 0 } if new_selected_index == 0 then button.caption = { "oarc-join-shared-button-disable" } button.style = "red_button" else button.caption = { "oarc-join-shared-button-enable", prev_selected_host, avail_spawns.spawns[new_selected_index].surface_name } button.style = "green_button" end button.style.horizontal_align = "right" else AddLabel(shared_spawn_frame, nil, { "oarc-no-shared-avail" }, my_label_style) end end ---Refresh the shared spawn frame if it exists ---@param player LuaPlayer ---@return nil function RefreshSharedSpawnFrameIfExist(player) local spawn_opts = player.gui.screen.spawn_opts if spawn_opts == nil then return end CreateSharedSpawnFrame(spawn_opts.spawn_opts_if, storage.ocfg.gameplay.enable_shared_spawns) end ---Creates the buddy spawn frame for spawning with a buddy ---@param parent_flow LuaGuiElement ---@param player LuaPlayer ---@param enable_buddy_spawn boolean ---@param enable_separate_teams boolean ---@return nil function CreateBuddySpawnFrame(parent_flow, player, enable_buddy_spawn, enable_separate_teams) local buddy_spawn_frame = parent_flow.buddy_spawn_frame local selected_buddy = nil ---@type string? -- Create the buddy spawn frame if it doesn't exist if buddy_spawn_frame == nil then buddy_spawn_frame = parent_flow.add { name = "buddy_spawn_frame", type = "frame", direction = "vertical", style = "bordered_frame" } buddy_spawn_frame.style.horizontally_stretchable = true buddy_spawn_frame.style.padding = 5 buddy_spawn_frame.style.margin = 4 --- Let's us refresh the frame if it already exists instead of recreating it else -- Save the previous selected buddy so we can reselect it after the frame is recreated if buddy_spawn_frame.waiting_buddies_dropdown ~= nil then local index = buddy_spawn_frame.waiting_buddies_dropdown.selected_index if index > 0 then selected_buddy = buddy_spawn_frame.waiting_buddies_dropdown.get_item(index) --[[@as string]] --- Make sure the buddy is still valid? if game.players[selected_buddy] and game.players[selected_buddy].gui.screen.spawn_opts == nil then selected_buddy = nil end end end for _,child in pairs(buddy_spawn_frame.children) do child.destroy() end end AddLabel(buddy_spawn_frame, nil, { "oarc-spawn-menu-buddy-header" }, my_label_header_style) if not enable_buddy_spawn then AddLabel(buddy_spawn_frame, nil, { "oarc-buddy-spawn-disabled" }, my_warning_style) return end -- Warnings and explanations... AddLabel(buddy_spawn_frame, nil, { "oarc-buddy-spawn-instructions" }, my_label_style) if (enable_separate_teams) then buddy_spawn_frame.add { tags = { action = "oarc_spawn_options", setting = "buddy_team_select" }, type = "checkbox", caption = { "oarc-create-buddy-team" }, state = false, tooltip = { "oarc-create-buddy-team-tooltip" } } end ---@type string[] local avail_buddies = GetOtherPlayersInSpawnMenu(player) log("Available buddies: " .. serpent.block(avail_buddies)) local previous_index = 0 if selected_buddy then for i,host in ipairs(avail_buddies) do if host == selected_buddy then previous_index = i break end end end local buddy_button_horizontal_flow = buddy_spawn_frame.add { type = "flow", direction = "horizontal", style = "dialog_buttons_horizontal_flow" } buddy_button_horizontal_flow.style.horizontally_stretchable = true local label = AddLabel(buddy_button_horizontal_flow, nil, { "oarc-buddy-select-label" }, my_label_style) label.style.horizontal_align = "left" local buddy_dropdown = buddy_button_horizontal_flow.add { name = "waiting_buddies_dropdown", tags = { action = "oarc_spawn_options", setting = "buddy_select" }, type = "drop-down", items = avail_buddies, selected_index = previous_index, tooltip = { "oarc-buddy-select-tooltip" } } buddy_dropdown.style.horizontal_align = "left" local empty = buddy_button_horizontal_flow.add { type = "empty-widget", style = "draggable_space", } empty.style.horizontally_stretchable = true local button = buddy_button_horizontal_flow.add { name = "buddy_spawn_request", tags = { action = "oarc_spawn_options", setting = "buddy_spawn_request" }, type = "button", caption = { "oarc-buddy-spawn" }, style = "green_button", tooltip = { "oarc-buddy-spawn-tooltip" } } button.style.horizontal_align = "right" end ---Refresh the buddy list without recreating any GUI elements ---@param player LuaPlayer ---@param dropdown LuaGuiElement The buddy dropdown element ---@return nil function RefreshBuddyList(player, dropdown) dropdown.items = GetOtherPlayersInSpawnMenu(player) end ---Display the spawn options and explanation ---@param player LuaPlayer ---@return nil function DisplaySpawnOptions(player) if (player == nil) then log("DisplaySpawnOptions with no valid player...") return end if (player.gui.screen.spawn_opts ~= nil) then log("Tried to display spawn options when it was already displayed!") return end -- Get gameplay settings from config ---@type OarcConfigGameplaySettings local gameplay = storage.ocfg.gameplay -- Create the primary frame and a warning label local sGui = CreateSpawnMenuGuiFrame(player) -- Create the default settings entry for the OarcSpawnChoices table local default_team = SPAWN_TEAM_CHOICE.join_main_team if (not gameplay.enable_main_team and gameplay.enable_separate_teams) then default_team = SPAWN_TEAM_CHOICE.join_own_team end ---@type OarcSpawnChoices local spawn_choices_entry = { surface_name = gameplay.default_surface, team = default_team, moat = gameplay.allow_moats_around_spawns, buddy = nil, distance = gameplay.near_spawn_distance, host = nil, buddy_team = false } storage.spawn_choices[player.name] = spawn_choices_entry CreateSpawnSettingsFrame(sGui, gameplay) -- The settings for configuring a spawn CreateSoloSpawnFrame(sGui, gameplay.enable_shared_spawns, gameplay.number_of_players_per_shared_spawn) -- The primary method of spawning CreateSharedSpawnFrame(sGui, gameplay.enable_shared_spawns) -- Spawn options to join another player's base. CreateBuddySpawnFrame(sGui, player, gameplay.enable_buddy_spawn, gameplay.enable_separate_teams) -- Awesome buddy spawning system end ---This just updates the radio buttons/checkboxes when players click them. ---@param event EventData.on_gui_checked_state_changed ---@return nil function SpawnOptsRadioSelect(event) if not event.element.valid then return end local elemName = event.element.name local player = game.players[event.player_index] local tags = event.element.tags if (tags.action ~= "oarc_spawn_options") then return end if (tags.setting == "team_select") then storage.spawn_choices[player.name].team = tags.value --[[@as SpawnTeamChoice]] -- Need to handle the radio button logic manually if (elemName == "isolated_spawn_main_team_radio") then if (event.element.parent.isolated_spawn_new_team_radio ~= nil) then event.element.parent.isolated_spawn_new_team_radio.state = false end elseif (elemName == "isolated_spawn_new_team_radio") then if (event.element.parent.isolated_spawn_main_team_radio ~= nil) then event.element.parent.isolated_spawn_main_team_radio.state = false end end elseif (tags.setting == "buddy_team_select") then storage.spawn_choices[player.name].buddy_team = event.element.state elseif (tags.setting == "moat_option") then storage.spawn_choices[player.name].moat = event.element.state end end ---Handle the gui click of the spawn options ---@param event EventData.on_gui_click ---@return nil function SpawnOptsGuiClick(event) if not event.element.valid then return end local player = game.players[event.player_index] local tags = event.element.tags if (tags.action ~= "oarc_spawn_options") then return end if (tags.setting == "welcome_okay") then if (player.gui.screen.welcome_msg ~= nil) then player.gui.screen.welcome_msg.destroy() end DisplaySpawnOptions(player) elseif (tags.setting == "spawn_request") then PrimarySpawnRequest(player) elseif (tags.setting == "join_other_spawn") then RequestToJoinSharedSpawn(player) elseif (tags.setting == "cancel_shared_spawn_wait_menu") then CancelSharedSpawnRequest(player) elseif (tags.setting == "buddy_select") then RefreshBuddyList(player, event.element) elseif (tags.setting == "buddy_spawn_request") then RequestBuddySpawn(player) elseif (tags.setting == "cancel_buddy_wait_menu") then CancelBuddySpawnWaitMenu(player) elseif (tags.setting == "accept_buddy_request") then AcceptBuddyRequest(player, tags.requesting_buddy_name --[[@as string]]) elseif (tags.setting == "reject_buddy_request") then RejectBuddyRequest(player, tags.requesting_buddy_name --[[@as string]]) elseif (tags.setting == "surface_select") then event.element.items = GetAllowedSurfaces() end end ---Request a buddy spawn. Requires the buddy to accept or reject the request. ---@param player LuaPlayer ---@return nil function RequestBuddySpawn(player) local buddy_choice = storage.spawn_choices[player.name].buddy if (buddy_choice == nil) then player.print({ "oarc-invalid-buddy" }) return end local buddy = game.players[buddy_choice] if (buddy == nil) then player.print({ "oarc-invalid-buddy" }) return end -- Confirm the buddy is still in the spawn menu! if (buddy.gui.screen.spawn_opts == nil) then player.print({ "oarc-invalid-buddy", buddy.name }) return end DisplayBuddySpawnWaitMenu(player) DisplayBuddySpawnRequestMenu(buddy, player.name) end ---Handle buddy spawn wait menu cancel (waiting for buddy to accept or decline proposal) ---@param player LuaPlayer ---@return nil function CancelBuddySpawnWaitMenu(player) ---@type OarcSpawnChoices local spawn_choices = storage.spawn_choices[player.name] local buddy = game.players[spawn_choices.buddy] player.gui.screen.buddy_wait_menu.destroy() DisplaySpawnOptions(player) -- Catch a case where the buddy has left the game early and no longer exists. if (buddy == nil) then return end if (buddy.gui.screen.buddy_request_menu ~= nil) then buddy.gui.screen.buddy_request_menu.destroy() DisplaySpawnOptions(buddy) end buddy.print({ "oarc-buddy-cancel-request", player.name }) end ---Request to join someone's shared spawn ---@param player LuaPlayer ---@return nil function RequestToJoinSharedSpawn(player) if (player.gui.screen.spawn_opts ~= nil) then player.gui.screen.spawn_opts.destroy() end local host_name = storage.spawn_choices[player.name].host_name if (host_name == nil) then player.print({ "oarc-no-shared-spawn-selected" }) return end -- Clear the spawn options gui if (player.gui.screen.spawn_opts ~= nil) then player.gui.screen.spawn_opts.destroy() end if ((game.players[host_name] ~= nil) and (game.players[host_name].connected)) then local primary_spawn = FindPrimaryUniqueSpawn(host_name) table.insert(storage.unique_spawns[primary_spawn.surface_name][host_name].join_queue, player.name) -- Display wait menu with cancel button. DisplaySharedSpawnJoinWaitMenu(player) -- Tell other player they are requesting a response. game.players[host_name].print({ "oarc-player-requesting-join-you", player.name }) OarcGuiRefreshContent(game.players[host_name]) else player.print({ "oarc-invalid-host-shared-spawn" }) DisplaySpawnOptions(player) end end ---Cancel shared spawn request from the waiting menu ---@param player LuaPlayer ---@return nil function CancelSharedSpawnRequest(player) local host_name = storage.spawn_choices[player.name].host_name if (host_name ~= nil) and (game.players[host_name] ~= nil) then game.players[host_name].print({ "oarc-player-cancel-join-request", player.name }) end --- Destroy the waiting menu and display the spawn options again. player.gui.screen.join_shared_spawn_wait_menu.destroy() DisplaySpawnOptions(player) RemovePlayerFromJoinQueue(player.name) log("ERROR! Failed to remove player from joinQueue?!") end ---Handle slider value changes ---@param event EventData.on_gui_value_changed ---@return nil function SpawnOptsValueChanged(event) if not event.element.valid then return end local player = game.players[event.player_index] local tags = event.element.tags if (tags.action ~= "oarc_spawn_options") then return end if (tags.setting == "distance_select") then local distance = event.element.slider_value storage.spawn_choices[player.name].distance = distance event.element.parent.spawn_distance_slider_value.text = tostring(distance) -- log("GUI DEBUG Selected distance: " .. distance) end end ---Handle dropdown selection changes ---@param event EventData.on_gui_selection_state_changed ---@return nil function SpawnOptsSelectionChanged(event) if not event.element.valid then return end local player = game.players[event.player_index] local tags = event.element.tags if (tags.action ~= "oarc_spawn_options") then return end if (tags.setting == "surface_select") then local index = event.element.selected_index local surface_name = event.element.get_item(index) --[[@as string]] storage.spawn_choices[player.name].surface_name = surface_name log("GUI DEBUG Selected surface: " .. surface_name) elseif (tags.setting == "shared_spawn_select") then SharedSpawnSelect(event.element, player) elseif (tags.setting == "buddy_select") then local index = event.element.selected_index if (index > 0) then local buddyName = event.element.get_item(index) --[[@as string]] storage.spawn_choices[player.name].buddy = buddyName log("GUI DEBUG Selected buddy: " .. buddyName) else storage.spawn_choices[player.name].buddy = nil end end end ---Handles the shared spawn host dropdown selection. Updates the join button and sets the host spawn choice entry. ---@param gui_element LuaGuiElement ---@param player LuaPlayer ---@return nil function SharedSpawnSelect(gui_element, player) local index = gui_element.selected_index if (index > 0) then local host_name = gui_element.get_item(index) --[[@as string]] local button = gui_element.parent.join_other_spawn local primary_spawn = FindPrimaryUniqueSpawn(host_name) if (primary_spawn and IsSharedSpawnOpen(primary_spawn.surface_name, host_name) and not IsSharedSpawnFull(primary_spawn.surface_name, host_name)) then storage.spawn_choices[player.name].host_name = host_name button.enabled = true button.caption = { "oarc-join-shared-button-enable", host_name, primary_spawn.surface_name } button.style = "green_button" else player.print({ "oarc-invalid-host-shared-spawn" }) storage.spawn_choices[player.name].host_name = nil gui_element.selected_index = 0 button.enabled = false button.caption = { "oarc-join-shared-button-disable" } button.style = "red_button" end else storage.spawn_choices[player.name].host_name = nil end end ---Requests the generation of a spawn point for the player (their first primary spawn) ---@param player LuaPlayer ---@return nil function PrimarySpawnRequest(player) -- Get the player's spawn choices ---@type OarcSpawnChoices local spawn_choices = storage.spawn_choices[player.name] if (spawn_choices == nil) then error("ERROR! No spawn choices found for player!") return end -- N/A for solo spawns so clear these! storage.spawn_choices[player.name].host_name = nil storage.spawn_choices[player.name].buddy = nil -- Find coordinates of a good place to spawn local spawn_position = FindUngeneratedCoordinates(spawn_choices.surface_name, spawn_choices.distance, 3) -- If that fails, just throw a warning and don't spawn them. They can try again. if ((spawn_position.x == 0) and (spawn_position.y == 0)) then player.print({ "oarc-no-ungenerated-land-error" }) return end -- Create a new force for player if they choose that radio button if spawn_choices.team ~= SPAWN_TEAM_CHOICE.join_main_team then CreatePlayerCustomForce(player) end -- Queue spawn generation and the player. local delayed_spawn = GenerateNewSpawn(player.name, spawn_choices.surface_name, spawn_position, spawn_choices, true) QueuePlayerForSpawn(player.name, delayed_spawn) SendBroadcastMsg({"", { "oarc-player-is-joining", player.name, spawn_choices.surface_name }, " ", GetGPStext(spawn_choices.surface_name, spawn_position)}) -- Unlock spawn control gui tab SetOarcGuiTabEnabled(player, OARC_SPAWN_CTRL_TAB_NAME, true) -- Destroy the spawn options gui if (player.gui.screen.spawn_opts ~= nil) then player.gui.screen.spawn_opts.destroy() end end ---Display shared spawn join wait menu to the requesting player ---@param player LuaPlayer ---@return nil function DisplaySharedSpawnJoinWaitMenu(player) if (player.gui.screen.spawn_opts ~= nil) then player.gui.screen.spawn_opts.destroy() end local sGui = player.gui.screen.add { name = "join_shared_spawn_wait_menu", type = "frame", direction = "vertical", caption = { "oarc-waiting-for-spawn-owner" } } sGui.auto_center = true sGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH sGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT sGui.style.padding = 5 local sGui_if = sGui.add { type = "frame", direction = "vertical", style = "inside_shallow_frame_with_padding" } -- Warnings and explanations... AddLabel(sGui_if, "warning_lbl1", { "oarc-you-will-spawn-once-host" }, my_note_style) local button_flow = sGui.add { type = "flow", direction = "horizontal", style = "dialog_buttons_horizontal_flow" } button_flow.style.horizontally_stretchable = true local cancel_button = button_flow.add { name = "cancel_shared_spawn_wait_menu", type = "button", tags = { action = "oarc_spawn_options", setting = "cancel_shared_spawn_wait_menu" }, caption = { "oarc-cancel-button-caption" }, tooltip = { "oarc-return-to-previous-tooltip" }, style = "back_button" } local dragger = button_flow.add{type="empty-widget", style="draggable_space"} dragger.style.right_margin = 0 dragger.style.horizontally_stretchable = true dragger.style.height = 30 end ---Display the buddy spawn wait menu ---@param player LuaPlayer ---@return nil function DisplayBuddySpawnWaitMenu(player) if (player.gui.screen.spawn_opts ~= nil) then player.gui.screen.spawn_opts.destroy() end local buddy_wait_menu = player.gui.screen.add { name = "buddy_wait_menu", type = "frame", direction = "vertical", caption = { "oarc-waiting-for-buddy" } } buddy_wait_menu.auto_center = true buddy_wait_menu.style.maximal_width = SPAWN_GUI_MAX_WIDTH buddy_wait_menu.style.maximal_height = SPAWN_GUI_MAX_HEIGHT buddy_wait_menu.style.padding = 5 local buddy_wait_menu_if = buddy_wait_menu.add { type = "frame", direction = "vertical", style = "inside_shallow_frame_with_padding" } -- Warnings and explanations... AddLabel(buddy_wait_menu_if, nil, { "oarc-wait-buddy-select-yes" }, my_note_style) AddSpacer(buddy_wait_menu_if) local button_flow = buddy_wait_menu.add { type = "flow", direction = "horizontal", style = "dialog_buttons_horizontal_flow" } button_flow.style.horizontally_stretchable = true local cancel_button = button_flow.add { name = "cancel_buddy_wait_menu", tags = { action = "oarc_spawn_options", setting = "cancel_buddy_wait_menu" }, type = "button", style = "back_button", caption = { "oarc-cancel-button-caption" }, tooltip = { "oarc-return-to-previous-tooltip" }, } local dragger = button_flow.add{type="empty-widget", style="draggable_space"} dragger.style.right_margin = 0 dragger.style.horizontally_stretchable = true dragger.style.height = 30 end ---Display the buddy spawn request menu ---@param player LuaPlayer The player that is receiving the buddy request ---@param requesting_buddy_name string The name of the player that is requesting to buddy spawn ---@return nil function DisplayBuddySpawnRequestMenu(player, requesting_buddy_name) if (player.gui.screen.spawn_opts ~= nil) then player.gui.screen.spawn_opts.destroy() end local buddy_request_gui = player.gui.screen.add { name = "buddy_request_menu", type = "frame", direction = "vertical", caption = { "oarc-buddy-spawn-request-header" } } buddy_request_gui.auto_center = true buddy_request_gui.style.maximal_width = SPAWN_GUI_MAX_WIDTH buddy_request_gui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT buddy_request_gui.style.padding = 5 local buddy_request_gui_if = buddy_request_gui.add { type = "frame", direction = "vertical", style = "inside_shallow_frame_with_padding" } -- Warnings and explanations... AddLabel(buddy_request_gui_if, nil, { "oarc-buddy-requesting-from-you", requesting_buddy_name }, my_note_style) AddSpacer(buddy_request_gui_if) ---@type OarcSpawnChoices local spawn_choices = storage.spawn_choices[requesting_buddy_name] ---@type LocalisedString local teamText = "error!" if (spawn_choices.buddy_team) then teamText = { "oarc-buddy-txt-buddy-team" } elseif (spawn_choices.team == SPAWN_TEAM_CHOICE.join_main_team) then teamText = { "oarc-buddy-txt-main-team" } elseif (spawn_choices.team == SPAWN_TEAM_CHOICE.join_own_team) then teamText = { "oarc-buddy-txt-new-teams" } end ---@type LocalisedString local moatText = " " if (spawn_choices.moat) then moatText = { "oarc-buddy-txt-moat" } end ---@type LocalisedString local surfaceText = { "oarc-buddy-txt-surface", spawn_choices.surface_name} ---@type LocalisedString local distText = { "oarc-buddy-txt-distance", spawn_choices.distance} ---@type LocalisedString local requestText = { "", requesting_buddy_name, { "oarc-buddy-txt-would-like" }, " ", teamText, { "oarc-buddy-txt-next-to-you" }, moatText, surfaceText, distText } AddLabel(buddy_request_gui_if, nil, requestText, my_label_style) local button_flow = buddy_request_gui.add { type = "flow", direction = "horizontal", style = "dialog_buttons_horizontal_flow" } button_flow.style.horizontally_stretchable = true local reject_button = button_flow.add { name = "reject_buddy_request", tags = { action = "oarc_spawn_options", setting = "reject_buddy_request", requesting_buddy_name = requesting_buddy_name }, type = "button", style = "red_back_button", caption = { "oarc-reject" } } reject_button.style.horizontal_align = "left" local dragger = button_flow.add{type="empty-widget", style="draggable_space"} dragger.style.horizontally_stretchable = true dragger.style.height = 30 local accept_button = button_flow.add { name = "accept_buddy_request", tags = { action = "oarc_spawn_options", setting = "accept_buddy_request", requesting_buddy_name = requesting_buddy_name }, type = "button", style = "confirm_button", caption = { "oarc-accept" } } accept_button.style.horizontal_align = "right" end ---Handles spawning the buddies together once the request has been accepted ---@param player LuaPlayer The player that is accepting the buddy request ---@param requesting_buddy_name string The name of the player that is requesting to buddy spawn ---@return nil function AcceptBuddyRequest(player, requesting_buddy_name) ---@type OarcSpawnChoices local spawn_choices = storage.spawn_choices[requesting_buddy_name] local requesting_buddy = game.players[requesting_buddy_name] local surface = game.surfaces[spawn_choices.surface_name] -- Copy the buddy's spawn choices to the accepting player spawn_choices.host_name = nil -- N/A for buddy spawns so clear it. storage.spawn_choices[player.name] = table.deepcopy(spawn_choices) storage.spawn_choices[player.name].buddy = requesting_buddy_name local buddy_choices = storage.spawn_choices[player.name] -- Find coordinates of a good place to spawn local spawn_position = FindUngeneratedCoordinates(spawn_choices.surface_name, spawn_choices.distance, 3) -- If that fails, just throw a warning and don't spawn them. They can try again. if ((spawn_position.x == 0) and (spawn_position.y == 0)) then player.print({ "oarc-no-ungenerated-land-error" }) return end -- Create a new force for the combined players if they chose that option if spawn_choices.buddy_team then local buddyForce = CreatePlayerCustomForce(requesting_buddy) player.force = buddyForce -- Create a new force for each player if they chose that option elseif spawn_choices.team == SPAWN_TEAM_CHOICE.join_own_team then CreatePlayerCustomForce(player) CreatePlayerCustomForce(requesting_buddy) end -- Destroy GUIs if (requesting_buddy.gui.screen.buddy_wait_menu ~= nil) then requesting_buddy.gui.screen.buddy_wait_menu.destroy() end if (player.gui.screen.buddy_request_menu ~= nil) then player.gui.screen.buddy_request_menu.destroy() end -- Create that spawn in the global vars local buddy_position = GetBuddySpawnPosition(spawn_position, spawn_choices.surface_name, spawn_choices.moat) -- Queue spawn generation for the requesting buddy FIRST. (left) local delayed_spawn = GenerateNewSpawn(requesting_buddy_name, spawn_choices.surface_name, spawn_position, spawn_choices, true) QueuePlayerForSpawn(requesting_buddy_name, delayed_spawn) -- ORDER MATTERS! Otherwise sometimes chunks don't generate properly! -- Queue spawn generation for the accepting buddy SECOND. (right) local delayed_spawn = GenerateNewSpawn(player.name, buddy_choices.surface_name, buddy_position, buddy_choices, true) QueuePlayerForSpawn(player.name, delayed_spawn) SendBroadcastMsg({"", {"oarc-buddies-are-joining", requesting_buddy_name, player.name, spawn_choices.surface_name}, " ", GetGPStext(spawn_choices.surface_name, spawn_position)}) -- Unlock spawn control gui tab SetOarcGuiTabEnabled(player, OARC_SPAWN_CTRL_TAB_NAME, true) SetOarcGuiTabEnabled(requesting_buddy, OARC_SPAWN_CTRL_TAB_NAME, true) storage.buddy_pairs[player.name] = requesting_buddy_name storage.buddy_pairs[requesting_buddy_name] = player.name end ---Rejects a buddy spawn request proposal ---@param player LuaPlayer The player that is rejecting the buddy request ---@param requesting_buddy_name string The name of the player that is requesting to buddy spawn ---@return nil function RejectBuddyRequest(player, requesting_buddy_name) player.gui.screen.buddy_request_menu.destroy() DisplaySpawnOptions(player) local requester_buddy = game.players[requesting_buddy_name] if (requester_buddy == nil) then return end if (requester_buddy.gui.screen.buddy_wait_menu ~= nil) then requester_buddy.gui.screen.buddy_wait_menu.destroy() DisplaySpawnOptions(requester_buddy) end requester_buddy.print({ "oarc-buddy-declined", player.name }) end ---Display the please wait dialog ---@param player LuaPlayer ---@param delay_seconds integer ---@param surface LuaSurface ---@param position MapPosition ---@return nil function DisplayPleaseWaitForSpawnDialog(player, delay_seconds, surface, position) local pleaseWaitGui = player.gui.screen.add { name = "wait_for_spawn_dialog", type = "frame", direction = "vertical", caption = { "oarc-spawn-wait" } } pleaseWaitGui.auto_center = true pleaseWaitGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH pleaseWaitGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT -- Warnings and explanations... local wait_warning_text = { "oarc-wait-text", delay_seconds } AddLabel(pleaseWaitGui, "warning_lbl1", wait_warning_text, my_warning_style) local pleaseWaitGui_if = pleaseWaitGui.add { type = "frame", direction = "vertical", style = "inside_shallow_frame_with_padding" } pleaseWaitGui_if.add { type = "minimap", position = position, surface_index = surface.index, force = player.force.name } end ---Get a list of OTHER players currently in the spawn menu ---@param self_player LuaPlayer ---@return table function GetOtherPlayersInSpawnMenu(self_player) local other_players_in_spawn_menu = {} for _, player in pairs(game.connected_players) do if (player.gui.screen.spawn_opts ~= nil) and (self_player ~= player) then table.insert(other_players_in_spawn_menu, player.name) end end return other_players_in_spawn_menu end ---Gui click event handlers ---@param event EventData.on_gui_click ---@return nil function SeparateSpawnsGuiClick(event) WelcomeTextGuiClick(event) SpawnOptsGuiClick(event) end ---Gui checked state changed event handlers ---@param event EventData.on_gui_checked_state_changed ---@return nil function SeparateSpawnsGuiCheckedStateChanged(event) SpawnOptsRadioSelect(event) end ---Gui value changed event handlers ---@param event EventData.on_gui_value_changed ---@return nil function SeparateSpawnsGuiValueChanged(event) SpawnOptsValueChanged(event) end ---Gui selection state changed event handlers ---@param event EventData.on_gui_selection_state_changed ---@return nil function SeparateSpawnsGuiSelectionStateChanged(event) SpawnOptsSelectionChanged(event) end