local LaunchpadGUI = {} LaunchpadGUI.name_rocket_launch_pad_gui_root = mod_prefix.."rocket-launch-pad-gui" LaunchpadGUI.name_window_close = "launchpad_close_button" --- Create the launch pad gui for a player ---@param player any ---@param launch_pad Launchpad launch pad data function LaunchpadGUI.gui_open(player, launch_pad) LaunchpadGUI.gui_close(player) if not launch_pad then Log.trace('LaunchpadGUI.gui_open launch_pad not found') return end local struct = launch_pad local gui = player.gui.relative local playerdata = get_make_playerdata(player) local anchor = {gui=defines.relative_gui_type.container_gui, position=defines.relative_gui_position.left} local container = gui.add{ type = "frame", name = LaunchpadGUI.name_rocket_launch_pad_gui_root, anchor = anchor, style="space_platform_container", direction="vertical", -- use gui element tags to store a reference to what launch pad this gui is displaying/controls tags = { unit_number = struct.unit_number } } local title_table = container.add{type="table", name="launchpad-title-table", column_count=2, draw_horizontal_lines=false} title_table.style.horizontally_stretchable = true title_table.style.column_alignments[1] = "left" title_table.style.column_alignments[2] = "right" local title_frame = title_table.add{type="frame", name="launchpad-title-frame", caption = {"space-exploration.relative-window-settings"}, style="informatron_title_frame"} title_frame.style.right_padding = -5 local bar container.add{ type="label", name="crew_capsules", caption="Space Capsule: "} bar = container.add{ type="progressbar", name="crew_capsules_progress", size = 300, value=0, style="space_platform_progressbar_capsule"} bar.style.horizontally_stretchable = true container.add{ type="label", name="rocket_sections", caption="Rocket Sections: "} bar = container.add{ type="progressbar", name="rocket_sections_progress", size = 300, value=0, style="space_platform_progressbar_sections"} bar.style.horizontally_stretchable = true container.add{ type="label", name="fuel_capacity", caption="Liquid Rocket Fuel: "} bar = container.add{ type="progressbar", name="fuel_capacity_progress", size = 300, value=0, style="space_platform_progressbar_fuel"} bar.style.horizontally_stretchable = true container.add{ type="label", name="cargo_capacity", caption="Cargo: "} bar = container.add{ type="progressbar", name="cargo_capacity_progress", size = 300, value=0, style="space_platform_progressbar_cargo"} bar.style.horizontally_stretchable = true container.add{ type="label", name="trigger-label", caption="Launch Trigger"} local list, selected_index = dropdown_from_preset(Launchpad.trigger_options, struct.launch_trigger) local trigger_dropdown = container.add{ type="drop-down", name="trigger", items=list, selected_index = selected_index} trigger_dropdown.style.horizontally_stretchable = true container.add{ type="label", name="destination-label", caption="Destination:"} container.add{type="checkbox", name="list-zones-alphabetical", caption="List destinations alphabetically", state=playerdata.zones_alphabetical and true or false} local filter_container = container.add{ type="flow", name="filter_flow", direction="horizontal"} local filter_field = filter_container.add{ type="textfield", name="filter_list"} filter_field.style.width = 275 local filter_button = filter_container.add{ type = "sprite-button", name="clear_filter", sprite="utility/search_icon", tooltip={"space-exploration.clear-filter"},} filter_button.style.left_margin = 5 filter_button.style.width = 28 filter_button.style.height = 28 local destination_zone = struct.destination.zone local list, selected_index, values = Zone.dropdown_list_zone_destinations(struct.force_name, destination_zone, playerdata.zones_alphabetical, nil, {list = "Any landing pad with name", value = {type = "any"}} -- wildcard ) if selected_index == 1 then selected_index = 2 end local zones_dropdown = container.add{ type="drop-down", name="launchpad-list-zones", items=list, selected_index=selected_index or 2} zones_dropdown.style.horizontally_stretchable = true player_set_dropdown_values(player, "launchpad-list-zones", values) container.add{ type="label", name="destination-location-label", caption="Destination Position:"} local destination_pad_name = struct.destination.landing_pad_name local list, selected_index, values, landingpads_dropdown if destination_zone then list, selected_index, values = Landingpad.dropdown_list_zone_landing_pad_names(struct.force_name, destination_zone, destination_pad_name) landingpads_dropdown = container.add{ type="drop-down", name="launchpad-list-landing-pad-names", items=list, selected_index=selected_index} player_set_dropdown_values(player, "launchpad-list-landing-pad-names", values) else list, selected_index, values = Landingpad.dropdown_list_force_landing_pad_names(struct.force_name, destination_pad_name) landingpads_dropdown = container.add{ type="drop-down", name="launchpad-list-landing-pad-names", items=list, selected_index=selected_index} player_set_dropdown_values(player, "launchpad-list-landing-pad-names", values) end landingpads_dropdown.style.horizontally_stretchable = true container.add{ type="label", name="cargo_loss", caption="Cargo safety:"} container.add{ type="label", name="survivability_loss", caption="Landing chance:"} container.add{ type="label", name="status", caption="Status: "} LaunchpadGUI.gui_update(player) end function LaunchpadGUI.gui_update(player) local root = player.gui.relative[LaunchpadGUI.name_rocket_launch_pad_gui_root] if root then local struct = Launchpad.from_unit_number( root and root.tags and root.tags.unit_number) if struct then Launchpad.prep(struct) local inv = struct.container.get_inventory(defines.inventory.chest) local inv_used = count_inventory_slots_used(inv) struct.crew_capsules = struct.crew_capsules or 0 struct.rocket_sections = struct.rocket_sections or 0 if root["crew_capsules"] then root["crew_capsules"].caption="Space Capsule: " .. struct.crew_capsules .. " / " .. Launchpad.crew_capsules_per_rocket root["crew_capsules_progress"].value = struct.crew_capsules / Launchpad.crew_capsules_per_rocket end root["rocket_sections"].caption="Cargo Rocket Sections: " .. struct.rocket_sections .. " / " .. Launchpad.rocket_sections_per_rocket root["rocket_sections_progress"].value = struct.rocket_sections / Launchpad.rocket_sections_per_rocket if struct.required_fuel then --root["fuel_capacity"].caption="Liquid Rocket Fuel: " .. math.floor(math.min(struct.total_fuel, struct.required_fuel)) .. " / " .. math.floor(struct.required_fuel) root["fuel_capacity"].caption="Liquid Rocket Fuel: " .. Util.format_fuel(math.min(struct.total_fuel, struct.required_fuel)) .. " / " .. Util.format_fuel(struct.required_fuel) root["fuel_capacity_progress"].value=math.min(struct.total_fuel, struct.required_fuel) / (struct.required_fuel) else root["fuel_capacity"].caption="Liquid Rocket Fuel: " .. math.floor(struct.total_fuel) .. " / ?" root["fuel_capacity_progress"].value=0 end root["cargo_capacity"].caption="Cargo: " .. math.min(inv_used, Launchpad.rocket_capacity) .. " / " .. Launchpad.rocket_capacity root["cargo_capacity_progress"].value=math.min(inv_used, Launchpad.rocket_capacity) / Launchpad.rocket_capacity local message = "" local ready = "disabled" if not (global.forces[player.force.name] and global.forces[player.force.name].satellites_launched > 0) then message = "No navigation data, launch a satellite from the Satellite rocket silo." elseif struct.launch_status > -1 then message = "Launching Rocket!" elseif struct.rocket_sections < Launchpad.rocket_sections_per_rocket then message = "Constructing Rocket" elseif struct.crew_capsules < Launchpad.crew_capsules_per_rocket then message = "Space Capsule Required" elseif not struct.required_fuel then message = "Invalid Destination" elseif struct.total_fuel < struct.required_fuel then message = "Loading Fuel" elseif inv_used < Launchpad.rocket_capacity then message = "Ready - Loading Cargo" ready = "ready" else message = "Ready - Cargo Full" ready = "ready" end if ready == "ready" and struct.destination.landing_pad_name then if struct.destination.zone then local landing_pads = Landingpad.get_zone_landing_pads_availability(struct.force_name, struct.destination.zone, struct.destination.landing_pad_name) if #landing_pads.empty_landing_pads == 0 then message = "Waiting for available empty landing pad." ready = "delayed" if #landing_pads.filled_landing_pads == 0 then message = "Waiting for available landing pad." ready = "disabled" if #landing_pads.blocked_landing_pads == 0 then message = "No landing pads found matching name." end end end else local landing_pads = Landingpad.get_force_landing_pads_availability(struct.force_name, struct.destination.landing_pad_name) if #landing_pads.empty_landing_pads == 0 then message = "Waiting for available empty landing pad." ready = "delayed" if #landing_pads.filled_landing_pads == 0 then message = "Waiting for available landing pad." ready = "disabled" if #landing_pads.blocked_landing_pads == 0 then message = "No landing pads found matching name." end end end end end local delta_v = Zone.get_travel_delta_v(struct.zone, struct.destination.zone) if root.cargo_loss then local cargo_loss = Launchpad.get_cargo_loss(game.forces[struct.force_name], delta_v) if struct.destination and struct.destination.landing_pad_name then cargo_loss = cargo_loss / 2 end if struct.destination.zone then root.cargo_loss.caption = "Cargo safety: " .. string.format("%.2f", (1-cargo_loss)*100).."%" else root.cargo_loss.caption = "Cargo safety: Variable." end root.cargo_loss.tooltip = "Surviving stacks numbers are rounded up, unique items are never lost." end if root.survivability_loss then local survivability_loss = Launchpad.get_survivability_loss(game.forces[struct.force_name], delta_v) if struct.destination and struct.destination.landing_pad_name then if struct.destination.zone then root.survivability_loss.caption = "Landing chance: " .. string.format("%.2f", (1-survivability_loss)*100).."%" else root.survivability_loss.caption = "Landing chance: Variable." end root.survivability_loss.tooltip = "Failure results in loss of all rocket sections. Cargo is still delivered in cargo pods with minimal damage." else root.survivability_loss.caption = "Landing chance: Requires landing pad." root.survivability_loss.tooltip = "All rocket sections will be lost but cargo is still delivered in cargo pods with minimal damage." end end root["status"].caption="Status: " .. message if ready == "ready" or ready == "delayed" then if root["launch-disabled"] then root["launch-disabled"].destroy() end if not root["launch"] then local launch_button if ready == "delayed" then launch_button = root.add{ type="button", name="launch", caption="Launch (Delayed)", style="confirm_button", tooltip="Waiting for target launch pad to empty. Manual launch can override."} else launch_button = root.add{ type="button", name="launch", caption={"space-exploration.button-launch"}, style="confirm_button"} end launch_button.style.top_margin = 10 launch_button.style.horizontally_stretchable = true launch_button.style.horizontal_align = "left" end else if root["launch"] then root["launch"].destroy() end if not root["launch-disabled"] then local launch_button = root.add{ type="button", name="launch-disabled", caption={"space-exploration.button-launch-disabled"}, style="red_confirm_button"} launch_button.style.top_margin = 10 launch_button.style.horizontally_stretchable = true launch_button.style.horizontal_align = "left" end local launch_button = root["launch-disabled"] if struct.launch_status > -1 then launch_button.caption = "Launch in progress" else launch_button.caption = {"space-exploration.button-launch-disabled"} end end end end end function LaunchpadGUI.on_gui_click(event) if not (event.element and event.element.valid) then return end local element = event.element local player = game.players[event.player_index] local root = gui_element_or_parent(element, LaunchpadGUI.name_rocket_launch_pad_gui_root) if not root then return end local struct = Launchpad.from_unit_number( root and root.tags and root.tags.unit_number) if not struct then return end if element.name == "trigger" then struct.launch_trigger = selected_name_from_dropdown_preset(element, Launchpad.trigger_options) or "none" elseif element.name == "launchpad-list-zones" then local value = player_get_dropdown_value(player, element.name, element.selected_index) if type(value) == "table" then if value.type == "zone" then local zone_index = value.index local zone = Zone.from_zone_index(zone_index) if zone then struct.destination.zone = zone Log.trace("set destination to location: " .. zone.name ) end local pad_names = element.parent["launchpad-list-landing-pad-names"] local destination_pad_name = struct.destination.landing_pad_name local list, selected_index, values = Landingpad.dropdown_list_zone_landing_pad_names(struct.force_name, struct.destination.zone, destination_pad_name) pad_names.items = list pad_names.selected_index = selected_index player_set_dropdown_values(player, "launchpad-list-landing-pad-names", values) elseif value.type == "spaceship" then local spaceship_index = value.index local spaceship = Spaceship.from_index(spaceship_index) if spaceship then struct.destination.zone = spaceship Log.trace("set destination to spaceship : " .. spaceship.name ) end local pad_names = element.parent["launchpad-list-landing-pad-names"] local destination_pad_name = struct.destination.landing_pad_name local list, selected_index, values = Landingpad.dropdown_list_zone_landing_pad_names(struct.force_name, struct.destination.zone, destination_pad_name) pad_names.items = list pad_names.selected_index = selected_index player_set_dropdown_values(player, "launchpad-list-landing-pad-names", values) elseif value.type == "any" then struct.destination.zone = nil Log.trace("set destination to location: any") local pad_names = element.parent["launchpad-list-landing-pad-names"] local destination_pad_name = struct.destination.landing_pad_name local list, selected_index, values = Landingpad.dropdown_list_force_landing_pad_names(struct.force_name, destination_pad_name) pad_names.items = list pad_names.selected_index = selected_index player_set_dropdown_values(player, "launchpad-list-landing-pad-names", values) end LaunchpadGUI.gui_update(player) else LaunchpadGUI.gui_close(player) Log.trace("Error: Non-table value ") end elseif element.name == "launchpad-list-landing-pad-names" then local value = player_get_dropdown_value(player, element.name, element.selected_index) local landing_pad_name = value if landing_pad_name and landing_pad_name ~= "" then struct.destination.landing_pad_name = landing_pad_name Log.trace("set destination to landing_pad " .. landing_pad_name ) else struct.destination.landing_pad_name = nil end elseif element.name == "launch" then Launchpad.launch(struct, false, true) elseif element.name == "clear_filter" then element.parent.filter_list.text = "" LaunchpadGUI.gui_update_destinations_list(player) elseif element.name == LaunchpadGUI.name_window_close then LaunchpadGUI.gui_close(player) end end Event.addListener(defines.events.on_gui_click, LaunchpadGUI.on_gui_click) Event.addListener(defines.events.on_gui_selection_state_changed, LaunchpadGUI.on_gui_click) function LaunchpadGUI.gui_update_destinations_list(player) local playerdata = get_make_playerdata(player) local root = player.gui.relative[LaunchpadGUI.name_rocket_launch_pad_gui_root] if root then local struct = Launchpad.from_unit_number( root and root.tags and root.tags.unit_number) if not struct then return end local filter = nil if root.filter_flow and root.filter_flow.filter_list then filter = string.trim(root.filter_flow.filter_list.text) if filter == "" then filter = nil end end -- update the list local destination_zone = struct.destination and struct.destination.zone local list, selected_index, values = Zone.dropdown_list_zone_destinations( struct.force_name, destination_zone, playerdata.zones_alphabetical, filter, {list = "Any landing pad with name", value = {type = "any"}} -- wildcard ) if selected_index == 1 then selected_index = 2 end root["launchpad-list-zones"].items = list root["launchpad-list-zones"].selected_index = selected_index or 2 player_set_dropdown_values(player, "launchpad-list-zones", values) end end function LaunchpadGUI.on_gui_checked_state_changed(event) if not (event.element and event.element.valid) then return end local element = event.element local player = game.players[event.player_index] local root = gui_element_or_parent(element, LaunchpadGUI.name_rocket_launch_pad_gui_root) if not root then return end local struct = Launchpad.from_unit_number( root and root.tags and root.tags.unit_number) if not struct then return end if element.name == "list-zones-alphabetical" then local playerdata = get_make_playerdata(player) playerdata.zones_alphabetical = element.state LaunchpadGUI.gui_update_destinations_list(player) end end Event.addListener(defines.events.on_gui_checked_state_changed, LaunchpadGUI.on_gui_checked_state_changed) function LaunchpadGUI.on_gui_text_changed(event) if not (event.element and event.element.valid) then return end local element = event.element local player = game.players[event.player_index] local root = gui_element_or_parent(element, LaunchpadGUI.name_rocket_launch_pad_gui_root) if root then -- remote view if element.name == "filter_list" then LaunchpadGUI.gui_update_destinations_list(player) end end end Event.addListener(defines.events.on_gui_text_changed, LaunchpadGUI.on_gui_text_changed) --- Close the launchpad gui for a player ---@param player any function LaunchpadGUI.gui_close (player) if player.gui.relative[LaunchpadGUI.name_rocket_launch_pad_gui_root] then player.gui.relative[LaunchpadGUI.name_rocket_launch_pad_gui_root].destroy() end end --- Respond to the main entity GUI being closed by destroying the relative GUI ---@param event any function LaunchpadGUI.on_gui_closed(event) local player = game.players[event.player_index] if player and event.entity and event.entity.name == Launchpad.name_rocket_launch_pad then LaunchpadGUI.gui_close(player) end end Event.addListener(defines.events.on_gui_closed, LaunchpadGUI.on_gui_closed) --- Opens the launchpad gui when a launchpad is clicked --- Closes the launchpad gui when another gui is opened ---@param event any function LaunchpadGUI.on_gui_opened(event) local player = game.players[event.player_index] if event.entity and event.entity.valid and event.entity.name == Launchpad.name_rocket_launch_pad then LaunchpadGUI.gui_open(player, Launchpad.from_entity(event.entity)) else LaunchpadGUI.gui_close(player) end end Event.addListener(defines.events.on_gui_opened, LaunchpadGUI.on_gui_opened) return LaunchpadGUI