local util = require("script/script_util") local names = require("shared") local teleporter_name = names.entities.teleporter local teleporter_sticker = names.entities.teleporter_sticker local data = { networks = {}, rename_frames = {}, button_actions = {}, teleporter_map = {}, teleporter_frames = {}, player_linked_teleporter = {}, to_be_removed = {}, tag_map = {}, search_boxes = {}, recent = {} } local preview_size = 256 local debug_print = false local print = function(string) if not debug_print then return end game.print(string) log(string) end local create_flash = function(surface, position) surface.create_entity{name = "teleporter-explosion", position = position} for k = 1, 3 do surface.create_entity{name = "teleporter-explosion-no-sound", position = position} end end local clear_gui = function(frame) if not (frame and frame.valid) then return end util.deregister_gui(frame, data.button_actions) frame.clear() end local close_gui = function(frame) if not (frame and frame.valid) then return end util.deregister_gui(frame, data.button_actions) frame.destroy() end local get_rename_frame = function(player) local frame = data.rename_frames[player.index] if frame and frame.valid then return frame end data.rename_frames[player.index] = nil end local get_teleporter_frame = function(player) local frame = data.teleporter_frames[player.index] if frame and frame.valid then return frame end data.teleporter_frames[player.index] = nil end local make_rename_frame = function(player, caption) local teleporter_frame = get_teleporter_frame(player) if teleporter_frame then teleporter_frame.ignored_by_interaction = true end player.opened = nil local force = player.force local teleporters = data.networks[force.name] local param = teleporters[caption] local text = param.flying_text local gui = player.gui.screen local frame = gui.add{type = "frame", caption = {"gui-train-rename.title", caption}, direction = "horizontal"} frame.auto_center = true player.opened = frame data.rename_frames[player.index] = frame local textfield = frame.add{type = "textfield", text = caption} textfield.style.horizontally_stretchable = true textfield.focus() textfield.select_all() util.register_gui(data.button_actions, textfield, {type = "confirm_rename_textfield", textfield = textfield, flying_text = text, tag = param.tag}) local confirm = frame.add{type = "sprite-button", sprite = "utility/enter", style = "tool_button", tooltip = {"gui-train-rename.perform-change"}} util.register_gui(data.button_actions, confirm, {type = "confirm_rename_button", textfield = textfield, flying_text = text, tag = param.tag}) end local get_force_color = function(force) local player = force.connected_players[1] if player and player.valid then return player.chat_color end return {r = 1, b = 1, g = 1} end local add_recent = function(player, teleporter) local recent = data.recent[player.name] if not recent then recent = {} data.recent[player.name] = recent end recent[teleporter.unit_number] = game.tick if table_size(recent) >= 9 then local min = math.huge local index for k, tick in pairs (recent) do if tick < min then min = tick index = k end end if index then recent[index] = nil end end end local unlink_teleporter = function(player) if player.character then player.character.active = true end close_gui(get_teleporter_frame(player)) local source = data.player_linked_teleporter[player.index] if source and source.valid then source.active = true add_recent(player, source) end data.player_linked_teleporter[player.index] = nil end local clear_teleporter_data = function(teleporter_data) local flying_text = teleporter_data.flying_text if flying_text and flying_text.valid then flying_text.destroy() end local map_tag = teleporter_data.tag if map_tag and map_tag.valid then data.tag_map[map_tag.tag_number] = nil map_tag.destroy() end end local get_sort_function = function() return function(t, a, b) return a < b end end local make_teleporter_gui = function(player, source) local location local teleporter_frame = get_teleporter_frame(player) if teleporter_frame then location = teleporter_frame.location data.teleporter_frames[player.index] = nil print("Frame already exists") close_gui(teleporter_frame) player.opened = nil end print("Making new frame") if not (source and source.valid and not data.to_be_removed[source.unit_number]) then unlink_teleporter(player) return end local force = source.force local network = data.networks[force.name] if not network then return end local gui = player.gui.screen local frame = gui.add{type = "frame", direction = "vertical", ignored_by_interaction = false} if location then frame.location = location else frame.auto_center = true end player.opened = frame data.teleporter_frames[player.index] = frame frame.ignored_by_interaction = false local title_flow = frame.add{type = "flow", direction = "horizontal"} title_flow.style.vertical_align = "center" local title = title_flow.add{type = "label", style = "heading_1_label"} title.drag_target = frame local rename_button = title_flow.add{type = "sprite-button", sprite = "utility/rename_icon_small_white", style = "frame_action_button", visible = source.force == player.force} local pusher = title_flow.add{type = "empty-widget", direction = "horizontal", style = "draggable_space_header"} pusher.style.horizontally_stretchable = true pusher.style.vertically_stretchable = true pusher.drag_target = frame local search_box = title_flow.add{type = "textfield", visible = false} local search_button = title_flow.add{type = "sprite-button", style = "frame_action_button", sprite = "utility/search_white", tooltip = {"gui.search-with-focus", {"search"}}} util.register_gui(data.button_actions, search_button, {type = "search_button", box = search_box}) data.search_boxes[player.index] = search_box local inner = frame.add{type = "frame", style = "inside_deep_frame"} local scroll = inner.add{type = "scroll-pane", direction = "vertical"} scroll.style.maximal_height = (player.display_resolution.height / player.display_scale) * 0.8 local column_count = ((player.display_resolution.width / player.display_scale) * 0.6) / preview_size local holding_table = scroll.add{type = "table", column_count = column_count} util.register_gui(data.button_actions, search_box, {type = "search_text_changed", parent = holding_table}) holding_table.style.horizontal_spacing = 2 holding_table.style.vertical_spacing = 2 local any = false --print(table_size(network)) local recent = data.recent[player.name] or {} local sorted = {} local i = 1 for name, teleporter in pairs (network) do if teleporter.teleporter.valid then sorted[i] = {name = name, teleporter = teleporter, unit_number = teleporter.teleporter.unit_number} i = i + 1 else clear_teleporter_data(teleporter) end end table.sort(sorted, function(a, b) if recent[a.unit_number] and recent[b.unit_number] then return recent[a.unit_number] > recent[b.unit_number] end if recent[a.unit_number] then return true end if recent[b.unit_number] then return false end return a.name:lower() < b.name:lower() end) local sorted_network = {} for k, sorted_data in pairs (sorted) do sorted_network[sorted_data.name] = sorted_data.teleporter end local chart = player.force.chart for name, teleporter in pairs(sorted_network) do local teleporter_entity = teleporter.teleporter if not (teleporter_entity.valid) then clear_teleporter_data(teleporter) elseif teleporter_entity == source then title.caption = name util.register_gui(data.button_actions, rename_button, {type = "rename_button", caption = name}) else local position = teleporter_entity.position local area = {{position.x - preview_size / 2, position.y - preview_size / 2}, {position.x + preview_size / 2, position.y + preview_size / 2}} chart(teleporter_entity.surface, area) local button = holding_table.add{type = "button", name = "_"..name} button.style.height = preview_size + 32 + 8 button.style.width = preview_size + 8 button.style.left_padding = 0 button.style.right_padding = 0 local inner_flow = button.add{type = "flow", direction = "vertical", ignored_by_interaction = true} inner_flow.style.vertically_stretchable = true inner_flow.style.horizontally_stretchable = true inner_flow.style.horizontal_align = "center" local map = inner_flow.add { type = "minimap", surface_index = teleporter_entity.surface.index, zoom = 1, force = teleporter_entity.force.name, position = position, } map.ignored_by_interaction = true map.style.height = preview_size map.style.width = preview_size map.style.horizontally_stretchable = true map.style.vertically_stretchable = true local caption = name if recent[teleporter_entity.unit_number] then caption = "[img=quantity-time] "..name end local label = inner_flow.add{type = "label", caption = caption} label.style.horizontally_stretchable = true label.style.font = "default-dialog-button" label.style.font_color = {} label.style.horizontally_stretchable = true label.style.maximal_width = preview_size util.register_gui(data.button_actions, button, {type = "teleport_button", param = teleporter}) any = true end end if not any then holding_table.add{type = "label", caption = {"no-teleporters"}} end end function spairs(t, order) -- collect the keys local keys = {} for k in pairs(t) do keys[#keys+1] = k end -- if order function given, sort by it by passing the table and keys a, b, -- otherwise just sort the keys if order then table.sort(keys, function(a,b) return order(t, a, b) end) else table.sort(keys) end -- return the iterator function local i = 0 return function() i = i + 1 if keys[i] then return keys[i], t[keys[i]] end end end local refresh_teleporter_frames = function() local players = game.players for player_index, source in pairs (data.player_linked_teleporter) do local player = players[player_index] local frame = get_teleporter_frame(player) if frame then print("Refreshing frame") make_teleporter_gui(player, source) end end end local check_player_linked_teleporter = function(player) print("Checking player linked teleporter") local source = data.player_linked_teleporter[player.index] if source and source.valid then print("Linked teleporter exists...") make_teleporter_gui(player, source) else print("Unlinnkgin") unlink_teleporter(player) end end local resync_teleporter = function(name, teleporter_data) local teleporter = teleporter_data.teleporter if not (teleporter and teleporter.valid) then return end local force = teleporter.force local surface = teleporter.surface local color = get_force_color(force) clear_teleporter_data(teleporter_data) local flying_text = teleporter.surface.create_entity { name = "teleporter-flying-text", text = name, position = {teleporter.position.x, teleporter.position.y - 2}, force = force, color = color } flying_text.active = false teleporter_data.flying_text = flying_text data.adding_tag = true local map_tag = force.add_chart_tag(surface, { icon = {type = "item", name = teleporter_name}, position = teleporter.position, text = name }) data.adding_tag = false if map_tag then teleporter_data.tag = map_tag data.tag_map[map_tag.tag_number] = teleporter_data end end local is_name_available = function(force, name) local network = data.networks[force.name] return not network[name] end local rename_teleporter = function(force, old_name, new_name) if old_name == new_name then refresh_teleporter_frames() return end local network = data.networks[force.name] local teleporter_data = network[old_name] network[new_name] = teleporter_data network[old_name] = nil resync_teleporter(new_name, teleporter_data) refresh_teleporter_frames() end local gui_actions = { rename_button = function(event, param) make_rename_frame(game.get_player(event.player_index), param.caption) end, cancel_rename = function(event, param) local player = game.get_player(event.player_index) close_gui(get_rename_frame(player)) print("On cancel rename linked check") check_player_linked_teleporter(player) end, confirm_rename_button = function(event, param) if event.name ~= defines.events.on_gui_click then return end local flying_text = param.flying_text if not (flying_text and flying_text.valid) then return end local player = game.players[event.player_index] if not (player and player.valid) then return end local old_name = flying_text.text local new_name = param.textfield.text if new_name ~= old_name and not is_name_available(player.force, new_name) then player.print({"name-already-taken"}) return end close_gui(get_rename_frame(player)) rename_teleporter(player.force, old_name, new_name) print("On rename linked check") --check_player_linked_teleporter(player) end, confirm_rename_textfield = function(event, param) if event.name ~= defines.events.on_gui_confirmed then return end local flying_text = param.flying_text if not (flying_text and flying_text.valid) then return end local player = game.players[event.player_index] if not (player and player.valid) then return end local old_name = flying_text.text local new_name = param.textfield.text if new_name ~= old_name and not is_name_available(player.force, new_name) then player.print({"name-already-taken"}) return end close_gui(get_rename_frame(player)) rename_teleporter(player.force, old_name, new_name) print("On rename linked check") --check_player_linked_teleporter(player) end, teleport_button = function(event, param) local teleport_param = param.param if not teleport_param then return end local destination = teleport_param.teleporter if not (destination and destination.valid) then return end destination.timeout = destination.prototype.timeout local destination_surface = destination.surface local destination_position = destination.position local player = game.players[event.player_index] if not (player and player.valid) then return end create_flash(destination_surface, destination_position) create_flash(player.surface, player.position) --This teleport doesn't check collisions. If someone complains, make it check 'can_place' and if false find a positions etc.... player.teleport(destination_position, destination_surface) unlink_teleporter(player) add_recent(player, destination) end, search_text_changed = function(event, param) local box = event.element local search = box.text local parent = param.parent for k, child in pairs (parent.children) do child.visible = child.name:lower():find(search:lower(), 1, true) end end, search_button = function(event, param) param.box.visible = not param.box.visible if param.box.visible then param.box.focus() end end } local get_network = function(force) local name = force.name local network = data.networks[name] if network then return network end data.networks[name] = {} return data.networks[name] end local on_built_entity = function(event) local entity = event.created_entity or event.entity or event.destination if not (entity and entity.valid) then return end if entity.name ~= teleporter_name then return end local surface = entity.surface local force = entity.force local name = "Teleporter ".. entity.unit_number local network = get_network(force) local teleporter_data = {teleporter = entity, flying_text = text, tag = tag} network[name] = teleporter_data data.teleporter_map[entity.unit_number] = teleporter_data resync_teleporter(name, teleporter_data) refresh_teleporter_frames() end local on_teleporter_removed = function(entity) if not (entity and entity.valid) then return end if entity.name ~= teleporter_name then return end local force = entity.force local teleporter_data = data.teleporter_map[entity.unit_number] if not teleporter_data then return end local caption = teleporter_data.flying_text.text local network = get_network(force) network[caption] = nil clear_teleporter_data(teleporter_data) data.teleporter_map[entity.unit_number] = nil data.to_be_removed[entity.unit_number] = true refresh_teleporter_frames() data.to_be_removed[entity.unit_number] = nil end local teleporter_triggered = function(entity, character) if not (entity and entity.valid and entity.name == teleporter_name) then return error("HEOK") end if character.type ~= "character" then return end local force = entity.force local surface = entity.surface local position = entity.position local param = data.teleporter_map[entity.unit_number] local player = character.player if not player then return end player.teleport(entity.position) entity.active = false entity.timeout = entity.prototype.timeout character.active = false data.player_linked_teleporter[player.index] = entity make_teleporter_gui(player, entity) end local on_entity_removed = function(event) local entity = event.entity if not (entity and entity.valid) then return end on_teleporter_removed(entity) end local on_entity_died = function(event) on_teleporter_removed(event.entity) end local on_player_mined_entity = function(event) on_teleporter_removed(event.entity) end local on_robot_mined_entity = function(event) on_teleporter_removed(event.entity) end local on_gui_action = function(event) local element = event.element if not (element and element.valid) then return end local player_data = data.button_actions[event.player_index] if not player_data then return end local action = player_data[element.index] if action then gui_actions[action.type](event, action) return true end end local on_gui_closed = function(event) --print("CLOSED "..event.tick) local element = event.element if not element then return end local player = game.get_player(event.player_index) local rename_frame = get_rename_frame(player) if rename_frame and rename_frame == element then close_gui(rename_frame) print("Closed rename frame, checking player linked") check_player_linked_teleporter(player) return end local teleporter_frame = get_teleporter_frame(player) if teleporter_frame and teleporter_frame == element and not teleporter_frame.ignored_by_interaction then close_gui(teleporter_frame) unlink_teleporter(player) print("Frame unlinked") return end end local on_player_removed = function(event) local player = game.get_player(event.player_index) close_gui(get_rename_frame(player)) unlink_teleporter(player) end local resync_all_teleporters = function() for force, network in pairs (data.networks) do for name, teleporter_data in pairs (network) do resync_teleporter(name, teleporter_data) end end end local on_chart_tag_modified = function(event) local force = event.force local tag = event.tag if not (force and force.valid and tag and tag.valid) then return end local teleporter_data = data.tag_map[tag.tag_number] if not teleporter_data then --Nothing to do with us... return end local player = event.player_index and game.get_player(event.player_index) local old_name = event.old_text local new_name = tag.text if tag.icon and tag.icon.name ~= teleporter_name then --They're trying to modify the icon! Straight to JAIL! if player and player.valid then player.print({"cant-change-icon"}) end tag.icon = {type = "item", name = teleporter_name} end if new_name == old_name then return end if new_name == "" or not is_name_available(force, new_name) then if player and player.valid then player.print({"name-already-taken"}) end tag.text = old_name return end rename_teleporter(force, old_name, new_name) end local on_chart_tag_removed = function(event) local force = event.force local tag = event.tag if not (force and force.valid and tag and tag.valid) then return end local teleporter_data = data.tag_map[tag.tag_number] if not teleporter_data then --Nothing to do with us... return end local name = tag.text resync_teleporter(name, teleporter_data) end local on_chart_tag_added = function(event) if data.adding_tag then return end local tag = event.tag if not (tag and tag.valid) then return end local icon = tag.icon if icon and icon.type == "item" and icon.name == teleporter_name then --Trying to add a fake teleporter tag! JAIL! local player = event.player_index and game.get_player(event.player_index) if player and player.valid then player.print({"cant-add-tag"}) end tag.destroy() return end end local toggle_search = function(player) local box = data.search_boxes[player.index] if not (box and box.valid) then return end box.visible = true box.focus() end local on_search_focused = function(event) local player = game.get_player(event.player_index) toggle_search(player) end local on_player_display_resolution_changed = function(event) local player = game.get_player(event.player_index) check_player_linked_teleporter(player) end local on_player_display_scale_changed = function(event) local player = game.get_player(event.player_index) check_player_linked_teleporter(player) end local on_trigger_created_entity = function(event) local entity = event.entity if not (entity and entity.valid) then return end if entity.name ~= teleporter_sticker then return end local source = event.source if not (source and source.valid) then return end local stuck_to = entity.sticked_to if not (stuck_to and stuck_to.valid) then return end teleporter_triggered(source, stuck_to) end local on_rocket_launched = function(event) local rocket = event.rocket if not (rocket and rocket.valid) then return end local count = rocket.get_item_count(teleporter_name) if count == 0 then return end local rand = math.random local rando = function(i) return rand() * (i or 5) end local rando_autoplace = function(i) return { frequency = rando(i), richness = rando(i), size = rando(i) } end local settings = rocket.surface.map_gen_settings for name, autoplace in pairs(settings.autoplace_controls) do settings.autoplace_controls[name] = rando_autoplace(rando(2)) end settings.cliff_settings = { cliff_elevation_0 = rando(20), cliff_elevation_interval = rando(80), name = "cliff", richness = 1 } settings.seed = math.floor(rando(2 ^ 32)) settings.terrain_segmentation = rando(3) settings.water = rando(3) settings.starting_area = rando(3) local new_surface = game.create_surface("teleporter_surface"..game.tick..settings.seed, settings) new_surface.create_entity{name = teleporter_name, position = {0, 0}, force = rocket.force, raise_built = true} end local teleporters = {} teleporters.events = { [defines.events.on_built_entity] = on_built_entity, [defines.events.on_robot_built_entity] = on_built_entity, [defines.events.script_raised_built] = on_built_entity, [defines.events.script_raised_revive] = on_built_entity, [defines.events.on_entity_cloned] = on_built_entity, [defines.events.on_entity_died] = on_entity_removed, [defines.events.on_player_mined_entity] = on_entity_removed, [defines.events.on_robot_mined_entity] = on_entity_removed, [defines.events.script_raised_destroy] = on_entity_removed, [defines.events.on_gui_click] = on_gui_action, [defines.events.on_gui_text_changed] = on_gui_action, [defines.events.on_gui_confirmed] = on_gui_action, [defines.events.on_gui_closed] = on_gui_closed, [require("shared").hotkeys.focus_search] = on_search_focused, [defines.events.on_player_display_resolution_changed] = on_player_display_resolution_changed, [defines.events.on_player_display_scale_changed] = on_player_display_scale_changed, [defines.events.on_player_died] = on_player_removed, [defines.events.on_player_left_game] = on_player_removed, [defines.events.on_player_changed_force] = on_player_removed, [defines.events.on_chart_tag_modified] = on_chart_tag_modified, [defines.events.on_chart_tag_removed] = on_chart_tag_removed, [defines.events.on_chart_tag_added] = on_chart_tag_added, [defines.events.on_trigger_created_entity] = on_trigger_created_entity, [defines.events.on_rocket_launched] = on_rocket_launched } teleporters.on_init = function() global.teleporters = global.teleporters or data end teleporters.on_load = function() data = global.teleporters end teleporters.on_configuration_changed = function() -- 0.1.2 migration... data.player_linked_teleporter = data.player_linked_teleporter or {} data.rename_frames = data.rename_frames or data.frames or {} data.to_be_removed = data.to_be_removed or {} --0.1.5... data.teleporter_map = data.teleporter_map or data.map or {} data.tag_map = data.tag_map or {} resync_all_teleporters() --0.1.7... data.search_boxes = data.search_boxes or {} data.recent = data.recent or {} end return teleporters