local Pin = {} --[[ Pins Pins lets the player save locations and quickly jump to them in navigation view. ]]-- -- constants Pin.name_button_add_pin = "remote-view-add-pin" Pin.tag_pin_button = "se-pin-button" Pin.name_button_confirm_pin = "remote-view-confirm-pin" Pin.name_button_cancel_pin = "remote-view-cancel-pin" Pin.name_button_update_position_pin = "remote-view-update-positioni-pin" Pin.name_button_pins = "remote-view-open_pins" Pin.name_dialog_root = "remote-view-dialog-root" Pin.name_hotkey_dropdown = "selector_content_frame_table_hotkey_dropdown" Pin.name_setting_expanded_tooltip = mod_prefix.."show-pin-help-tooltip" Pin.name_all_root = "remote-view-all-root" Pin.name_button_close = "remote-view-close-pins" Pin.min_zoom = 0.40 -- pretty close approximation to what vanilla lets you max zoom out Pin.max_zoom = 3.00 -- pretty close approximation to what vanilla lets you max zoom in -- goto pin hotkeys Pin.name_event_one = mod_prefix.."pin-one" Pin.name_event_two = mod_prefix.."pin-two" Pin.name_event_three = mod_prefix.."pin-three" Pin.name_event_four = mod_prefix.."pin-four" Pin.name_event_five = mod_prefix.."pin-five" Pin.name_event_six = mod_prefix.."pin-six" Pin.name_event_seven = mod_prefix.."pin-seven" Pin.name_event_eight = mod_prefix.."pin-eight" Pin.name_event_nine = mod_prefix.."pin-nine" Pin.name_event_zero = mod_prefix.."pin-zero" -- set pin hotkeys Pin.name_event_set_one = mod_prefix.."pin-set-one" Pin.name_event_set_two = mod_prefix.."pin-set-two" Pin.name_event_set_three = mod_prefix.."pin-set-three" Pin.name_event_set_four = mod_prefix.."pin-set-four" Pin.name_event_set_five = mod_prefix.."pin-set-five" Pin.name_event_set_six = mod_prefix.."pin-set-six" Pin.name_event_set_seven = mod_prefix.."pin-set-seven" Pin.name_event_set_eight = mod_prefix.."pin-set-eight" Pin.name_event_set_nine = mod_prefix.."pin-set-nine" Pin.name_event_set_zero = mod_prefix.."pin-set-zero" --[[ Opens the pin GUI as part of the navigation view GUI. ]]-- function Pin.gui_open(player) local root = player.gui.left[RemoteView.name_gui_root] local pin_table = root.add { type = "table", name = "pin_table", column_count = 4, style = "filter_slot_table" } pin_table.style.top_margin = 10 pin_table.style.horizontally_stretchable = true pin_table.style.column_alignments[1] = "left" pin_table.style.column_alignments[2] = "right" local pin_add_flow = pin_table.add{ type = "flow", name = "pin_add_flow" } local pin_list_flow = pin_table.add{ type = "flow", name = "pin_list_flow" } local new_pin_button = pin_add_flow.add { type = "sprite-button", sprite = ("se-pin-add"), name = Pin.name_button_add_pin, style="informatron_close_button", tooltip = {"space-exploration.remote-view-add-pin"}, mouse_button_filter = {"left"} } new_pin_button.style.width = 32 new_pin_button.style.height = 32 local add_pin_label = pin_add_flow.add { type = "label", name = "add_pin_label", caption = {"space-exploration.remote-view-pin-make"} } -- left add_pin_label.style.font_color = { r = 0.5, g = 0.5, b = 0.5 } add_pin_label.style.horizontally_stretchable = true add_pin_label.style.left_margin = 10 local list_pins_label = pin_list_flow.add { type = "label", name = "list_pins_label", caption = {"space-exploration.remote-view-pins-list"} } -- left list_pins_label.style.font_color = { r = 0.5, g = 0.5, b = 0.5 } list_pins_label.style.horizontally_stretchable = true list_pins_label.style.right_margin = 10 local pins = pin_list_flow.add{ type="sprite-button", name=Pin.name_button_pins, sprite = "se-pin-list", style="informatron_close_button", tooltip={"space-exploration.open-pins-remote-view"} } pins.style.width = 32 pins.style.height = 32 local pin_frame = root.add { type = "frame", name = "pin_frame", style = "se_frame_deep_slots_small" } pin_frame.style.horizontally_stretchable = true pin_frame.style.top_margin = 10 local pins_table = pin_frame.add { type = "table", name = "pins_table", column_count = 10, style = "filter_slot_table" } end -- Maps the hotkey index to the tooltip string local tooltip_map = {} -- 0 is the dropdown menu for no selection (we don't use this) -- 1 is the dropdown menu for none hotkey (we use this) tooltip_map[2] = "space-exploration.remote-view-pin-button-tooltip-hotkey-one" tooltip_map[3] = "space-exploration.remote-view-pin-button-tooltip-hotkey-two" tooltip_map[4] = "space-exploration.remote-view-pin-button-tooltip-hotkey-three" tooltip_map[5] = "space-exploration.remote-view-pin-button-tooltip-hotkey-four" tooltip_map[6] = "space-exploration.remote-view-pin-button-tooltip-hotkey-five" tooltip_map[7] = "space-exploration.remote-view-pin-button-tooltip-hotkey-six" tooltip_map[8] = "space-exploration.remote-view-pin-button-tooltip-hotkey-seven" tooltip_map[9] = "space-exploration.remote-view-pin-button-tooltip-hotkey-eight" tooltip_map[10] = "space-exploration.remote-view-pin-button-tooltip-hotkey-nine" tooltip_map[11] = "space-exploration.remote-view-pin-button-tooltip-hotkey-zero" --[[ Determines the appropriate tooltip to use for a certain pin. Based on if the pin has an associated hotkey, it will use a different tooltip to show the hotkey in the tooltip. ]]-- function Pin.get_tooltip_for_pin(playerdata, item_name) for key, value in pairs(playerdata.saved_hotkeys) do if value == item_name then return tooltip_map[key] end end return "space-exploration.remote-view-pin-button-tooltip" end --[[ Makes the data valid. Deletes quick pins to deleted surfaces/spaceships. ]] function Pin.make_valid(playerdata) -- determine invalid pins for key, value in pairs(playerdata.saved_pins) do if key ~= "id" then if not value then playerdata.saved_pins[key] = nil else local location = Location.from_reference(value.location_reference) if not location or not location.zone then playerdata.saved_pins[key] = nil end end end end -- replace the old pin table with a new pin table that doesn't include the invalid ones local saved_pins_new = {} for key, value in pairs(playerdata.saved_pins) do if value then saved_pins_new[key] = value end end playerdata.saved_pins = saved_pins_new -- remove hotkeys for deleted pins for key, value in pairs(playerdata.saved_hotkeys) do if not playerdata.saved_pins[value] then playerdata.saved_hotkeys[key] = nil end end end --[[ If it is valid for the player to add or update the position of pins from their current zone. ]] function Pin.add_or_update_pin_position_valid(player) local root = player.gui.left[RemoteView.name_gui_root] if root then local currently_viewing_frame = root.currently_viewing_frame if currently_viewing_frame then return currently_viewing_frame.visible end end return true end --[[ Rebuilds the entire quick pin GUI. Necessary to call in response to changes to the data displayed in the GUI. ]]-- function Pin.gui_update(player) local playerdata = Pin.get_make_pin_playerdata(player) -- makes the data valid (fix deletes surfaces) Pin.make_valid(playerdata) -- update the window too if it exists Pin.window_update(player) -- clear the data-driven part of the GUI local root = player.gui.left[RemoteView.name_gui_root] if not root then return end local pins_table = root.pin_frame.pins_table pins_table.clear() -- build the data-driven part of the GUI local add_or_update_pin_position_valid = Pin.add_or_update_pin_position_valid(player) for key, value in pairs(playerdata.saved_pins) do if key ~= "id" then local sprite_path = Pin.signal_to_sprite_path(value.signal) local location = Location.from_reference(value.location_reference) local name = location.name or "" local tooltip = {"", {Pin.get_tooltip_for_pin(playerdata, key), location.zone.name, name}} if settings.get_player_settings(player)[Pin.name_setting_expanded_tooltip].value == true then table.insert(tooltip, {"space-exploration.remote-view-pin-button-tooltip-help-text"}) if add_or_update_pin_position_valid then table.insert(tooltip, {"space-exploration.remote-view-pin-button-tooltip-help-text-update-position"}) end end if location.zone == Zone.from_surface(player.surface) then local pin_button = pins_table.add { type = "sprite-button", sprite = sprite_path, tags = { action = Pin.tag_pin_button, item_name = key }, style = "se_sprite-button_inset", tooltip = tooltip } end end end -- add the new-pin button at the end of the list so it's always last if add_or_update_pin_position_valid then root.pin_table.pin_add_flow.add_pin_label.visible = true root.pin_table.pin_add_flow[Pin.name_button_add_pin].visible = true else root.pin_table.pin_add_flow.add_pin_label.visible = false root.pin_table.pin_add_flow[Pin.name_button_add_pin].visible = false end -- if there are no pins and adding pins is not valid, make the UI for pins invisible if #pins_table.children > 0 then --root.pin_table.visible = true root.pin_frame.visible = true else --root.pin_table.visible = false root.pin_frame.visible = false end end --[[ Handles click events anywhere on the UI: clicks on the add-pin button uses Pin.modal_open delegates clicks on pin buttons to Pin.on_gui_pin_button_click delegates clicks on the modal to Pin.on_gui_modal_click Return value is if the quick pin UI consumed the event. ]]-- function Pin.on_gui_click(event) local element = event.element local player = game.players[event.player_index] if element.name == Pin.name_button_pins then Pin.window_toggle(player) return end if element.tags and element.tags.action == Pin.tag_pin_button then Pin.on_gui_pin_button_click(player, event) return true elseif element.name == Pin.name_button_add_pin then local playerdata = Pin.get_make_pin_playerdata(player) local id = playerdata.saved_pins.id local item_name = "pin-button-prefix-" .. id playerdata.saved_pins.id = playerdata.saved_pins.id + 1 Pin.modal_open(player, item_name) return true else local root = gui_element_or_parent(element, Pin.name_dialog_root) if root then Pin.on_gui_modal_click(player, root, element) return true else root = gui_element_or_parent(element, Pin.name_all_root) if root then Pin.on_gui_window_click(player, root, element) return true end end end return false end --[[ Handles the GUI attempting to be closed. ]] function Pin.on_gui_closed (event) if event.element and event.element.valid and event.element.name == Pin.name_all_root then Pin.window_close(game.get_player(event.player_index)) elseif event.element and event.element.valid and event.element.name == Pin.name_dialog_root then Pin.modal_close(game.get_player(event.player_index)) end end Event.addListener(defines.events.on_gui_closed, Pin.on_gui_closed) --[[ Handles click events on the modal. ]] function Pin.on_gui_modal_click(player, root, element) if element.name == Pin.name_button_confirm_pin then local name_textfield = root.selector_content_flow.selector_content_frame.selector_content_frame_flow .selector_content_frame_table.selector_content_frame_table_name_textfield local icon_selector = root.selector_content_flow.selector_content_frame.selector_content_frame_flow .selector_content_frame_table.selector_content_frame_table_icon_selector local zoom_textfield = root.selector_content_flow.selector_content_frame.selector_content_frame_flow .selector_content_frame_table.selector_content_frame_table_zoom_flow.selector_content_frame_table_zoom_flow_textfield local hotkey_dropdown = root.selector_content_flow.selector_content_frame.selector_content_frame_flow .selector_content_frame_table.selector_content_frame_table_hotkey_dropdown local text = name_textfield.text local icon = icon_selector.elem_value local zoom = zoom_textfield.text local hotkey = hotkey_dropdown.selected_index Pin.add_update_pin(player, text, icon, zoom, hotkey, true) Pin.modal_close(player) elseif element.name == Pin.name_button_cancel_pin then Pin.modal_close(player) elseif element.name == Pin.name_button_update_position_pin then local add_or_update_pin_position_valid = Pin.add_or_update_pin_position_valid(player) -- don't allow the player to update the position of a pin when their current position isn't valid if add_or_update_pin_position_valid then local playerdata = Pin.get_make_pin_playerdata(player) local saved_pin = playerdata.saved_pins[playerdata.quick_pin_opened_item_name] saved_pin.location_reference = Location.update_reference_position(saved_pin.location_reference, Zone.from_surface(player.surface), player.position) -- since we don't have a method for rebuilding the modal gui, make the changes to it here local root = player.gui.screen[Pin.name_dialog_root] if root then root.selector_content_flow.selector_content_frame.selector_content_frame_flow .selector_content_frame_table.selector_content_frame_table_location_flow .selector_content_frame_table_location_flow_label.caption = {"space-exploration.remote-view-pin-location-position", string.format("%.0f", player.position.x), string.format("%.0f", player.position.y)} end end elseif element.name == "goto_informatron_pins" then remote.call("informatron", "informatron_open_to_page", { player_index = player.index, interface = "space-exploration", page_name = "pinned_locations" }) end end --[[ Handles click events on the window. ]] function Pin.on_gui_window_click(player, root, element) if element.name == Pin.name_button_close then Pin.window_close(player) elseif element.name == "goto_informatron_pins" then remote.call("informatron", "informatron_open_to_page", { player_index = player.index, interface = "space-exploration", page_name = "pinned_locations" }) end end --[[ Closes this GUI. ]] function Pin.gui_close(player) Pin.modal_close(player) Pin.window_close(player) end --[[ Closes the modal. ]]-- function Pin.modal_close(player) if player.gui.screen[Pin.name_dialog_root] then player.gui.screen[Pin.name_dialog_root].destroy() end end --[[ Converts a signal to a sprite path. The signal is chosen from a "choose-elem-button" UI element with "elem_type" set to "signal". ]] function Pin.signal_to_sprite_path(signal) if signal and signal.name then if signal.type == "item" then return "item/" .. signal.name elseif signal.type == "fluid" then return "fluid/" .. signal.name elseif signal.type == "virtual" then return "virtual-signal/" .. signal.name end end return nil end --[[ Converts a signal to a rich text icon. The signal is chosen from a "choose-elem-button" UI element with "elem_type" set to "signal". ]]-- function Pin.signal_to_rich_text(signal) if signal and signal.name then if signal.type == "item" then return "[img=item."..signal.name.."]" elseif signal.type == "fluid" then return "[img=fluid."..signal.name.."]" elseif signal.type == "virtual" then return "[img=virtual-signal."..signal.name.."]" end end return "" end --[[ Makes the rich text icon string for the hotkey dropdown option. Each hotkey dropdown displays the hotkey itself followed by a rich text icon of the pin it is for. This function creates that rich text icon based on the index of the dropdown option. ]]-- function Pin.make_hotkey_string(playerdata, index) if playerdata.saved_hotkeys[index] then local saved_pin = playerdata.saved_pins[playerdata.saved_hotkeys[index]] if saved_pin then return Pin.signal_to_rich_text(saved_pin.signal) end end return "" end --[[ Opens a modal for selecting the properties of the pin icon on the bar. The modal is setup to match the look/feel of the vanilla map tag UI. ]]-- function Pin.modal_open(player, item_name) Pin.modal_close(player) -- always start fresh local playerdata = Pin.get_make_pin_playerdata(player) playerdata.quick_pin_opened_item_name = item_name local selector_frame = player.gui.screen.add { type = "frame", name = Pin.name_dialog_root, direction = "vertical" } selector_frame.style.maximal_height = 1440 selector_frame.auto_center = true player.opened = selector_frame if not (selector_frame and selector_frame.valid) then return end -- setting player.opened can cause other mods to close GUIs local selector_title_flow = selector_frame.add { type = "flow", direction = "horizontal" } selector_title_flow.drag_target = selector_frame selector_title_flow.style.vertically_stretchable = "off" local selector_title_label = selector_title_flow.add { type = "label", caption = {"space-exploration.remote-view-pin-details"}, style = "frame_title", ignored_by_interaction = true } selector_title_label.style.vertically_stretchable = "on" selector_title_label.style.horizontally_squashable = "on" local selector_title_empty = selector_title_flow.add { type = "empty-widget", style = "draggable_space", ignored_by_interaction = true } selector_title_empty.style.horizontally_stretchable = "on" selector_title_empty.style.left_margin = 4 selector_title_empty.style.right_margin = 0 selector_title_empty.style.height = 24 local selector_title_informatron = selector_title_flow.add { type="sprite-button", name="goto_informatron_pins", sprite = "virtual-signal/informatron", style="informatron_close_button", tooltip={"space-exploration.informatron-open-help"} } selector_title_informatron.style.width = 28 selector_title_informatron.style.height = 28 local selector_content_flow = selector_frame.add { type = "flow", name = "selector_content_flow", direction = "vertical" } local selector_content_frame = selector_content_flow.add { type = "frame", name = "selector_content_frame", direction = "vertical", style = "inside_shallow_frame_with_padding" } selector_content_frame.style.horizontally_stretchable = "on" local selector_content_frame_flow = selector_content_frame.add { type = "flow", name = "selector_content_frame_flow", direction = "horizontal" } selector_content_frame_flow.style.horizontally_stretchable = "on" local selector_content_frame_table = selector_content_frame_flow.add { type = "table", name = "selector_content_frame_table", column_count = 2, style = "player_input_table" } local selector_content_frame_table_name_label = selector_content_frame_table.add { type = "label", caption = {"space-exploration.remote-view-pin-name"} } local selector_content_frame_table_name_textfield = selector_content_frame_table.add { type = "textfield", name = "selector_content_frame_table_name_textfield" } selector_content_frame_table_name_textfield.style.maximal_width = 0 selector_content_frame_table_name_textfield.style.horizontally_stretchable = "on" selector_content_frame_table_name_textfield.focus() selector_content_frame_table_name_textfield.select_all() local selector_content_frame_table_icon_label = selector_content_frame_table.add { type = "label", caption = {"space-exploration.remote-view-pin-icon"} } local selector_content_frame_table_icon_selector = selector_content_frame_table.add { type = "choose-elem-button", elem_type = "signal", name = "selector_content_frame_table_icon_selector" } local selector_content_frame_table_zoom_label = selector_content_frame_table.add { type = "label", caption = {"space-exploration.remote-view-pin-zoom"} } local selector_content_frame_table_zoom_flow = selector_content_frame_table.add { type = "flow", direction = "horizontal", name = "selector_content_frame_table_zoom_flow" } selector_content_frame_table_zoom_flow.style.vertical_align = "center" local selector_content_frame_table_zoom_flow_slider = selector_content_frame_table_zoom_flow.add { type = "slider", minimum_value = Pin.min_zoom, maximum_value = Pin.max_zoom, discrete_values = false, name = "selector_content_frame_table_zoom_flow_slider" } local selector_content_frame_table_zoom_flow_textfield = selector_content_frame_table_zoom_flow.add { type = "textfield", numeric = true, allow_decimal = true, name = "selector_content_frame_table_zoom_flow_textfield" } selector_content_frame_table_zoom_flow_textfield.style.width = 40 local selector_content_frame_table_hotkey_label = selector_content_frame_table.add { type = "label", caption = {"space-exploration.remote-view-pin-hotkey"} } local selector_content_frame_table_hotkey_dropdown = selector_content_frame_table.add { type = "drop-down", name = Pin.name_hotkey_dropdown, items = { {"space-exploration.remote-view-pin-hotkey-none"}, {"space-exploration.remote-view-pin-hotkey-one", Pin.make_hotkey_string(playerdata, 2)}, {"space-exploration.remote-view-pin-hotkey-two", Pin.make_hotkey_string(playerdata, 3)}, {"space-exploration.remote-view-pin-hotkey-three", Pin.make_hotkey_string(playerdata, 4)}, {"space-exploration.remote-view-pin-hotkey-four", Pin.make_hotkey_string(playerdata, 5)}, {"space-exploration.remote-view-pin-hotkey-five", Pin.make_hotkey_string(playerdata, 6)}, {"space-exploration.remote-view-pin-hotkey-six", Pin.make_hotkey_string(playerdata, 7)}, {"space-exploration.remote-view-pin-hotkey-seven", Pin.make_hotkey_string(playerdata, 8)}, {"space-exploration.remote-view-pin-hotkey-eight", Pin.make_hotkey_string(playerdata, 9)}, {"space-exploration.remote-view-pin-hotkey-nine", Pin.make_hotkey_string(playerdata, 10)}, {"space-exploration.remote-view-pin-hotkey-zero", Pin.make_hotkey_string(playerdata, 11)} }, selected_index = 1 -- start with none selected } -- update location is only valid if the saved_pin we are updating has a valid location local saved_pin = playerdata.saved_pins[playerdata.quick_pin_opened_item_name] if saved_pin then local location = Location.from_reference(saved_pin.location_reference) if location and location.position and location.zone == Zone.from_surface(player.surface) then local selector_content_frame_table_location_label = selector_content_frame_table.add { type = "label", caption = {"space-exploration.remote-view-pin-location"} } local selector_content_frame_table_location_flow = selector_content_frame_table.add { type = "flow", direction = "horizontal", name = "selector_content_frame_table_location_flow" } selector_content_frame_table_location_flow.style.vertical_align = "center" local selector_content_frame_table_location_flow_button = selector_content_frame_table_location_flow.add { type = "button", name = Pin.name_button_update_position_pin, caption = {"space-exploration.remote-view-pin-location-position-button"}, tooltip = {"space-exploration.remote-view-pin-location-position-button-tooltip"}, mouse_button_filter = {"left"} } local selector_content_frame_table_location_flow_label = selector_content_frame_table_location_flow.add { type = "label", caption = {"space-exploration.remote-view-pin-location-position", string.format("%.0f", location.position.x), string.format("%.0f", location.position.y)}, name = "selector_content_frame_table_location_flow_label" } end end -- Button bar local button_bar = selector_frame.add { type = "flow", direction = "horizontal", style = "dialog_buttons_horizontal_flow" } button_bar.style.horizontal_spacing = 0 button_bar.drag_target = selector_frame -- Cancel/Back button local button_cancel = button_bar.add { type = "button", name = Pin.name_button_cancel_pin, style = "back_button", caption = {"gui-tag-edit.cancel"}, tooltip = {"gui.cancel-instruction"}, mouse_button_filter = {"left"} } button_cancel.style.minimal_width = 0 button_cancel.style.padding = {1, 12, 0, 12} local button_empty = button_bar.add { type = "empty-widget", style = "draggable_space", ignored_by_interaction = true } button_empty.style.horizontally_stretchable = "on" button_empty.style.left_margin = 10 button_empty.style.right_margin = 10 button_empty.style.height = 24 -- Submit button local button_submit = button_bar.add { type = "button", name = Pin.name_button_confirm_pin, caption = {"gui-tag-edit.confirm"}, tooltip = {"gui.confirm-instruction"}, style = "confirm_button", mouse_button_filter = {"left"} } button_submit.style.minimal_width = 0 button_submit.style.padding = {1, 8, 0, 12} -- if we are opening an existing pin to modify it, pre-populate the modal with the info for that pin if saved_pin then local location = Location.from_reference(saved_pin.location_reference) selector_content_frame_table_name_textfield.text = location.name or "" selector_content_frame_table_icon_selector.elem_value = saved_pin.signal selector_content_frame_table_zoom_flow_slider.slider_value = saved_pin.zoom or selector_content_frame_table_zoom_flow_slider.slider_value selector_content_frame_table_hotkey_dropdown.selected_index = 1 for key, value in pairs(playerdata.saved_hotkeys) do if value == playerdata.quick_pin_opened_item_name then selector_content_frame_table_hotkey_dropdown.selected_index = key end end end selector_content_frame_table_zoom_flow_textfield.text = string.format("%.2f", selector_content_frame_table_zoom_flow_slider.slider_value) end --[[ Clamps zoom to reasonable values. Must not be nil ]] function Pin.clamp_zoom(zoom) if zoom < Pin.min_zoom then zoom = Pin.min_zoom end if zoom > Pin.max_zoom then zoom = Pin.max_zoom end return zoom end --[[ Handles text changes to the zoom textfield. ]]-- function Pin.on_gui_text_changed(event) if event.element.name == "selector_content_frame_table_zoom_flow_textfield" then local player = game.players[event.player_index] local root = player.gui.screen[Pin.name_dialog_root] local zoom_slider = root.selector_content_flow.selector_content_frame.selector_content_frame_flow .selector_content_frame_table.selector_content_frame_table_zoom_flow.selector_content_frame_table_zoom_flow_slider local zoom = tonumber(event.text) -- empty textbox gives nil if zoom then zoom = Pin.clamp_zoom(zoom) zoom_slider.slider_value = zoom end end end Event.addListener(defines.events.on_gui_text_changed, Pin.on_gui_text_changed) --[[ Handles value changes to the zoom slider. ]]-- function Pin.on_gui_value_changed(event) if event.element.name == "selector_content_frame_table_zoom_flow_slider" then local player = game.players[event.player_index] local root = player.gui.screen[Pin.name_dialog_root] local zoom_textfield = root.selector_content_flow.selector_content_frame.selector_content_frame_flow .selector_content_frame_table.selector_content_frame_table_zoom_flow.selector_content_frame_table_zoom_flow_textfield local zoom = event.element.slider_value zoom_textfield.text = string.format("%.2f", zoom) end end Event.addListener(defines.events.on_gui_value_changed, Pin.on_gui_value_changed) -- Maps the input event name to the index that we save that hotkey's pin at. local hotkey_map = {} -- 0 is the dropdown menu for no selection (we don't use this) -- 1 is the dropdown menu for none hotkey (we use this) hotkey_map[Pin.name_event_one]=2 hotkey_map[Pin.name_event_two]=3 hotkey_map[Pin.name_event_three]=4 hotkey_map[Pin.name_event_four]=5 hotkey_map[Pin.name_event_five]=6 hotkey_map[Pin.name_event_six]=7 hotkey_map[Pin.name_event_seven]=8 hotkey_map[Pin.name_event_eight]=9 hotkey_map[Pin.name_event_nine]=10 hotkey_map[Pin.name_event_zero]=11 hotkey_map[Pin.name_event_set_one]=2 hotkey_map[Pin.name_event_set_two]=3 hotkey_map[Pin.name_event_set_three]=4 hotkey_map[Pin.name_event_set_four]=5 hotkey_map[Pin.name_event_set_five]=6 hotkey_map[Pin.name_event_set_six]=7 hotkey_map[Pin.name_event_set_seven]=8 hotkey_map[Pin.name_event_set_eight]=9 hotkey_map[Pin.name_event_set_nine]=10 hotkey_map[Pin.name_event_set_zero]=11 --[[ Handles pin hotkey presses. ]]-- function Pin.on_pin_hotkey_press(event) local player_index = event.player_index if player_index and game.players[player_index] and game.players[player_index].connected then local player = game.players[player_index] local playerdata = Pin.get_make_pin_playerdata(player) local index = hotkey_map[event.input_name] if index then if playerdata.saved_hotkeys[index] then Pin.do_pin(player, playerdata.saved_hotkeys[index]) end end end end Event.addListener(Pin.name_event_one, Pin.on_pin_hotkey_press) Event.addListener(Pin.name_event_two, Pin.on_pin_hotkey_press) Event.addListener(Pin.name_event_three, Pin.on_pin_hotkey_press) Event.addListener(Pin.name_event_four, Pin.on_pin_hotkey_press) Event.addListener(Pin.name_event_five, Pin.on_pin_hotkey_press) Event.addListener(Pin.name_event_six, Pin.on_pin_hotkey_press) Event.addListener(Pin.name_event_seven, Pin.on_pin_hotkey_press) Event.addListener(Pin.name_event_eight, Pin.on_pin_hotkey_press) Event.addListener(Pin.name_event_nine, Pin.on_pin_hotkey_press) Event.addListener(Pin.name_event_zero, Pin.on_pin_hotkey_press) --[[ Handles set hotkey presses. ]] function Pin.on_set_hotkey_press(event) local player_index = event.player_index if player_index and game.players[player_index] and game.players[player_index].connected then local player = game.players[player_index] local playerdata = Pin.get_make_pin_playerdata(player) local index = hotkey_map[event.input_name] if index then local id = playerdata.saved_pins.id local item_name = "pin-button-prefix-" .. id playerdata.saved_pins.id = playerdata.saved_pins.id + 1 playerdata.quick_pin_opened_item_name = item_name local zone = Zone.from_surface(player.surface) if zone then Pin.add_update_pin(player, nil, { type = "virtual", name = Zone.get_signal_name(zone) }, Pin.min_zoom, index, false) end end end end Event.addListener(Pin.name_event_set_one, Pin.on_set_hotkey_press) Event.addListener(Pin.name_event_set_two, Pin.on_set_hotkey_press) Event.addListener(Pin.name_event_set_three, Pin.on_set_hotkey_press) Event.addListener(Pin.name_event_set_four, Pin.on_set_hotkey_press) Event.addListener(Pin.name_event_set_five, Pin.on_set_hotkey_press) Event.addListener(Pin.name_event_set_six, Pin.on_set_hotkey_press) Event.addListener(Pin.name_event_set_seven, Pin.on_set_hotkey_press) Event.addListener(Pin.name_event_set_eight, Pin.on_set_hotkey_press) Event.addListener(Pin.name_event_set_nine, Pin.on_set_hotkey_press) Event.addListener(Pin.name_event_set_zero, Pin.on_set_hotkey_press) --[[ Removes any pins that have the given hotkey as long as they are not player-modified. This allows for the pin-set-hotkeys that create new locations to overwrite each other and not leave a bunch of permanent pins if used a bunch of times in a row without the player explicitly indicating that they want to permanently keep the hotkey by manually opening the modal and changing something in it. ]]-- function Pin.remove_unmodified_with_hotkey(playerdata, hotkey) if hotkey then for key, value in pairs(playerdata.saved_hotkeys) do if key == hotkey and value and playerdata.saved_pins[value] then if not playerdata.saved_pins[value].player_modified then playerdata.saved_pins[value] = nil end end end end end --[[ Updates the hotkey associated with a pin. First clears the hotkey associated with the pin. Then adds the new hotkey. ]]-- function Pin.set_hotkey(playerdata, item_name, hotkey) if hotkey then for key, value in pairs(playerdata.saved_hotkeys) do if value == item_name then playerdata.saved_hotkeys[key] = nil end end -- only save hotkey information for proper hotkeys (not unselected or none) if hotkey > 1 then playerdata.saved_hotkeys[hotkey] = item_name end end end --[[ Creates or updates a pin. Always sets the pin's name and icon. When creating an entirely new pin, the pin's position is set based on the player's current position in the navigation view. Forces a GUI rebuild. ]]-- function Pin.add_update_pin(player, name, signal, zoom, hotkey, player_modified) local playerdata = Pin.get_make_pin_playerdata(player) playerdata.saved_pins[playerdata.quick_pin_opened_item_name] = playerdata.saved_pins[playerdata.quick_pin_opened_item_name] or {} local saved_pin = playerdata.saved_pins[playerdata.quick_pin_opened_item_name] saved_pin.signal = signal zoom = tonumber(zoom) or 1 zoom = Pin.clamp_zoom(zoom) saved_pin.zoom = tonumber(string.format("%.2f", zoom)) saved_pin.player_modified = player_modified if not saved_pin.location_reference then saved_pin.location_reference = Location.make_reference(Zone.from_surface(player.surface), player.position, name) else saved_pin.location_reference = Location.update_reference_name(saved_pin.location_reference, name) end Pin.remove_unmodified_with_hotkey(playerdata, hotkey) Pin.set_hotkey(playerdata, playerdata.quick_pin_opened_item_name, hotkey) Pin.gui_update(player) end --[[ Performs a pin. Opens/moves the player's navigation view to the pin's position. Only attempts to perform the pin if the target is valid. ]]-- function Pin.do_pin(player, item_name) if item_name then local playerdata = Pin.get_make_pin_playerdata(player) local saved_pin = playerdata.saved_pins[item_name] if saved_pin then local location = Location.from_reference(saved_pin.location_reference) if location then RemoteView.start(player, location.zone, location.position, location.name) end -- if playerdata.surface_positions then -- playerdata.surface_positions[player.surface.index] = player.position -- end player.close_map() if saved_pin.zoom then player.zoom = saved_pin.zoom end end end end --[[ Deletes a pin. Forces a GUI rebuild. ]]-- function Pin.delete_pin(player, item_name) local playerdata = Pin.get_make_pin_playerdata(player) playerdata.saved_pins[item_name] = nil Pin.gui_update(player) end --[[ Handles click events for the pin buttons. Four distinct events are tracked: left-click performs a pin right-click opens the modal for editing the pin shift-right-click resets the pin position to the current location ctrl-right-click deletes the pin ]]-- function Pin.on_gui_pin_button_click(player, event) local element = event.element if not element or not element.tags or not element.tags.item_name then return end local item_name = element.tags.item_name if event.button == defines.mouse_button_type.left then Pin.do_pin(player, item_name) elseif event.button == defines.mouse_button_type.right then if event.control then Pin.delete_pin(player, item_name) elseif event.shift then local add_or_update_pin_position_valid = Pin.add_or_update_pin_position_valid(player) -- don't allow the player to update the position of a pin when their current position isn't valid if add_or_update_pin_position_valid then local playerdata = Pin.get_make_pin_playerdata(player) local saved_pin = playerdata.saved_pins[item_name] saved_pin.location_reference = Location.update_reference_position(saved_pin.location_reference, Zone.from_surface(player.surface), player.position) end else Pin.modal_open(player, item_name) end end end --[[ Closes the window. ]]-- function Pin.window_close(player) if player.gui.screen[Pin.name_all_root] then player.gui.screen[Pin.name_all_root].destroy() end end --[[ Toggles the window. ]]-- function Pin.window_toggle(player) if player.gui.screen[Pin.name_all_root] then Pin.window_close(player) else Pin.window_open(player) end end --[[ Opens the window for viewing all pins. ]]-- function Pin.window_open(player) Pin.window_close(player) local playerdata = Pin.get_make_pin_playerdata(player) local viewer_frame = player.gui.screen.add { type = "frame", name = Pin.name_all_root, direction = "vertical" } viewer_frame.style.maximal_height = 1440 viewer_frame.auto_center = true player.opened = viewer_frame if not (viewer_frame and viewer_frame.valid) then return end -- setting player.opened can cause other scripts to delete UIs local viewer_title_table = viewer_frame.add { type = "table", column_count = 2, draw_horizontal_lines = false, } viewer_title_table.style.horizontally_stretchable = true viewer_title_table.style.column_alignments[1] = "left" viewer_title_table.style.column_alignments[2] = "right" local viewer_title_flow = viewer_title_table.add { type = "flow", direction = "horizontal" } viewer_title_flow.drag_target = viewer_frame viewer_title_flow.style.vertically_stretchable = false local viewer_title_label = viewer_title_flow.add { type = "label", caption = {"space-exploration.remote-view-pin-viewer-title"}, style = "frame_title", ignored_by_interaction = true } viewer_title_label.style.vertically_stretchable = true viewer_title_label.style.horizontally_squashable = true local viewer_title_empty = viewer_title_flow.add { type = "empty-widget", style = "draggable_space", ignored_by_interaction = true } viewer_title_empty.style.horizontally_stretchable = true viewer_title_empty.style.left_margin = 4 viewer_title_empty.style.right_margin = 0 viewer_title_empty.style.height = 24 local viewer_title_flow_right = viewer_title_table.add { type = "flow", direction = "horizontal" } local viewer_title_informatron = viewer_title_flow_right.add { type="sprite-button", name="goto_informatron_pins", sprite = "virtual-signal/informatron", style="informatron_close_button", tooltip={"space-exploration.informatron-open-help"} } viewer_title_informatron.style.width = 28 viewer_title_informatron.style.height = 28 local viewer_title_close = viewer_title_flow_right.add { type="sprite-button", name=Pin.name_button_close, sprite = "utility/close_white", style="informatron_close_button", tooltip={"space-exploration.exit-pins-remote-view"} } viewer_title_close.style.width = 28 viewer_title_close.style.height = 28 local viewer_content_flow = viewer_frame.add { type = "scroll-pane", name = "viewer_content_flow", horizontal_scroll_policy = "never" } viewer_content_flow.style.height = 420 viewer_content_flow.style.minimal_width = 400 viewer_content_flow.style.padding = 0 Pin.window_update(player) end --[[ Makes a row containing some pins for the window. ]] function Pin.make_window_row(player, playerdata, viewer_content_frame_flow, pins, zone_name) local pin_row_label = viewer_content_frame_flow.add { type = "label", caption = zone_name } -- left pin_row_label.style.font_color = { r = 0.5, g = 0.5, b = 0.5 } local pin_row_frame = viewer_content_frame_flow.add { type = "frame", style = "se_frame_deep_slots_small" } pin_row_frame.style.horizontally_stretchable = true local pin_row_table = pin_row_frame.add { type = "table", column_count = 10, style = "filter_slot_table" } pin_row_table.style.width = 400 local add_or_update_pin_position_valid = Pin.add_or_update_pin_position_valid(player) for key, value in pairs(pins) do local sprite_path = Pin.signal_to_sprite_path(value.signal) local location = Location.from_reference(value.location_reference) if location then local name = location.name or "" local tooltip = {"", {Pin.get_tooltip_for_pin(playerdata, key), location.zone.name, name}} if settings.get_player_settings(player)[Pin.name_setting_expanded_tooltip].value == true then table.insert(tooltip, {"space-exploration.remote-view-pin-button-tooltip-help-text"}) if add_or_update_pin_position_valid then table.insert(tooltip, {"space-exploration.remote-view-pin-button-tooltip-help-text-update-position"}) end end local pin_button = pin_row_table.add { type = "sprite-button", sprite = sprite_path, tags = { action = Pin.tag_pin_button, item_name = key }, style = "se_sprite-button_inset", tooltip = tooltip } end end end --[[ Updates the pins window in response to changes to the data. Rebuilds the entire window UI. ]] function Pin.window_update(player) local playerdata = Pin.get_make_pin_playerdata(player) local force_name = player.force.name -- clear the data-driven part of the GUI local root = player.gui.screen[Pin.name_all_root] if not root then return end local viewer_content_flow = root.viewer_content_flow viewer_content_flow.clear() local zone_to_count = {} local spaceships -- count how many pins are in each zone for key, value in pairs(playerdata.saved_pins) do if key ~= "id" then local location = Location.from_reference(value.location_reference) if location then local priority = Zone.get_priority(location.zone, force_name) local zone_name = location.zone.name -- track spaceship pins separately from normal pins if location.type == "spaceship" then if spaceships then spaceships.pins[key] = value if priority > spaceships.priority then spaceships.priority = priority end else local pins = {} pins[key] = value spaceships = {pins=pins,priority=priority} end -- normal pins here else if zone_to_count[zone_name] then zone_to_count[zone_name].count = zone_to_count[zone_name].count + 1 zone_to_count[zone_name].pins[key] = value if priority > zone_to_count[zone_name].priority then zone_to_count[zone_name].priority = priority end else local pins = {} pins[key] = value zone_to_count[zone_name] = {count=1,pins=pins,priority=priority} end end end end end local roots_to_count = {} -- for any zone that has < 2 pins, reassign its pins to either its star's zone (if possible) for key, value in pairs(zone_to_count) do local look_for_parent = value.count < 2 if value.pins then local zone = Zone.from_name(key) local priority = value.priority if look_for_parent and zone then zone = Zone.find_parent_star(zone) or zone end for key2, value2 in pairs(value.pins) do local zone_name if zone then zone_name = zone.name else zone_name = key end if roots_to_count[zone_name] then roots_to_count[zone_name].count = roots_to_count[zone_name].count + 1 roots_to_count[zone_name].pins[key2] = value2 if priority > roots_to_count[zone_name].priority then roots_to_count[zone_name].priority = priority end else local pins = {} pins[key2] = value2 roots_to_count[zone_name] = {count=1,pins=pins,priority=priority} end end end end local finals_to_count = {} -- for any zone that *still* has < 2 pins, reassign its pins to the misc zone for key, value in pairs(roots_to_count) do local assign_to_misc = value.count < 2 if value.pins then local zone_name = key local priority = value.priority if assign_to_misc then zone_name = {"space-exploration.remote-view-pin-viewer-misc-row"} end for key2, value2 in pairs(value.pins) do if finals_to_count[zone_name] then finals_to_count[zone_name].count = finals_to_count[zone_name].count + 1 finals_to_count[zone_name].pins[key2] = value2 if priority > finals_to_count[zone_name].priority then finals_to_count[zone_name].priority = priority end else local pins = {} pins[key2] = value2 finals_to_count[zone_name] = {count=1,pins=pins,priority=priority} end end end end -- assign all spaceship pins to a special row if spaceships then finals_to_count[{"space-exploration.remote-view-pin-viewer-spaceship-row"}] = {pins=spaceships.pins,priority=spaceships.priority} end -- sort the rows by priority local rows_array = {} for key, value in pairs(finals_to_count) do table.insert(rows_array, {key=key,value=value}) end table.sort(rows_array, function (a, b) return a.value.priority > b.value.priority end) for index, element in ipairs(rows_array) do Pin.make_window_row(player, playerdata, viewer_content_flow, element.value.pins, element.key) end end --[[ Toggles if the pins window is open/closed. ]] function Pin.window_toggle(player) if player.gui.screen[Pin.name_all_root] then Pin.window_close(player) else Pin.window_open(player) end end --[[ Get/Make PlayerData relevant to Pin. Ensures that the returned PlayerData has two tables so accessing them can be done without nil checks: saved_pins stores a mapping of pin-id to the details about that quick pin saved_hotkeys stores a mapping of hotkey-id to the pin-id that hotkey links to ]]-- function Pin.get_make_pin_playerdata(player) local playerdata = get_make_playerdata(player) playerdata.saved_pins = playerdata.saved_pins or { id = 0 } playerdata.saved_hotkeys = playerdata.saved_hotkeys or {} return playerdata end return Pin