diff --git a/exp_legacy/module/config/_file_loader.lua b/exp_legacy/module/config/_file_loader.lua index 88fdec5efe..010f42f6a1 100644 --- a/exp_legacy/module/config/_file_loader.lua +++ b/exp_legacy/module/config/_file_loader.lua @@ -20,7 +20,6 @@ return { --"modules.data.toolbar", --- GUI - "modules.gui.warp-list", "modules.gui.vlayer", "modules.graftorio.require", -- graftorio diff --git a/exp_legacy/module/locale/en.cfg b/exp_legacy/module/locale/en.cfg index 708296e0cb..af6bca63f9 100644 --- a/exp_legacy/module/locale/en.cfg +++ b/exp_legacy/module/locale/en.cfg @@ -158,37 +158,6 @@ ban-player=Ban player afk-time=__1__% of total map time\nLast moved __2__ ago open-map=__1__ __2__\n__3__\nClick to open map -[warp-list] -main-caption=Warp List [img=info] -main-tooltip=Warp List -sub-tooltip=Warps can only be used every __1__ seconds and when within __2__ tiles\n__3__\n__4__\n__5__\n__6__\n__7__\n__8__ -sub-tooltip-current= - __1__ This is your current warp point; -sub-tooltip-connected= - __1__ You can travel to this warp point; -sub-tooltip-different= - __1__ This warp is on a different network; -sub-tooltip-cooldown= - __1__ You are currently on cooldown; -sub-tooltip-not_available= - __1__ You are not in range of a warp point; -sub-tooltip-bypass= - __1__ Your role allows you to travel here; -too-close=Cannot create warp; too close to existing warp point: __1__ -too-close-to-water=Cannot create warp; too close to water, please move __1__ tiles away from the water body or landfill it -too-close-to-entities=Cannot create warp; too close to other entities, please move __1__ tiles away from the entities or remove them -last-edit=Last edited by __1__ at __2__\nClick to view on map -add-tooltip=Add new warp -confirm-tooltip=Save changes -cancel-tooltip=Discard changes -edit-tooltip=Currently being edited by: __1__ -edit-tooltip-none=Currently being edited by: Nobody -remove-tooltip=Remove warp -timer-tooltip=Please wait __1__ seconds before you can warp -timer-tooltip-zero=After each warp you will need to wait __1__ seconds before you can warp again -goto-tooltip=Go to x __1__ y __2__ -goto-bypass=Go to x __1__ y __2__, bypass mode -goto-bypass-different-network=Go to x __1__ y __2__, bypass mode different network -goto-same-warp=You are already on this warp -goto-different-network=This warp is on a different network, use power poles to connect it -goto-cooldown=You are on cooldown, wait for your cooldown to recharge -goto-disabled=You are not on a warp point, walk to a warp point -goto-edit=Edit warp icon - [bonus] description=Get / Set the amount of bonus you receive. arg-amount=Amount to set your bonus to, 0 will disable bonus. diff --git a/exp_legacy/module/locale/zh-CN.cfg b/exp_legacy/module/locale/zh-CN.cfg index 3f4027b1a5..9e83eef325 100644 --- a/exp_legacy/module/locale/zh-CN.cfg +++ b/exp_legacy/module/locale/zh-CN.cfg @@ -158,37 +158,6 @@ ban-player=封禁用戶 afk-time=__1__% 地圖時間\n上次移動: __2__ open-map=__1__ __2__\n__3__\n按下打開地圖 -[warp-list] -main-caption=傳送陣清單 [img=info] -main-tooltip=傳送陣清單 -sub-tooltip=傳送陣在 __1__ 秒內只能使用一次。 亦需在 __2__ 格之內使用\n__3__\n__4__\n__5__\n__6__\n__7__\n__8__ -sub-tooltip-current= - __1__ 你目前的傳送陣; -sub-tooltip-connected= - __1__ 你可以到本傳送陣; -sub-tooltip-different= - __1__ 該傳送陣未連接; -sub-tooltip-cooldown= - __1__ 傳送冷卻中; -sub-tooltip-not_available= - __1__ 你不在傳送陣範圍; -sub-tooltip-bypass= - __1__ 你可以傳送到這裏; -too-close=無法新建傳送陣; 太接近 __1__ 了 -too-close-to-water=無法新建傳送陣; 太接近水源了, 請移動 __1__ 格又或者填平該地方 -too-close-to-entities=無法新建傳送陣; 太接近其他東西, 請移動 __1__ 格又或者拆除該東西 -last-edit=最後由 __1__ 在 __2__ 更改\n按這在地圖上看 -add-tooltip=新建傳送陣 -confirm-tooltip=儲存更改 -cancel-tooltip=放棄更改 -edit-tooltip=現被 __1__ 修改中 -edit-tooltip-none=現沒有被人修改 -remove-tooltip=拆除傳送陣 -timer-tooltip=請等 __1__ 秒才再次傳送 -timer-tooltip-zero=你要等 __1__ 秒才可以再次傳送 -goto-tooltip=到 X __1__ Y __2__ -goto-bypass=到 X __1__ Y __2__, 繞行模式 -goto-bypass-different-network=到 X __1__ Y __2__, 繞行模式 (不同網絡) -goto-same-warp=你已在該傳送陣 -goto-different-network=該傳送陣未連接, 你可使用電纜連接 -goto-cooldown=傳送冷卻中, 請等候冷卻 -goto-disabled=你不在傳送陣, 請移至任意傳送陣 -goto-edit=修改傳送陣圖案 - [bonus] description=取得 / 設定 Bonus 量。 arg-amount=Bonus 數量, 0 來停用。 diff --git a/exp_legacy/module/locale/zh-TW.cfg b/exp_legacy/module/locale/zh-TW.cfg index 3f4027b1a5..9e83eef325 100644 --- a/exp_legacy/module/locale/zh-TW.cfg +++ b/exp_legacy/module/locale/zh-TW.cfg @@ -158,37 +158,6 @@ ban-player=封禁用戶 afk-time=__1__% 地圖時間\n上次移動: __2__ open-map=__1__ __2__\n__3__\n按下打開地圖 -[warp-list] -main-caption=傳送陣清單 [img=info] -main-tooltip=傳送陣清單 -sub-tooltip=傳送陣在 __1__ 秒內只能使用一次。 亦需在 __2__ 格之內使用\n__3__\n__4__\n__5__\n__6__\n__7__\n__8__ -sub-tooltip-current= - __1__ 你目前的傳送陣; -sub-tooltip-connected= - __1__ 你可以到本傳送陣; -sub-tooltip-different= - __1__ 該傳送陣未連接; -sub-tooltip-cooldown= - __1__ 傳送冷卻中; -sub-tooltip-not_available= - __1__ 你不在傳送陣範圍; -sub-tooltip-bypass= - __1__ 你可以傳送到這裏; -too-close=無法新建傳送陣; 太接近 __1__ 了 -too-close-to-water=無法新建傳送陣; 太接近水源了, 請移動 __1__ 格又或者填平該地方 -too-close-to-entities=無法新建傳送陣; 太接近其他東西, 請移動 __1__ 格又或者拆除該東西 -last-edit=最後由 __1__ 在 __2__ 更改\n按這在地圖上看 -add-tooltip=新建傳送陣 -confirm-tooltip=儲存更改 -cancel-tooltip=放棄更改 -edit-tooltip=現被 __1__ 修改中 -edit-tooltip-none=現沒有被人修改 -remove-tooltip=拆除傳送陣 -timer-tooltip=請等 __1__ 秒才再次傳送 -timer-tooltip-zero=你要等 __1__ 秒才可以再次傳送 -goto-tooltip=到 X __1__ Y __2__ -goto-bypass=到 X __1__ Y __2__, 繞行模式 -goto-bypass-different-network=到 X __1__ Y __2__, 繞行模式 (不同網絡) -goto-same-warp=你已在該傳送陣 -goto-different-network=該傳送陣未連接, 你可使用電纜連接 -goto-cooldown=傳送冷卻中, 請等候冷卻 -goto-disabled=你不在傳送陣, 請移至任意傳送陣 -goto-edit=修改傳送陣圖案 - [bonus] description=取得 / 設定 Bonus 量。 arg-amount=Bonus 數量, 0 來停用。 diff --git a/exp_legacy/module/modules/control/warps.lua b/exp_legacy/module/modules/control/warps.lua deleted file mode 100644 index fdca5b6246..0000000000 --- a/exp_legacy/module/modules/control/warps.lua +++ /dev/null @@ -1,488 +0,0 @@ ---[[-- Control Module - Warps -- Stores warps for each force. -@control Warps -@alias Warps - -@usage-- Making a new spawn warp -local player = game.player -local force = player.force -local spawn_id = Warps.add_warp(force.name, player.physical_surface, player.physical_position, player.name, 'Spawn') - -Warps.set_spawn_warp(spawn_id, force) -Warps.make_warp_tag(spawn_id) - -@usage-- Making a new warp with a warp area -local player = game.player -local force = player.force -local warp_id = Warps.add_warp(force.name, player.physical_surface, player.physical_position, player.name) - -Warps.make_warp_area(warp_id) -Warps.make_warp_tag(warp_id) - -]] - -local Datastore = require("modules.exp_legacy.expcore.datastore") --- @dep expcore.datastore -local Storage = require("modules/exp_util/storage") -local config = require("modules.exp_legacy.config.gui.warps") --- @dep config.warps - ---- Stores all data for the warp system -local WrapData = Datastore.connect("WrapData") -WrapData:set_serializer(function(raw_key) return raw_key.warp_id end) - -local Warps = {} - ---- @class ExpScenario_Warps.force_warps: { spawn: string, [number]: string } - --- Storage lookup table for force name to task ids -local force_warps = { _uid = 1 } ---- @cast force_warps table -Storage.register(force_warps, function(tbl) - force_warps = tbl -end) - --- Create an array of entity names that will be added to the remove filter -local remove_warp_area_entity_names = {} -for _, entity in pairs(config.entities) do - table.insert(remove_warp_area_entity_names, entity[1]) -end - --- When a warp is updated change its chat tag and restore the warp order -WrapData:on_update(function(warp_id, warp, old_warp) - if warp then - warp.updates = warp.updates + 1 - -- Update the map chart tag if there is one - if warp.tag then - Warps.make_warp_tag(warp_id) - end - - -- Check that the name of the warp has been changed - if not old_warp or warp.name == old_warp.name then return end - - -- Get the names of all the warp points for this force - local force_name = warp.force_name - local warp_ids = force_warps[force_name] - local spawn_id = warp_ids.spawn - - local warp_names = {} --- @type table - for _, next_warp_id in pairs(warp_ids) do - local next_warp = WrapData:get(next_warp_id) - if next_warp_id ~= spawn_id then - warp_names[next_warp.name .. next_warp_id] = next_warp_id - end - end - - -- Sort the warp names in alphabetical order - local new_warp_ids = table.get_values(table.key_sort(warp_names)) - --- @cast new_warp_ids ExpScenario_Warps.force_warps - table.insert(new_warp_ids, 1, spawn_id) - new_warp_ids.spawn = spawn_id - force_warps[force_name] = new_warp_ids - end -end) - ---- Map Integration. --- functions used to create and alter warps with in the map --- @section mapIntegration - ---[[-- Add or update the chat tag for this warp -@tparam string warp_id the uid of the warp you want the chart tag for -@treturn boolean true if a new tag was made, false if it was updated - -@usage-- Adding a chart tag for a new warp -local tag_added = Warps.make_warp_tag(warp_id) - -]] -function Warps.make_warp_tag(warp_id) - local warp = WrapData:get(warp_id) - local name = warp.name - local icon = warp.icon - - -- Edit the existing tag if it is present - local tag = warp.tag - - if tag and tag.valid then - tag.text = "Warp: " .. name - tag.icon = icon - return false - end - - -- Make a new tag if one did not exist - local force = game.forces[warp.force_name] - local surface = warp.surface - local position = warp.position - - tag = force.add_chart_tag(surface, { - position = { position.x + 0.5, position.y + 0.5 }, - text = "Warp: " .. name, - icon = icon, - }) - - -- Add the tag to this warp, store.update not needed as we dont want it to trigger - warp.tag = tag - return true -end - ---[[-- Remove the chart tag for this warp -@tparam string warp_id the uid for the warp that you want to remove the chart tag from -@treturn boolean true if the tag was valid and was removed, false if the tag was invalid - -@usage-- Removing the chart tag from a warp -local removed = Warps.remove_warp_tag(warp_id) - -]] -function Warps.remove_warp_tag(warp_id) - local warp = WrapData:get(warp_id) - - -- Check there is a tag to remove - local tag = warp.tag - if not tag or not tag.valid then - warp.tag = nil - return false - end - - -- Remove the warp chart tag if it is valid - tag.destroy() - warp.tag = nil - - return true -end - ---[[-- Add a warp area for the warp, purely cosmetic -@tparam string warp_id the uid of the warp you want the area for - -@usage-- Adding a warp area for a warp -Warps.make_warp_area(warp_id) - -]] -function Warps.make_warp_area(warp_id) - local warp = WrapData:get(warp_id) - local surface = warp.surface - local position = warp.position - local posx = position.x - local posy = position.y - - -- Get the tile that is being replaced, store.update not needed as we don't want it to trigger - local old_tile = surface.get_tile(position).name - warp.old_tile = old_tile - - -- Add a tile pattern on top of the base - local tiles = {} - for _, tile in pairs(config.tiles) do - table.insert(tiles, { name = tile[1], position = { tile[2] + posx, tile[3] + posy } }) - end - - surface.set_tiles(tiles) - - -- Add entities to the warp structure - for _, entity in pairs(config.entities) do - entity = surface.create_entity{ - name = entity[1], - position = { entity[2] + posx, entity[3] + posy }, - force = "neutral", - } - entity.destructible = false - entity.health = 0 - entity.minable_flag = false - entity.rotatable = false - - -- Save reference of the last power pole - if entity.type == "electric-pole" then - warp.electric_pole = entity - end - end -end - ---[[-- Remove the warp area for a warp -@tparam string warp_id the uid of the warp that you want to remove the area for - -@usage-- Remove the warp area for a warp -Warps.remove_warp_area(warp_id) - -]] -function Warps.remove_warp_area(warp_id) - local warp = WrapData:get(warp_id) - local position = warp.position - local surface = warp.surface - local radius = config.standard_proximity_radius - - -- Check that a warp area was created previously - local old_tile = warp.old_tile - if not old_tile then return end - - -- Restore the original tiles before the creation of the warp - local tiles = {} - - for _, tile in pairs(config.tiles) do - table.insert(tiles, { name = old_tile, position = { tile[2] + position.x, tile[3] + position.y } }) - end - - surface.set_tiles(tiles) - - local area = { - { position.x - radius, position.y - radius }, - { position.x + radius, position.y + radius }, - } - - -- Remove warp structure entities - local entities = surface.find_entities_filtered{ force = "neutral", area = area, name = remove_warp_area_entity_names } - for _, entity in pairs(entities) do - -- Destroy them, this will leave corpses of the entities that it destroyed. - if entity and entity.valid and entity.destructible == false then - entity.destructible = true - entity.die(entity.force) - end - end - - -- Rechart map area, useful if warp is not covered by a radar - game.forces[warp.force_name].chart(surface, area) -end - ---[[-- Set a warp to be the spawn point for a force, force must own this warp -@tparam string warp_id the uid of the warp that you want to be the spawn for the force -@tparam LuaForce force the force that you want to set the spawn for - -@usage-- Set your forces spawn to a warp -Warps.set_spawn_warp(warp_id, game.player.force) - -]] -function Warps.set_spawn_warp(warp_id, force) - -- Check the force owns this warp - local warp = WrapData:get(warp_id) - if warp.force_name ~= force.name then - return - end - -- Set this warp as the spawn - local warp_ids = force_warps[warp.force_name] - if not warp_ids then - warp_ids = {} - force_warps[warp.force_name] = warp_ids - end - warp_ids.spawn = warp_id - -- Set the forces spawn to this warp - force.set_spawn_position(warp.position, warp.surface) -end - ---[[-- Teleport a player to a warp point -@tparam string warp_id the uid of the warp to send the player to -@tparam LuaPlayer player the player to teleport to the warp - -@usage-- Teleport yourself to a warp point -Warps.teleport_player(warp_id, game.player) - -]] -function Warps.teleport_player(warp_id, player) - local warp = WrapData:get(warp_id) - local surface = warp.surface - local position = { - x = warp.position.x + 0.5, - y = warp.position.y + 0.5, - } - - if player.vehicle then - -- Teleport the entity - local entity = player.vehicle - local goto_position = surface.find_non_colliding_position(entity.name, position, 32, 1) - -- Surface teleport can only be done for players and cars at the moment. (with surface as an peramitor it gives this error) - if entity.type == "car" then - entity.teleport(goto_position, surface) - elseif surface.index == entity.surface.index then - -- Try teleport the entity - if not entity.teleport(goto_position) then - player.driving = false - -- Need to calculate new goto_position because entities have different collision boxes - goto_position = surface.find_non_colliding_position("character", position, 32, 1) - player.teleport(goto_position, surface) - end - end - else - -- Teleport the player - local goto_position = surface.find_non_colliding_position("character", position, 32, 1) - if player.driving then player.driving = false end - player.teleport(goto_position, surface) - end -end - ---- Setters. --- functions used to created and alter warps --- @section setters - ---[[-- Add a new warp for a force, the warp must have a surface and a position -@tparam string force_name the name of the force to add the warp for -@tparam LuaSurface surface the surface that the warp will be on -@tparam Concepts.Position position the position that the warp will be on -@tparam[opt] string player_name the name of the player that is adding the warp -@tparam[opt] string warp_name the name of the warp that is being added, if omited default is used -@treturn string the uid of the warp which was created - -@usage-- Adding a new warp for your force at your position -local player = game.player -local warp_id = Warps.add_warp(player.force.name, player.physical_surface, player.physical_position, player.name) - -]] -function Warps.add_warp(force_name, surface, position, player_name, warp_name) - -- Get new warp id - local warp_id = tostring(force_warps._uid) - force_warps._uid = force_warps._uid + 1 - warp_name = warp_name or "New warp" - - -- Get the existing warps for this force - local warp_ids = force_warps[force_name] - if not warp_ids then - warp_ids = {} - force_warps[force_name] = warp_ids - end - - -- Insert the warp id into the force warps - table.insert(warp_ids, warp_id) - - -- Create the editing table - local editing = {} - if player_name then - editing[player_name] = true - end - - -- Add the new warp to the store - WrapData:set(warp_id, { - warp_id = warp_id, - force_name = force_name, - name = warp_name, - icon = { type = config.default_icon.type, name = config.default_icon.name }, - surface = surface, - position = { - x = math.floor(position.x), - y = math.floor(position.y), - }, - last_edit_name = player_name or "", - last_edit_time = game.tick, - currently_editing = editing, - updates = 0, - }) - - return warp_id -end - ---[[-- Removes a warp and any data linked to it -@tparam string warp_id the uid of the warp that you want to remove - -@usage-- Removing a warp -Warps.remove_warp(warp_id) - -]] -function Warps.remove_warp(warp_id) - local warp = WrapData:get(warp_id) - local force_name = warp.force_name - Warps.remove_warp_tag(warp_id) - Warps.remove_warp_area(warp_id) - WrapData:remove(warp_id) - table.remove_element(force_warps[force_name], warp_id) -end - ---[[-- Update the name and icon for a warp -@tparam string warp_id the uid of the warp that you want to update -@tparam[opt] string new_name the new name that you want the warp to have -@tparam[opt] string new_icon the new icon that you want the warp to have -@tparam[opt='server'] string player_name the name of the player that made the edit - -@usage-- Changing the name and icon for a warp -Warps.update_warp(warp_id, 'My Warp', 'iron-plate', game.player.name) - -]] -function Warps.update_warp(warp_id, new_name, new_icon, player_name) - WrapData:update(warp_id, function(_, warp) - -- If the icon is not valid then replace with the old icon - if new_icon and not new_icon.name or not new_icon.type then - new_icon = warp.icon - end - - warp.last_edit_name = player_name or "" - warp.last_edit_time = game.tick - warp.name = new_name or warp.name - warp.icon = new_icon or warp.icon - end) -end - ---[[-- Set the editing state for a player, can be used as a warning or to display a text field -@tparam string warp_id the uid of the warp that you want to effect -@tparam string player_name the name of the player you want to set the state for -@tparam boolean state the new state to set editing to - -@usage-- Setting your editing state to true -Warps.set_editing(warp_id, game.player.name, true) - -]] -function Warps.set_editing(warp_id, player_name, state) - WrapData:update(warp_id, function(_, warp) - warp.currently_editing[player_name] = state - end) -end - ---[[-- Adds an update handler for when a warp is added, removed, or updated -@tparam function handler the handler which is called when a warp is updated - -@usage-- Add a game print when a warp is updated -Warps.on_update(function(warp) - game.print(warp.force_name..' now has the warp: '..warp.name) -end) - -]] -function Warps.on_update(handler) - WrapData:on_update(handler) -end - ---- Getters. --- function used to get information about warps --- @section getters - ---[[-- Gets the warp information that is linked with this id -@tparam string warp_id the uid of the warp you want to get -@treturn table the warp information - -@usage-- Getting warp information outside of on_update -local warp = Warps.get_warp(warp_id) - -]] -function Warps.get_warp(warp_id) - return WrapData:get(warp_id) -end - ---[[-- Gets all the warp ids that a force has -@tparam string force_name the name of the force that you want the warp ids for -@treturn table an array of all the warp ids - -@usage-- Getting the warp ids for a force -local warp_ids = Warps.get_force_warp_ids(game.player.force.name) - -]] -function Warps.get_force_warp_ids(force_name) - return force_warps[force_name] or {} -end - ---[[-- Get the id of the spawn warp -@tparam string force_name the name of the force that you want to get the spawn warp for -@treturn ?string|nil the uid of the spawn warp for this force if there is one - -@usage-- Getting the spawn warp id -local spawn_id = Warps.get_spawn_warp_id(game.player.force.name) - -]] -function Warps.get_spawn_warp_id(force_name) - local warp_ids = force_warps[force_name] or {} - return warp_ids.spawn -end - ---[[-- Gets the editing state for a player -@tparam string warp_id the uid of the warp you want to check -@tparam string player_name the name of the player that you want to check -@treturn boolean weather the player is currently editing this warp - -@usage-- Check if a player is editing a warp or not -local editing = Warps.get_editing(warp_id, game.player.name) - -]] -function Warps.get_editing(warp_id, player_name) - local warp = WrapData:get(warp_id) - return warp.currently_editing[player_name] -end - --- Module return -return Warps diff --git a/exp_legacy/module/modules/gui/warp-list.lua b/exp_legacy/module/modules/gui/warp-list.lua deleted file mode 100644 index 5c456558e1..0000000000 --- a/exp_legacy/module/modules/gui/warp-list.lua +++ /dev/null @@ -1,909 +0,0 @@ ---[[-- Gui Module - Warp List - - Adds a warp list gui which allows players to add and remove warp points - @gui Warps-List - @alias warp_list -]] - -local ExpUtil = require("modules/exp_util") -local Gui = require("modules/exp_gui") -local Datastore = require("modules.exp_legacy.expcore.datastore") --- @dep expcore.datastore -local Storage = require("modules/exp_util/storage") -local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event -local Roles = require("modules/exp_roles") -local Colors = require("modules/exp_util/include/color") -local config = require("modules.exp_legacy.config.gui.warps") --- @dep config.gui.warps -local Warps = require("modules.exp_legacy.modules.control.warps") --- @dep modules.control.warps - -local format_time = ExpUtil.format_time_factory_locale{ format = "short", hours = true, minutes = true } - ---- Stores all data for the warp gui -local WrapGuiData = Datastore.connect("WrapGuiData") -WrapGuiData:set_serializer(Datastore.name_serializer) -local PlayerInRange = WrapGuiData:combine("PlayerInRange") -PlayerInRange:set_default(false) -local PlayerCooldown = WrapGuiData:combine("PlayerCooldown") -PlayerCooldown:set_default(0) - ---- Table that stores a boolean value of weather to keep the warp gui open -local keep_gui_open = {} -Storage.register(keep_gui_open, function(tbl) - keep_gui_open = tbl -end) - ---- Styles used for sprite buttons -local Styles = { - sprite22 = { height = 22, width = 22, padding = -2 }, - sprite32 = { height = 32, width = 32, left_margin = 1 }, -} ---- Status icon of a warp -local warp_status_icons = { - cooldown = "[img=utility/multiplayer_waiting_icon]", - not_available = "[img=utility/set_bar_slot]", - bypass = "[img=utility/side_menu_bonus_icon]", - current = "[img=utility/side_menu_map_icon]", - connected = "[img=utility/logistic_network_panel_white]", - different = "[img=utility/warning_white]", -} - ---- Returns if a player is allowed to edit the given warp ---- If a player is allowed to use the edit buttons -local function check_player_permissions(player, action, warp) - -- Check if the action is allow edit and then check bypass settings - if action == "allow_edit_warp" then - -- Check if the warp is the spawn then it cant be edited - local spawn_id = Warps.get_spawn_warp_id(player.force.name) - if spawn_id == warp.warp_id then - return false - end - - -- Check if the player being the last to edit will override existing permisisons - if config.user_can_edit_own_warps and warp.last_edit_name == player.name then - return true - end - end - - -- Check player has permission based on value in the config - local action_config = config[action] - if action_config == "all" then - return true - elseif action_config == "admin" then - return player.admin - elseif action_config == "exp_roles" then - return Roles.player_has_permission(player, config["exp_roles_" .. action]) - end - - -- Return false as all other conditions have not been met - return false -end - ---- Will add a new warp to the list, checks if the player is too close to an existing one --- @element add_new_warp -local add_new_warp = Gui.define("add_new_warp") - :draw{ - type = "sprite-button", - sprite = "utility/add", - tooltip = { "warp-list.add-tooltip" }, - style = "shortcut_bar_button", - name = Gui.from_name, - } - :style(Styles.sprite22) - :on_click(function(def, player, element) - if player.controller_type ~= defines.controllers.character then return end - -- Add the new warp - local force_name = player.force.name - local surface = player.physical_surface - local position = player.physical_position - - -- Check if the warp is too close to water - local water_tiles = surface.find_tiles_filtered{ collision_mask = "water_tile", radius = config.standard_proximity_radius + 1, position = position } - if #water_tiles > 0 then - player.print( - { "expcore-commands.command-fail", { "warp-list.too-close-to-water", config.standard_proximity_radius + 1 } }, - { color = Colors.orange_red } - ) - if game.player then game.player.play_sound{ path = "utility/wire_pickup" } end - for _, tile in pairs(water_tiles) do - rendering.draw_sprite{ - sprite = "utility/rail_path_not_possible", - x_scale = 0.5, - y_scale = 0.5, - target = tile.position, - surface = surface, - players = { player }, - time_to_live = 60, - } - end - - return - end - - -- Check if there are player entities in the way (has a bigger radius because the enities that can be placed by a player are larger) - local entities = surface.find_entities_filtered{ - radius = config.standard_proximity_radius + 2.5, - position = position, - collision_mask = { - "item", "object", "player", "water_tile", - }, - } - -- Remove 1 because that is the current player - if #entities > 1 then - player.print( - { "expcore-commands.command-fail", { "warp-list.too-close-to-entities", config.standard_proximity_radius + 2.5 } }, - { color = Colors.orange_red } - ) - if game.player then game.player.play_sound{ path = "utility/wire_pickup" } end - local character = player.character - for _, entity in pairs(entities) do - if entity ~= character then - rendering.draw_sprite{ - sprite = "utility/rail_path_not_possible", - x_scale = 0.5, - y_scale = 0.5, - target = entity, - surface = surface, - players = { player }, - time_to_live = 60, - } - end - end - - return - end - - -- Create the warp - local warp_id = Warps.add_warp(force_name, surface, position, player.name) - Warps.make_warp_tag(warp_id) - Warps.make_warp_area(warp_id) - end) - ---- Warp icon button, this will trigger a warp when the player is able to --- @element warp_icon_button -local warp_icon_button = Gui.define("warp_icon_button") - :draw(function(def, parent, warp) - local warp_position = warp.position - - -- The SpritePath type is not the same as the SignalID type - local sprite = warp.icon.type .. "/" .. warp.icon.name - if warp.icon.type == "virtual" then - sprite = "virtual-signal/" .. warp.icon.name - end - - -- Draw the element - return parent.add{ - type = "sprite-button", - sprite = sprite, - name = def.name, - tooltip = { "warp-list.goto-tooltip", warp_position.x, warp_position.y }, - style = "slot_button", - } - end) - :style(Styles.sprite32) - :on_click(function(def, player, element) - if element.type == "choose-elem-button" then return end - local warp_id = assert(element.parent).caption - Warps.teleport_player(warp_id, player) - - -- Reset the warp cooldown if the player does not have unlimited warps - if not check_player_permissions(player, "bypass_warp_cooldown") then - PlayerCooldown:set(player, config.update_smoothing * config.cooldown_duration) - end - - PlayerInRange:set(player, warp_id) - end) - ---- The button that is visible when the warp is in edit state --- @element warp_icon_editing -local warp_icon_editing = Gui.define("warp_icon_editing") - :draw(function(def, parent, warp) - return parent.add{ - name = def.name, - type = "choose-elem-button", - elem_type = "signal", - signal = { type = warp.icon.type, name = warp.icon.name }, - tooltip = { "warp-list.goto-edit" }, - } - end) - :style(Styles.sprite32) - ---- Warp label, visible if the player is not in edit state --- @element warp_label -local warp_label = Gui.define("warp_label") - :draw(function(def, parent, warp) - local last_edit_name = warp.last_edit_name - local last_edit_time = warp.last_edit_time - -- Draw the element - return parent.add{ - type = "label", - caption = warp.name, - tooltip = { "warp-list.last-edit", last_edit_name, format_time(last_edit_time) }, - name = def.name, - } - end) - :style{ - single_line = true, - left_padding = 2, - right_padding = 2, - horizontally_stretchable = true, - } - :on_click(function(def, player, element) - local warp_id = assert(element.parent).caption - local warp = Warps.get_warp(warp_id) - player.set_controller{ type = defines.controllers.remote, position = warp.position, surface = warp.surface } - end) - ---- Warp status, visible if the player is not in edit state ---- This will show if the warp is connected or not --- @element warp_status -local warp_status = Gui.define("warp_status") - :draw{ - type = "label", - caption = "[img=utility/electricity_icon_unplugged]", -- Temporary icon - name = Gui.from_name, - } - :style{ - -- When editing mode because textbox is larger the icon would move up. - top_padding = 1, - single_line = false, - } - ---- Warp textfield, visible if the player is in edit state --- @element warp_textfield -local warp_textfield = Gui.define("warp_textfield") - :draw(function(def, parent, warp) - -- Draw the element - return parent.add{ - type = "textfield", - text = warp.name, - clear_and_focus_on_right_click = true, - name = def.name, - } - end) - :style{ - -- Required fields to make it squashable and strechable. - minimal_width = 10, - maximal_width = 300, - horizontally_squashable = true, - horizontally_stretchable = true, - -- Other styling - height = 22, - padding = -2, - left_margin = 2, - right_margin = 2, - } - :on_confirmed(function(def, player, element) - local warp_id = assert(element.parent).caption - local warp_name = element.text - local warp_icon = assert(element.parent).parent["icon-" .. warp_id][warp_icon_editing.name].elem_value --[[@as SignalID]] - if warp_icon.type == nil then warp_icon.type = "item" end - Warps.set_editing(warp_id, player.name) - Warps.update_warp(warp_id, warp_name, warp_icon, player.name) - end) - ---- Confirms the edit to name or icon of the warp --- @element confirm_edit_button -local confirm_edit_button = Gui.define("confirm_edit_button") - :draw{ - type = "sprite-button", - sprite = "utility/confirm_slot", - tooltip = { "warp-list.confirm-tooltip" }, - style = "shortcut_bar_button_green", - name = Gui.from_name, - } - :style(Styles.sprite22) - :on_click(function(def, player, element) - local parent = assert(element.parent) - local grandparent = assert(parent.parent) - local warp_id = parent.caption --[[@as string]] - local name_flow = grandparent["name-" .. warp_id] - local icon_flow = grandparent["icon-" .. warp_id] - local warp_name = name_flow[warp_textfield.name].text - local warp_icon = icon_flow[warp_icon_editing.name].elem_value --[[@as SignalID]] - if warp_icon.type == nil then warp_icon.type = "item" end - Warps.set_editing(warp_id, player.name) - Warps.update_warp(warp_id, warp_name, warp_icon, player.name) - end) - ---- Cancels the editing changes of the selected warp name or icon --- @element cancel_edit_button -local cancel_edit_button = Gui.define("cancel_edit_button") - :draw{ - type = "sprite-button", - sprite = "utility/close_black", - tooltip = { "warp-list.cancel-tooltip" }, - style = "shortcut_bar_button_red", - name = Gui.from_name, - } - :style(Styles.sprite22) - :on_click(function(def, player, element) - local warp_id = assert(element.parent).caption - -- Check if this is the first edit, if so remove the warp. - local warp = Warps.get_warp(warp_id) - if warp.updates == 1 then - Warps.remove_warp(warp_id) - return - end - Warps.set_editing(warp_id, player.name) - end) - ---- Removes a warp from the list, including the physical area and map tag --- @element remove_warp_button -local remove_warp_button = Gui.define("remove_warp_button") - :draw{ - type = "sprite-button", - sprite = "utility/trash", - tooltip = { "warp-list.remove-tooltip" }, - style = "shortcut_bar_button_red", - name = Gui.from_name, - } - :style(Styles.sprite22) - :on_click(function(def, player, element) - local warp_id = assert(element.parent).caption - Warps.remove_warp(warp_id) - end) - ---- Opens edit mode for the warp --- @element edit_warp_button -local edit_warp_button = Gui.define("edit_warp_button") - :draw{ - type = "sprite-button", - sprite = "utility/rename_icon", - tooltip = { "warp-list.edit-tooltip-none" }, - style = "shortcut_bar_button", - name = Gui.from_name, - } - :style(Styles.sprite22) - :on_click(function(def, player, element) - local warp_id = assert(element.parent).caption - Warps.set_editing(warp_id, player.name, true) - end) - -local update_all_warp_elements ---- Set of three elements which make up each row of the warp table --- @element add_warp_elements -local add_warp_elements = Gui.define("add_warp_elements") - :draw(function(_, parent, warp) - -- Add icon flow, this will contain the warp button and warp icon edit button - local icon_flow = parent.add{ - name = "icon-" .. warp.warp_id, - type = "flow", - caption = warp.warp_id, - } - icon_flow.style.padding = 0 - - -- Add the button and the icon edit button - warp_icon_button(icon_flow, warp) - warp_icon_editing(icon_flow, warp) - - -- Add name flow, this will contain the warp label and textbox - local name_flow = parent.add{ - type = "flow", - name = "name-" .. warp.warp_id, - caption = warp.warp_id, - } - name_flow.style.padding = 0 - - -- Add the label and textfield of the warp - warp_status(name_flow) - warp_label(name_flow, warp) - warp_textfield(name_flow, warp) - - -- Add button flow, this will contain buttons to manage this specific warp - local button_flow = parent.add{ - type = "flow", - name = "button-" .. warp.warp_id, - caption = warp.warp_id, - } - button_flow.style.padding = 0 - - -- Add both edit state buttons - confirm_edit_button(button_flow) - cancel_edit_button(button_flow) - edit_warp_button(button_flow) - remove_warp_button(button_flow) - - return Gui.no_return() - end) - --- Removes the three elements that are added as part of the warp base -local function remove_warp_elements(parent, warp_id) - Gui.destroy_if_valid(parent["icon-" .. warp_id]) - Gui.destroy_if_valid(parent["name-" .. warp_id]) - Gui.destroy_if_valid(parent["button-" .. warp_id]) -end - ---- This timer controls when a player is able to warp, eg every 60 seconds --- @element warp_timer -local warp_timer = Gui.define("warp_timer") - :draw{ - type = "progressbar", - name = Gui.from_name, - tooltip = { "warp-list.timer-tooltip-zero", config.cooldown_duration }, - minimum_value = 0, - maximum_value = config.cooldown_duration * config.update_smoothing, - } - :style{ - horizontally_stretchable = true, - color = Colors.light_blue, - } - -local warp_list_container - --- Helper function to style and enable or disable a button element -local function update_warp_elements(element, warp, warp_player_is_on, on_cooldown, bypass_warp_proximity) - -- Check if button element is valid - if not element or not element.valid then return end - - local label_style = assert(element.parent).parent["name-" .. warp.warp_id][warp_label.name].style - local warp_status_element = assert(element.parent).parent["name-" .. warp.warp_id][warp_status.name] - - -- If player is not on a warp - if not warp_player_is_on then - -- If player is allowed to warp without being on a warp. If not then disable the warp location - if bypass_warp_proximity then - local position = warp.position - element.tooltip = { "warp-list.goto-bypass", position.x, position.y } - element.enabled = true - warp_status_element.tooltip = { "warp-list.goto-bypass", position.x, position.y } - warp_status_element.caption = warp_status_icons.bypass - label_style.font = "default-semibold" - else - element.tooltip = { "warp-list.goto-disabled" } - element.enabled = false - warp_status_element.tooltip = { "warp-list.goto-disabled" } - warp_status_element.caption = warp_status_icons.not_available - label_style.font = "default" - end - -- If player is on the warp that is being updated - elseif warp_player_is_on.warp_id == warp.warp_id then - element.tooltip = { "warp-list.goto-same-warp" } - element.enabled = false - warp_status_element.tooltip = { "warp-list.goto-same-warp" } - warp_status_element.caption = warp_status_icons.current - label_style.font = "default" - -- If player is on cooldown - elseif on_cooldown then - element.tooltip = { "warp-list.goto-cooldown" } - element.enabled = false - warp_status_element.tooltip = { "warp-list.goto-cooldown" } - warp_status_element.caption = warp_status_icons.cooldown - label_style.font = "default" - else - -- If the warp the player is standing on is the same as the warp that is being updated - local warp_electric_network_id = warp.electric_pole and warp.electric_pole.electric_network_id or -1 - local player_warp_electric_network_id = warp_player_is_on.electric_pole and warp_player_is_on.electric_pole.electric_network_id or -2 - if warp_electric_network_id == player_warp_electric_network_id then - local position = warp.position - element.tooltip = { "warp-list.goto-tooltip", position.x, position.y } - element.enabled = true - warp_status_element.tooltip = { "warp-list.goto-tooltip", position.x, position.y } - warp_status_element.caption = warp_status_icons.connected - label_style.font = "default-semibold" - -- If the warp is not on the same network but the player is allowed to warp without being on a warp - elseif bypass_warp_proximity then - local position = warp.position - element.tooltip = { "warp-list.goto-bypass-different-network", position.x, position.y } - element.enabled = true - warp_status_element.tooltip = { "warp-list.goto-bypass-different-network", position.x, position.y } - warp_status_element.caption = warp_status_icons.bypass - label_style.font = "default-semibold" - -- If the warp is on a different network than the one the player is standing on - else - element.tooltip = { "warp-list.goto-different-network" } - element.enabled = false - warp_status_element.tooltip = { "warp-list.goto-different-network" } - warp_status_element.caption = warp_status_icons.different - label_style.font = "default" - end - end -end - ---- Update the warp buttons for a player -function update_all_warp_elements(player, timer, warp_id) - -- Get the warp table - local container = Gui.get_left_element(warp_list_container, player) - local scroll_table = container.frame.scroll.table - - -- Check if the player is currently on cooldown - timer = timer or PlayerCooldown:get(player) - local on_cooldown = timer > 0 - -- Get the warp the player is on - warp_id = warp_id or PlayerInRange:get(player) - local warp_player_is_on = warp_id and Warps.get_warp(warp_id) or nil - -- Check player permission - local bypass_warp_proximity = check_player_permissions(player, "bypass_warp_proximity") - - -- Change the enabled state of the warp buttons - local warp_ids = Warps.get_force_warp_ids(player.force.name) - for _, next_warp_id in pairs(warp_ids) do - local element = scroll_table["icon-" .. next_warp_id][warp_icon_button.name] - local next_warp = Warps.get_warp(next_warp_id) - update_warp_elements(element, next_warp, warp_player_is_on, on_cooldown, bypass_warp_proximity) - end -end - ---- Updates a warp for a player -local function update_warp(player, warp_table, warp_id) - local warp = Warps.get_warp(warp_id) - - -- If the warp no longer exists then remove the warp elements from the warp table - if not warp then - remove_warp_elements(warp_table, warp_id) - return - end - - -- Create the warp elements if they do not already exist - if not warp_table["icon-" .. warp_id] then - add_warp_elements(warp_table, warp) - end - local icon_flow = warp_table["icon-" .. warp_id] - local name_flow = warp_table["name-" .. warp_id] - local button_flow = warp_table["button-" .. warp_id] - - -- Create local references to the elements for this warp - local warp_icon_element = icon_flow[warp_icon_button.name] - local warp_icon_edit_element = icon_flow[warp_icon_editing.name] - - local label_element = name_flow[warp_label.name] - local textfield_element = name_flow[warp_textfield.name] - - local cancel_edit_element = button_flow[cancel_edit_button.name] - local confirm_edit_element = button_flow[confirm_edit_button.name] - - local edit_warp_element = button_flow[edit_warp_button.name] - local remove_warp_element = button_flow[remove_warp_button.name] - - -- Hide the edit button if the player is not allowed to edit the warp - local player_allowed_edit = check_player_permissions(player, "allow_edit_warp", warp) - local players_editing = table.get_keys(warp.currently_editing) - edit_warp_element.visible = player_allowed_edit - - -- Set the tooltip of the edit button - if #players_editing > 0 then - edit_warp_element.hovered_sprite = "utility/warning_icon" - edit_warp_element.tooltip = { "warp-list.edit-tooltip", table.concat(players_editing, ", ") } - else - edit_warp_element.hovered_sprite = edit_warp_element.sprite - edit_warp_element.tooltip = { "warp-list.edit-tooltip-none" } - end - - -- Set the visibility of the warp elements based on whether the user is editing or not - local player_is_editing = warp.currently_editing[player.name] - if player_is_editing then - -- Set the icon elements visibility - warp_icon_element.visible = false - warp_icon_edit_element.visible = true - -- Set the name elements visibility - label_element.visible = false - textfield_element.visible = true - textfield_element.focus() - warp_table.parent.scroll_to_element(textfield_element, "top-third") - -- Set the edit buttons - cancel_edit_element.visible = true - confirm_edit_element.visible = true - -- Set the warp buttons - edit_warp_element.visible = false - remove_warp_element.visible = false - else - -- Set the icon elements visibility - warp_icon_element.visible = true - -- The SpritePath type is not the same as the SignalID type - local sprite = warp.icon.type .. "/" .. warp.icon.name - if warp.icon.type == "virtual" then - sprite = "virtual-signal/" .. warp.icon.name - end - warp_icon_element.sprite = sprite - -- Set icon edit to the warps icon - warp_icon_edit_element.elem_value = warp.icon - warp_icon_edit_element.visible = false - -- Set the name elements visibility - label_element.visible = true - label_element.caption = warp.name - textfield_element.visible = false - textfield_element.text = warp.name - -- Set the edit buttons - cancel_edit_element.visible = false - confirm_edit_element.visible = false - -- Set the warp buttons - edit_warp_element.visible = true and player_allowed_edit - remove_warp_element.visible = true and player_allowed_edit - end - - local timer = PlayerCooldown:get(player) - local current_warp_id = PlayerInRange:get(player) - local to_warp = current_warp_id and Warps.get_warp(current_warp_id) or nil - local bypass_warp_proximity = check_player_permissions(player, "bypass_warp_proximity") - update_warp_elements(warp_icon_element, warp, to_warp, timer > 0, bypass_warp_proximity) -end - --- Update all the warps for a player -local function update_all_warps(player, warp_table) - local warp_ids = Warps.get_force_warp_ids(player.force.name) - warp_table.clear() - for _, warp_id in ipairs(warp_ids) do - update_warp(player, warp_table, warp_id) - end -end - --- Update all warps for all players on a force -local function update_all_warp_force(force) - local warp_ids = Warps.get_force_warp_ids(force.name) - for _, player in pairs(force.connected_players) do - local container = Gui.get_left_element(warp_list_container, player) - local warp_table = container.frame.scroll.table - - warp_table.clear() -- Needed to re-sort the warps - for _, warp_id in ipairs(warp_ids) do - update_warp(player, warp_table, warp_id) - end - end -end - ---- Main warp list container for the left flow --- @element warp_list_container -warp_list_container = Gui.define("warp_list_container") - :draw(function(def, parent) - local player = Gui.get_player(parent) - -- Check if user has permission to add warps - local allow_add_warp = check_player_permissions(player, "allow_add_warp") - - -- Draw the internal container - local container = Gui.elements.container(parent, allow_add_warp and 268 or 220) - - -- Draw the header - local header = Gui.elements.header(container, { - name = "header", - caption = { "warp-list.main-caption" }, - tooltip = { - "warp-list.sub-tooltip", - config.cooldown_duration, - config.standard_proximity_radius, - { "warp-list.sub-tooltip-current", warp_status_icons.current }, - { "warp-list.sub-tooltip-connected", warp_status_icons.connected }, - { "warp-list.sub-tooltip-different", warp_status_icons.different }, - { "warp-list.sub-tooltip-cooldown", warp_status_icons.cooldown }, - { "warp-list.sub-tooltip-not_available", warp_status_icons.not_available }, - { "warp-list.sub-tooltip-bypass", warp_status_icons.bypass }, - }, - }) - - -- Draw the new warp button - local add_new_warp_element = add_new_warp(header) - add_new_warp_element.visible = allow_add_warp - - -- Draw the scroll table for the warps - local scroll_table = Gui.elements.scroll_table(container, 250, 3, "scroll") - -- Set the scroll panel to always show the scrollbar (not doing this will result in a changing gui size) - assert(scroll_table.parent).vertical_scroll_policy = "always" - - -- Change the style of the scroll table - local scroll_table_style = scroll_table.style - scroll_table_style.top_cell_padding = 3 - scroll_table_style.bottom_cell_padding = 3 - - -- Draw the warp cooldown progress bar - local warp_timer_element = warp_timer(container) - - -- Change the progress of the warp timer - local timer = PlayerCooldown:get(player) - if timer > 0 then - warp_timer_element.tooltip = { "warp-list.timer-tooltip", math.floor(timer / config.update_smoothing) } - warp_timer_element.value = 1 - (timer / config.update_smoothing / config.cooldown_duration) - else - warp_timer_element.tooltip = { "warp-list.timer-tooltip-zero", config.cooldown_duration } - warp_timer_element.value = 1 - end - - -- Add any existing warps - update_all_warps(player, scroll_table) - - -- Return the external container - return container.parent - end) - ---- Add the element to the left flow with a toolbar button -Gui.add_left_element(warp_list_container, false) -Gui.toolbar.create_button{ - name = "warp_list_toggle", - left_element = warp_list_container, - sprite = config.default_icon.type .. "/" .. config.default_icon.name, - tooltip = { "warp-list.main-tooltip" }, - visible = function(player, element) - return Roles.player_has_permission(player, "exp_scenario.gui.warp_list") - end -}:on_click(function(def, player, element) - -- Set gui keep open state for player that clicked the button: true if visible, false if invisible - keep_gui_open[player.name] = Gui.toolbar.get_button_toggled_state(def, player) -end) - ---- When the name of a warp is updated this is triggered -Warps.on_update(function(_, warp, old_warp) - -- Get the force to update, warp is nil when removed - if warp then - update_all_warp_force(game.forces[warp.force_name]) - else - update_all_warp_force(game.forces[old_warp.force_name]) - end -end) - ---- When the player leaves or enters range of a warp this is triggered -PlayerInRange:on_update(function(player_name, warp_id) - local player = game.players[player_name] - - -- Change if the frame is visible based on if the player is in range - if not keep_gui_open[player.name] then - Gui.toolbar.set_left_element_visible_state(warp_list_container, player, warp_id ~= nil) - end - - update_all_warp_elements(player, nil, warp_id) -end) - ---- Update the warp cooldown progress bars to match the current cooldown -PlayerCooldown:on_update(function(player_name, player_cooldown) - -- Get the progress bar element - local player = game.players[player_name] - local container = Gui.get_left_element(warp_list_container, player) - local warp_timer_element = container.frame[warp_timer.name] - - -- Set the progress - if player_cooldown and player_cooldown > 0 then - warp_timer_element.tooltip = { "warp-list.timer-tooltip", math.floor(player_cooldown / config.update_smoothing) } - warp_timer_element.value = 1 - (player_cooldown / config.update_smoothing / config.cooldown_duration) - else - warp_timer_element.tooltip = { "warp-list.timer-tooltip-zero", config.cooldown_duration } - warp_timer_element.value = 1 - end - - -- Trigger update of buttons if cooldown is now 0 - if player_cooldown == 0 then - update_all_warp_elements(player, player_cooldown, nil) - end -end) - ---- Handles updating the timer and checking distance from a warp -local r2 = config.standard_proximity_radius ^ 2 -local rs2 = config.spawn_proximity_radius ^ 2 -local mr2 = config.minimum_distance ^ 2 -Event.on_nth_tick(math.floor(60 / config.update_smoothing), function() - PlayerCooldown:update_all(function(_, player_cooldown) - if player_cooldown > 0 then return player_cooldown - 1 end - end) - - local force_warps = {} - local warps = {} - for _, player in pairs(game.connected_players) do - local was_in_range = PlayerInRange:get(player) - - -- Get the ids of all the warps on the players force - local force_name = player.force.name - local warp_ids = force_warps[force_name] - if not warp_ids then - warp_ids = Warps.get_force_warp_ids(force_name) - force_warps[force_name] = warp_ids - end - - -- Check if the force has any warps - local closest_warp = nil --- @type { warp_id: string, name: string }? - local closest_distance = nil - if #warp_ids > 0 then - local surface = player.surface - local pos = player.physical_position - local px, py = pos.x, pos.y - - -- Loop over each warp - for _, warp_id in ipairs(warp_ids) do - -- Check if warp id is cached - local warp = warps[warp_id] - if not warp then - warp = Warps.get_warp(warp_id) - warps[warp_id] = warp - end - - -- Check if the player is within range - local warp_pos = warp.position - if warp.surface == surface then - local dx, dy = px - warp_pos.x, py - warp_pos.y - local dist = (dx * dx) + (dy * dy) - if closest_distance == nil or dist < closest_distance then - closest_warp = warp - closest_distance = dist - if dist < r2 then break end - end - end - end - - -- Check the dist to the closest warp - local in_range = closest_warp ~= nil and (closest_warp.warp_id == warp_ids.spawn and closest_distance < rs2 or closest_distance < r2) - if was_in_range and not in_range then - PlayerInRange:set(player, nil) - elseif not was_in_range and in_range then - --- @cast closest_warp -nil - PlayerInRange:set(player, closest_warp.warp_id) - end - - -- Change the enabled state of the add warp button - local container = Gui.get_left_element(warp_list_container, player) - local add_warp_element = container.frame.header[add_new_warp.name] - local old_closest_warp_name = add_warp_element.tooltip[2] or closest_warp and closest_warp.name - local was_able_to_make_warp = add_warp_element.enabled - local can_make_warp = closest_distance == nil or closest_distance > mr2 - if can_make_warp and not was_able_to_make_warp then - add_warp_element.enabled = true - add_warp_element.tooltip = { "warp-list.add-tooltip" } - elseif not can_make_warp and was_able_to_make_warp or closest_warp and (old_closest_warp_name ~= closest_warp.name) then - --- @cast closest_warp -nil - add_warp_element.enabled = false - add_warp_element.tooltip = { "warp-list.too-close", closest_warp.name } - end - end - end -end) - ---- When a player is created make sure that there is a spawn warp created -Event.add(defines.events.on_player_created, function(event) - -- If the force has no spawn then make a spawn warp - local player = game.players[event.player_index] - local force = player.force --[[@as LuaForce]] - local spawn_id = Warps.get_spawn_warp_id(force.name) - if not spawn_id then - local spawn_position = force.get_spawn_position(player.surface) - spawn_id = Warps.add_warp(force.name, player.surface, spawn_position, nil, "Spawn") - Warps.set_spawn_warp(spawn_id, force) - Warps.make_warp_tag(spawn_id) - - local entities = player.surface.find_entities_filtered{ type = "electric-pole", position = spawn_position, radius = 20, limit = 1 } - if entities and entities[1] then - local warp = Warps.get_warp(spawn_id) - warp.electric_pole = entities[1] - end - end -end) - ---- Update the warps when the player joins -Event.add(defines.events.on_player_joined_game, function(event) - local player = game.players[event.player_index] - local container = Gui.get_left_element(warp_list_container, player) - local scroll_table = container.frame.scroll.table - update_all_warps(player, scroll_table) -end) - ---- Makes sure the right buttons are present when roles change -local function role_update_event(event) - local player = game.players[event.player_index] - local frame = Gui.get_left_element(warp_list_container, player).frame - - -- Check if user has permission to add warps - local allow_add_warp = check_player_permissions(player, "allow_add_warp") - -- Update container size depending on whether the player is allowed to add warps - assert(frame.parent).style.width = allow_add_warp and 268 or 220 - - -- Update the warps, in case the user can now edit them - local scroll_table = frame.scroll.table - update_all_warps(player, scroll_table) - - -- Update the new warp button in case the user can now add them - local add_new_warp_element = frame.header[add_new_warp.name] - add_new_warp_element.visible = allow_add_warp -end - -Event.add(Roles.events.on_player_roles_changed, role_update_event) - ---- When a chart tag is removed or edited make sure it is not one that belongs to a warp -local function maintain_tag(event) - if not event.player_index then return end - local tag = event.tag - local force_name = event.force.name - local warp_ids = Warps.get_force_warp_ids(force_name) - for _, warp_id in pairs(warp_ids) do - local warp = Warps.get_warp(warp_id) - local warp_tag = warp.tag - if not warp_tag or not warp_tag.valid or warp_tag == tag then - if event.name == defines.events.on_chart_tag_removed then - warp.tag = nil - end - Warps.make_warp_tag(warp_id) - end - end -end - -Event.add(defines.events.on_chart_tag_modified, maintain_tag) -Event.add(defines.events.on_chart_tag_removed, maintain_tag) diff --git a/exp_scenario/module/control.lua b/exp_scenario/module/control.lua index 1cd47d66ef..62c4e94349 100644 --- a/exp_scenario/module/control.lua +++ b/exp_scenario/module/control.lua @@ -83,5 +83,6 @@ add(require("modules/exp_scenario/gui/rocket_info")) add(require("modules/exp_scenario/gui/science_production")) add(require("modules/exp_scenario/gui/surveillance")) add(require("modules/exp_scenario/gui/task_list")) +add(require("modules/exp_scenario/gui/warp_list")) add(require("modules/exp_scenario/gui/debug/shim")) add(require("modules/exp_scenario/gui/debug/event_view")) diff --git a/exp_scenario/module/gui/warp_list.lua b/exp_scenario/module/gui/warp_list.lua new file mode 100644 index 0000000000..f76faed4f3 --- /dev/null +++ b/exp_scenario/module/gui/warp_list.lua @@ -0,0 +1,1218 @@ +--[[-- Gui - Warp List +Adds a list of warp points which players can travel between, add, and edit +]] + +local Gui = require("modules/exp_gui") +local Roles = require("modules/exp_roles") +local Colors = require("modules/exp_util/include/color") +local config = require("modules.exp_legacy.config.gui.warps") + +local ExpUtil = require("modules/exp_util") +local format_time = ExpUtil.format_time_factory_locale{ format = "short", hours = true, minutes = true } + +local floor = math.floor +local ceil = math.ceil + +--- @class ExpGui_WarpList.elements +local Elements = {} + +local Styles = { + sprite22 = { height = 22, width = 22, padding = -2 }, + sprite32 = { height = 32, width = 32, left_margin = 1 }, +} + +--- Rich text icons shown next to a warp name to describe why it can or can not be used +local status_icons = { + cooldown = "[img=utility/multiplayer_waiting_icon]", + not_available = "[img=utility/set_bar_slot]", + bypass = "[img=utility/side_menu_bonus_icon]", + current = "[img=utility/side_menu_map_icon]", + connected = "[img=utility/logistic_network_panel_white]", + different = "[img=utility/warning_white]", +} + +local update_interval = floor(60 / config.update_smoothing) +local cooldown_ticks = config.cooldown_duration * 60 +local proximity_radius_sq = config.standard_proximity_radius ^ 2 +local spawn_radius_sq = config.spawn_proximity_radius ^ 2 +local minimum_distance_sq = config.minimum_distance ^ 2 + +--- Names of the entities which make up a warp area, used to find them when the area is removed +local area_entity_names = {} --- @type string[] +for _, entity in ipairs(config.entities) do + area_entity_names[#area_entity_names + 1] = entity[1] +end + +--- @class ExpGui_WarpList.Warp +--- @field id number +--- @field force LuaForce +--- @field name string +--- @field icon SignalID +--- @field surface LuaSurface +--- @field position MapPosition.struct The tile the warp is centred on +--- @field last_user LuaPlayer? Nil for warps made by the scenario +--- @field last_edit_tick number +--- @field editing table Names of the players editing the warp keyed by their index +--- @field tag LuaCustomChartTag? +--- @field electric_pole LuaEntity? Pole within the warp area, warps on the same network can be travelled between +--- @field old_tile string? The tile the warp area replaced, nil when no area was made +--- @field new boolean? True until the first edit is confirmed, discarding that edit removes the warp + +--- @class ExpGui_WarpList.PlayerStatus +--- @field in_range ExpGui_WarpList.Warp? The warp the player is standing on +--- @field on_cooldown boolean +--- @field bypass_proximity boolean + +--- Check a permission from the config, which says who an action applies to +--- @param player LuaPlayer +--- @param action "bypass_warp_cooldown" | "bypass_warp_proximity" | "allow_add_warp" | "allow_edit_warp" +--- @return boolean +local function has_config_permission(player, action) + local setting = config[action] + if setting == "all" then + return true + elseif setting == "admin" then + return player.admin + elseif setting == "exp_roles" then + return Roles.player_has_permission(player, config["exp_roles_" .. action]) + end + + return false +end + +--- Check if a player can edit a warp, the spawn warp can never be edited +--- @param player LuaPlayer +--- @param warp ExpGui_WarpList.Warp +--- @return boolean +local function has_permission_edit_warp(player, warp) + if Elements.container.get_spawn_warp(warp.force) == warp then + return false + end + + if config.user_can_edit_own_warps and warp.last_user == player then + return true + end + + return has_config_permission(player, "allow_edit_warp") +end + +--- The sprite path for a warp icon, signal ids and sprite paths name virtual signals differently +--- @param icon SignalID +--- @return SpritePath +local function icon_sprite(icon) + if icon.type == "virtual" then + return "virtual-signal/" .. icon.name + end + return (icon.type or "item") .. "/" .. icon.name +end + +--- Draw a cross over something that stops a warp being added +--- @param surface LuaSurface +--- @param player LuaPlayer +--- @param target LuaEntity | MapPosition +local function draw_blocker(surface, player, target) + rendering.draw_sprite{ + sprite = "utility/rail_path_not_possible", + x_scale = 0.5, + y_scale = 0.5, + target = target, + surface = surface, + players = { player }, + time_to_live = 60, + } +end + +--- Button in the header which adds a warp at the player +--- @class ExpGui_WarpList.elements.add_warp_button: ExpElement +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.add_warp_button = Gui.define("warp_list/add_warp_button") + :track_all_elements() + :draw{ + type = "sprite-button", + sprite = "utility/add", + tooltip = { "exp-gui_warp-list.tooltip-add" }, + style = "shortcut_bar_button", + } + :style(Styles.sprite22) + :on_click(function(_, player) + if player.controller_type ~= defines.controllers.character then return end + local surface = player.physical_surface + local position = player.physical_position + + local water_tiles = surface.find_tiles_filtered{ + collision_mask = "water_tile", + radius = config.standard_proximity_radius + 1, + position = position, + } + if #water_tiles > 0 then + player.print({ "exp-gui_warp-list.add-warning-water", config.standard_proximity_radius + 1 }, Colors.orange_red) + player.play_sound{ path = "utility/wire_pickup" } + for _, tile in pairs(water_tiles) do + draw_blocker(surface, player, tile.position) + end + return + end + + -- A larger radius than the area because the entities a player can place are larger than tiles + local entities = surface.find_entities_filtered{ + radius = config.standard_proximity_radius + 2.5, + position = position, + collision_mask = { "item", "object", "player", "water_tile" }, + } + if #entities > 1 then -- The player's own character is always found + player.print({ "exp-gui_warp-list.add-warning-entities", config.standard_proximity_radius + 2.5 }, Colors.orange_red) + player.play_sound{ path = "utility/wire_pickup" } + local character = player.character + for _, entity in pairs(entities) do + if entity ~= character then + draw_blocker(surface, player, entity) + end + end + return + end + + local force = player.force --[[@as LuaForce]] + local warp = Elements.container.add_warp(force, surface, position, player) + Elements.container.set_editing(warp, player, true) + end) --[[@as any]] + +--- Refresh whether the add button is enabled based on the closest warp +--- @param player LuaPlayer +--- @param blocking ExpGui_WarpList.Warp? The warp too close to add another, nil when a warp can be added +function Elements.add_warp_button.refresh_player_blocking(player, blocking) + local tooltip = blocking and { "exp-gui_warp-list.tooltip-add-too-close", blocking.name } or { "exp-gui_warp-list.tooltip-add" } + for _, add_warp_button in Elements.add_warp_button:tracked_elements(player) do + add_warp_button.enabled = blocking == nil + add_warp_button.tooltip = tooltip + end +end + +--- @class ExpGui_WarpList.elements.warp_button.element_data +--- @field warp ExpGui_WarpList.Warp + +--- Button which travels to the warp, enabled when the player is allowed to use it +--- @class ExpGui_WarpList.elements.warp_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, warp: ExpGui_WarpList.Warp): LuaGuiElement +Elements.warp_button = Gui.define("warp_list/warp_button") + :draw(function(_, parent, warp) + --- @cast warp ExpGui_WarpList.Warp + return parent.add{ + type = "sprite-button", + sprite = icon_sprite(warp.icon), + style = "slot_button", + } + end) + :style(Styles.sprite32) + :element_data{ + warp = Gui.from_argument(1), + } + :on_click(function(def, player, warp_button) + --- @cast def ExpGui_WarpList.elements.warp_button + Elements.container.teleport_player(player, def.data[warp_button].warp) + end) --[[@as any]] + +--- @class ExpGui_WarpList.elements.icon_picker.element_data +--- @field warp ExpGui_WarpList.Warp + +--- Signal picker shown in place of the warp button while editing +--- @class ExpGui_WarpList.elements.icon_picker: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, warp: ExpGui_WarpList.Warp): LuaGuiElement +Elements.icon_picker = Gui.define("warp_list/icon_picker") + :draw(function(_, parent, warp) + --- @cast warp ExpGui_WarpList.Warp + return parent.add{ + type = "choose-elem-button", + elem_type = "signal", + signal = warp.icon, + tooltip = { "exp-gui_warp-list.tooltip-icon" }, + } + end) + :style(Styles.sprite32) + :element_data{ + warp = Gui.from_argument(1), + } --[[@as any]] + +--- Get the icon chosen in a picker, the game omits the type for items +--- @param icon_picker LuaGuiElement +--- @return SignalID +function Elements.icon_picker.get_icon(icon_picker) + local icon = icon_picker.elem_value --[[@as SignalID?]] + if not icon then + return Elements.icon_picker.data[icon_picker].warp.icon + end + icon.type = icon.type or "item" + return icon +end + +--- Icon describing whether the warp can be used +--- @class ExpGui_WarpList.elements.status_label: ExpElement +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.status_label = Gui.define("warp_list/status_label") + :draw{ + type = "label", + caption = status_icons.not_available, + } + :style{ + top_padding = 1, -- Keeps the icon in place when the taller textfield is shown + single_line = false, + } --[[@as any]] + +--- @class ExpGui_WarpList.elements.name_label.element_data +--- @field warp ExpGui_WarpList.Warp + +--- Warp name, clicking it opens the map at the warp +--- @class ExpGui_WarpList.elements.name_label: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, warp: ExpGui_WarpList.Warp): LuaGuiElement +Elements.name_label = Gui.define("warp_list/name_label") + :draw(function(_, parent, warp) + --- @cast warp ExpGui_WarpList.Warp + return parent.add{ + type = "label", + caption = warp.name, + } + end) + :style{ + single_line = true, + left_padding = 2, + right_padding = 2, + horizontally_stretchable = true, + } + :element_data{ + warp = Gui.from_argument(1), + } + :on_click(function(def, player, name_label) + --- @cast def ExpGui_WarpList.elements.name_label + local warp = def.data[name_label].warp + player.set_controller{ type = defines.controllers.remote, position = warp.position, surface = warp.surface } + end) --[[@as any]] + +--- Refresh the name and last edit of the label +--- @param name_label LuaGuiElement +--- @param warp ExpGui_WarpList.Warp +function Elements.name_label.refresh(name_label, warp) + local last_user = warp.last_user and warp.last_user.name or "" + name_label.caption = warp.name + name_label.tooltip = { "exp-gui_warp-list.tooltip-last-edit", last_user, format_time(warp.last_edit_tick) } +end + +--- @class ExpGui_WarpList.elements.name_field.element_data +--- @field warp ExpGui_WarpList.Warp +--- @field icon_picker LuaGuiElement + +--- Textfield shown in place of the name label while editing, confirming it saves the edit +--- @class ExpGui_WarpList.elements.name_field: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, warp: ExpGui_WarpList.Warp, icon_picker: LuaGuiElement): LuaGuiElement +Elements.name_field = Gui.define("warp_list/name_field") + :draw(function(_, parent, warp) + --- @cast warp ExpGui_WarpList.Warp + return parent.add{ + type = "textfield", + text = warp.name, + clear_and_focus_on_right_click = true, + } + end) + :style{ + minimal_width = 10, + maximal_width = 300, + horizontally_squashable = true, + horizontally_stretchable = true, + height = 22, + padding = -2, + left_margin = 2, + right_margin = 2, + } + :element_data{ + warp = Gui.from_argument(1), + icon_picker = Gui.from_argument(2), + } + :on_confirmed(function(def, player, name_field) + --- @cast def ExpGui_WarpList.elements.name_field + local elements = def.data[name_field] + local icon = Elements.icon_picker.get_icon(elements.icon_picker) + Elements.container.update_warp(elements.warp, name_field.text, icon, player) + end) --[[@as any]] + +--- @class ExpGui_WarpList.elements.confirm_edit_button.element_data +--- @field warp ExpGui_WarpList.Warp +--- @field name_field LuaGuiElement +--- @field icon_picker LuaGuiElement + +--- Button which saves the name and icon being edited +--- @class ExpGui_WarpList.elements.confirm_edit_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, warp: ExpGui_WarpList.Warp, name_field: LuaGuiElement, icon_picker: LuaGuiElement): LuaGuiElement +Elements.confirm_edit_button = Gui.define("warp_list/confirm_edit_button") + :draw{ + type = "sprite-button", + sprite = "utility/confirm_slot", + tooltip = { "exp-gui_warp-list.tooltip-confirm" }, + style = "shortcut_bar_button_green", + } + :style(Styles.sprite22) + :element_data{ + warp = Gui.from_argument(1), + name_field = Gui.from_argument(2), + icon_picker = Gui.from_argument(3), + } + :on_click(function(def, player, confirm_edit_button) + --- @cast def ExpGui_WarpList.elements.confirm_edit_button + local elements = def.data[confirm_edit_button] + local icon = Elements.icon_picker.get_icon(elements.icon_picker) + Elements.container.update_warp(elements.warp, elements.name_field.text, icon, player) + end) --[[@as any]] + +--- @class ExpGui_WarpList.elements.discard_edit_button.element_data +--- @field warp ExpGui_WarpList.Warp + +--- Button which discards an edit, a warp which was never confirmed is removed +--- @class ExpGui_WarpList.elements.discard_edit_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, warp: ExpGui_WarpList.Warp): LuaGuiElement +Elements.discard_edit_button = Gui.define("warp_list/discard_edit_button") + :draw{ + type = "sprite-button", + sprite = "utility/close_black", + tooltip = { "exp-gui_warp-list.tooltip-discard" }, + style = "shortcut_bar_button_red", + } + :style(Styles.sprite22) + :element_data{ + warp = Gui.from_argument(1), + } + :on_click(function(def, player, discard_edit_button) + --- @cast def ExpGui_WarpList.elements.discard_edit_button + local warp = def.data[discard_edit_button].warp + if warp.new then + Elements.container.remove_warp(warp) + else + Elements.container.set_editing(warp, player, false) + end + end) --[[@as any]] + +--- @class ExpGui_WarpList.elements.edit_warp_button.element_data +--- @field warp ExpGui_WarpList.Warp + +--- Button which starts editing a warp +--- @class ExpGui_WarpList.elements.edit_warp_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, warp: ExpGui_WarpList.Warp): LuaGuiElement +Elements.edit_warp_button = Gui.define("warp_list/edit_warp_button") + :draw{ + type = "sprite-button", + sprite = "utility/rename_icon", + tooltip = { "exp-gui_warp-list.tooltip-edit-none" }, + style = "shortcut_bar_button", + } + :style(Styles.sprite22) + :element_data{ + warp = Gui.from_argument(1), + } + :on_click(function(def, player, edit_warp_button) + --- @cast def ExpGui_WarpList.elements.edit_warp_button + Elements.container.set_editing(def.data[edit_warp_button].warp, player, true) + end) --[[@as any]] + +--- Refresh the tooltip which lists who is editing the warp +--- @param edit_warp_button LuaGuiElement +--- @param warp ExpGui_WarpList.Warp +function Elements.edit_warp_button.refresh(edit_warp_button, warp) + if next(warp.editing) then + local player_names = table.get_values(warp.editing) + edit_warp_button.hovered_sprite = "utility/warning_icon" + edit_warp_button.tooltip = { "exp-gui_warp-list.tooltip-edit", table.concat(player_names, ", ") } + else + edit_warp_button.hovered_sprite = "utility/rename_icon" + edit_warp_button.tooltip = { "exp-gui_warp-list.tooltip-edit-none" } + end +end + +--- @class ExpGui_WarpList.elements.remove_warp_button.element_data +--- @field warp ExpGui_WarpList.Warp + +--- Button which removes a warp along with its area and map tag +--- @class ExpGui_WarpList.elements.remove_warp_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, warp: ExpGui_WarpList.Warp): LuaGuiElement +Elements.remove_warp_button = Gui.define("warp_list/remove_warp_button") + :draw{ + type = "sprite-button", + sprite = "utility/trash", + tooltip = { "exp-gui_warp-list.tooltip-remove" }, + style = "shortcut_bar_button_red", + } + :style(Styles.sprite22) + :element_data{ + warp = Gui.from_argument(1), + } + :on_click(function(def, _, remove_warp_button) + --- @cast def ExpGui_WarpList.elements.remove_warp_button + Elements.container.remove_warp(def.data[remove_warp_button].warp) + end) --[[@as any]] + +--- @class ExpGui_WarpList.elements.warp_table.row +--- @field warp ExpGui_WarpList.Warp +--- @field icon_flow LuaGuiElement +--- @field name_flow LuaGuiElement +--- @field action_flow LuaGuiElement +--- @field warp_button LuaGuiElement +--- @field icon_picker LuaGuiElement +--- @field status_label LuaGuiElement +--- @field name_label LuaGuiElement +--- @field name_field LuaGuiElement +--- @field confirm_edit_button LuaGuiElement +--- @field discard_edit_button LuaGuiElement +--- @field edit_warp_button LuaGuiElement +--- @field remove_warp_button LuaGuiElement + +--- @class ExpGui_WarpList.elements.warp_table.element_data +--- @field rows table Keyed by warp id + +--- Table with a row of icon, name, and actions for each warp of the player's force +--- @class ExpGui_WarpList.elements.warp_table: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.warp_table = Gui.define("warp_list/warp_table") + :track_all_elements() + :element_data{ rows = {} } + :draw(function(_, parent) + local warp_table = Gui.elements.scroll_table(parent, 250, 3, "scroll") + -- Always showing the scrollbar stops the gui changing width as warps are added + assert(warp_table.parent).vertical_scroll_policy = "always" + return warp_table + end) + :style{ + top_cell_padding = 3, + bottom_cell_padding = 3, + } --[[@as any]] + +--- Add a row for a warp +--- @param warp_table LuaGuiElement +--- @param warp ExpGui_WarpList.Warp +--- @return ExpGui_WarpList.elements.warp_table.row +function Elements.warp_table.add_row(warp_table, warp) + local icon_flow = warp_table.add{ type = "flow" } + icon_flow.style.padding = 0 + local warp_button = Elements.warp_button(icon_flow, warp) + local icon_picker = Elements.icon_picker(icon_flow, warp) + + local name_flow = warp_table.add{ type = "flow" } + name_flow.style.padding = 0 + local status_label = Elements.status_label(name_flow) + local name_label = Elements.name_label(name_flow, warp) + local name_field = Elements.name_field(name_flow, warp, icon_picker) + + local action_flow = warp_table.add{ type = "flow" } + action_flow.style.padding = 0 + local confirm_edit_button = Elements.confirm_edit_button(action_flow, warp, name_field, icon_picker) + local discard_edit_button = Elements.discard_edit_button(action_flow, warp) + local edit_warp_button = Elements.edit_warp_button(action_flow, warp) + local remove_warp_button = Elements.remove_warp_button(action_flow, warp) + + local row = { + warp = warp, + icon_flow = icon_flow, + name_flow = name_flow, + action_flow = action_flow, + warp_button = warp_button, + icon_picker = icon_picker, + status_label = status_label, + name_label = name_label, + name_field = name_field, + confirm_edit_button = confirm_edit_button, + discard_edit_button = discard_edit_button, + edit_warp_button = edit_warp_button, + remove_warp_button = remove_warp_button, + } + + Elements.warp_table.data[warp_table].rows[warp.id] = row + return row +end + +--- Remove the row for a warp +--- @param warp_table LuaGuiElement +--- @param warp ExpGui_WarpList.Warp +function Elements.warp_table.remove_row(warp_table, warp) + local rows = Elements.warp_table.data[warp_table].rows + local row = rows[warp.id] + if not row then return end + rows[warp.id] = nil + row.icon_flow.destroy() + row.name_flow.destroy() + row.action_flow.destroy() +end + +--- Refresh whether a warp can be used from where the player is +--- @param row ExpGui_WarpList.elements.warp_table.row +--- @param status ExpGui_WarpList.PlayerStatus +function Elements.warp_table._refresh_row_status(row, status) + local warp = row.warp + local in_range = status.in_range + local position = warp.position + local tooltip, icon, enabled + + if not in_range then + if status.bypass_proximity then + tooltip, icon, enabled = { "exp-gui_warp-list.tooltip-goto-bypass", position.x, position.y }, status_icons.bypass, true + else + tooltip, icon, enabled = { "exp-gui_warp-list.tooltip-goto-disabled" }, status_icons.not_available, false + end + elseif in_range == warp then + tooltip, icon, enabled = { "exp-gui_warp-list.tooltip-goto-same-warp" }, status_icons.current, false + elseif status.on_cooldown then + tooltip, icon, enabled = { "exp-gui_warp-list.tooltip-goto-cooldown" }, status_icons.cooldown, false + else + -- Warps are connected when their poles share an electric network, missing poles never match + local network_id = warp.electric_pole and warp.electric_pole.valid and warp.electric_pole.electric_network_id or -1 + local in_range_network_id = in_range.electric_pole and in_range.electric_pole.valid and in_range.electric_pole.electric_network_id or -2 + if network_id == in_range_network_id then + tooltip, icon, enabled = { "exp-gui_warp-list.tooltip-goto", position.x, position.y }, status_icons.connected, true + elseif status.bypass_proximity then + tooltip, icon, enabled = { "exp-gui_warp-list.tooltip-goto-bypass-different-network", position.x, position.y }, status_icons.bypass, true + else + tooltip, icon, enabled = { "exp-gui_warp-list.tooltip-goto-different-network" }, status_icons.different, false + end + end + + row.warp_button.tooltip = tooltip + row.warp_button.enabled = enabled + row.status_label.tooltip = tooltip + row.status_label.caption = icon + row.name_label.style.font = enabled and "default-semibold" or "default" +end + +--- Refresh a row to match its warp, the player's permissions, and where the player is +--- @param warp_table LuaGuiElement +--- @param row ExpGui_WarpList.elements.warp_table.row +--- @param status ExpGui_WarpList.PlayerStatus +function Elements.warp_table.refresh_row(warp_table, row, status) + local player = Gui.get_player(warp_table) + local warp = row.warp + local can_edit = has_permission_edit_warp(player, warp) + local editing = warp.editing[player.index] ~= nil + + Elements.edit_warp_button.refresh(row.edit_warp_button, warp) + Elements.name_label.refresh(row.name_label, warp) + + row.warp_button.visible = not editing + row.icon_picker.visible = editing + row.name_label.visible = not editing + row.name_field.visible = editing + row.confirm_edit_button.visible = editing + row.discard_edit_button.visible = editing + row.edit_warp_button.visible = can_edit and not editing + row.remove_warp_button.visible = can_edit and not editing + + if editing then + row.name_field.focus() + assert(warp_table.parent).scroll_to_element(row.name_field, "top-third") + else + row.warp_button.sprite = icon_sprite(warp.icon) + row.icon_picker.elem_value = warp.icon + row.name_field.text = warp.name + end + + Elements.warp_table._refresh_row_status(row, status) +end + +--- Rebuild every row in the order of the warps +--- @param warp_table LuaGuiElement +--- @param warps ExpGui_WarpList.Warp[] +--- @param status ExpGui_WarpList.PlayerStatus +function Elements.warp_table.rebuild(warp_table, warps, status) + warp_table.clear() + Elements.warp_table.data[warp_table].rows = {} + for _, warp in ipairs(warps) do + local row = Elements.warp_table.add_row(warp_table, warp) + Elements.warp_table.refresh_row(warp_table, row, status) + end +end + +--- Refresh the row of one warp for the online players of its force +--- @param warp ExpGui_WarpList.Warp +function Elements.warp_table.refresh_row_force(warp) + for player, warp_table in Elements.warp_table:online_elements(warp.force) do + --- @cast player -nil + local row = Elements.warp_table.data[warp_table].rows[warp.id] + if row then + Elements.warp_table.refresh_row(warp_table, row, Elements.container.get_status(player)) + end + end +end + +--- Refresh whether each warp can be used from where the player is +--- @param player LuaPlayer +function Elements.warp_table.refresh_status_player(player) + local status = Elements.container.get_status(player) + for _, warp_table in Elements.warp_table:tracked_elements(player) do + for _, row in pairs(Elements.warp_table.data[warp_table].rows) do + Elements.warp_table._refresh_row_status(row, status) + end + end +end + +--- Rebuild the rows for a player, used when their force or permissions change +--- @param player LuaPlayer +function Elements.warp_table.rebuild_player(player) + local warps = Elements.container.get_warps(player.force --[[@as LuaForce]]) + local status = Elements.container.get_status(player) + for _, warp_table in Elements.warp_table:tracked_elements(player) do + Elements.warp_table.rebuild(warp_table, warps, status) + end +end + +--- Rebuild the rows for the online players of a force, used when the warps change +--- @param force LuaForce +function Elements.warp_table.rebuild_force(force) + local warps = Elements.container.get_warps(force) + for player, warp_table in Elements.warp_table:online_elements(force) do + --- @cast player -nil + Elements.warp_table.rebuild(warp_table, warps, Elements.container.get_status(player)) + end +end + +--- Progress bar which fills as the warp cooldown runs out +--- @class ExpGui_WarpList.elements.cooldown_bar: ExpElement +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.cooldown_bar = Gui.define("warp_list/cooldown_bar") + :track_all_elements() + :draw{ + type = "progressbar", + value = 1, + tooltip = { "exp-gui_warp-list.tooltip-cooldown-none", config.cooldown_duration }, + } + :style{ + horizontally_stretchable = true, + color = Colors.light_blue, + } --[[@as any]] + +--- Refresh the bar to show the ticks left on the cooldown +--- @param cooldown_bar LuaGuiElement +--- @param cooldown number +function Elements.cooldown_bar.refresh(cooldown_bar, cooldown) + if cooldown > 0 then + cooldown_bar.value = 1 - cooldown / cooldown_ticks + cooldown_bar.tooltip = { "exp-gui_warp-list.tooltip-cooldown", ceil(cooldown / 60) } + else + cooldown_bar.value = 1 + cooldown_bar.tooltip = { "exp-gui_warp-list.tooltip-cooldown-none", config.cooldown_duration } + end +end + +--- Refresh the bars of a player +--- @param player LuaPlayer +--- @param cooldown number +function Elements.cooldown_bar.refresh_player(player, cooldown) + for _, cooldown_bar in Elements.cooldown_bar:tracked_elements(player) do + Elements.cooldown_bar.refresh(cooldown_bar, cooldown) + end +end + +--- @class ExpGui_WarpList.elements.container.elements +--- @field frame LuaGuiElement +--- @field add_warp_button LuaGuiElement +--- @field warp_table LuaGuiElement +--- @field cooldown_bar LuaGuiElement + +--- @class ExpGui_WarpList.elements.container.force_data +--- @field warps ExpGui_WarpList.Warp[] The spawn warp first, then by name +--- @field spawn ExpGui_WarpList.Warp? +--- @field next_warp_id number + +--- @class ExpGui_WarpList.elements.container.player_data +--- @field cooldown number Ticks until the player can warp again +--- @field in_range ExpGui_WarpList.Warp? The warp the player is standing on +--- @field blocking ExpGui_WarpList.Warp? The warp too close for another to be added +--- @field keep_open boolean True once the player toggles the list themselves, which stops it hiding when they leave a warp + +--- @class ExpGui_WarpList.elements.container.data +--- @field [LuaGuiElement] ExpGui_WarpList.elements.container.elements +--- @field [LuaForce] ExpGui_WarpList.elements.container.force_data +--- @field [LuaPlayer] ExpGui_WarpList.elements.container.player_data + +--- Container added to the left gui flow +--- @class ExpGui_WarpList.elements.container: ExpElement +--- @field data ExpGui_WarpList.elements.container.data +Elements.container = Gui.define("warp_list/container") + :draw(function(def, parent) + --- @cast def ExpGui_WarpList.elements.container + local player = Gui.get_player(parent) + local allow_add_warp = has_config_permission(player, "allow_add_warp") + local container = Gui.elements.container(parent, allow_add_warp and 268 or 220) + + local header = Gui.elements.header(container, { + caption = { "exp-gui_warp-list.caption-main" }, + tooltip = { + "exp-gui_warp-list.tooltip-sub", + config.cooldown_duration, + config.standard_proximity_radius, + { "exp-gui_warp-list.tooltip-sub-current", status_icons.current }, + { "exp-gui_warp-list.tooltip-sub-connected", status_icons.connected }, + { "exp-gui_warp-list.tooltip-sub-different", status_icons.different }, + { "exp-gui_warp-list.tooltip-sub-cooldown", status_icons.cooldown }, + { "exp-gui_warp-list.tooltip-sub-not-available", status_icons.not_available }, + { "exp-gui_warp-list.tooltip-sub-bypass", status_icons.bypass }, + }, + }) + + local add_warp_button = Elements.add_warp_button(header) + add_warp_button.visible = allow_add_warp + + local warp_table = Elements.warp_table(container) + local force = player.force --[[@as LuaForce]] + Elements.warp_table.rebuild(warp_table, def.get_warps(force), def.get_status(player)) + + local cooldown_bar = Elements.cooldown_bar(container) + Elements.cooldown_bar.refresh(cooldown_bar, def._get_player_data(player).cooldown) + + local root = Gui.elements.container.get_root_element(container) + def.data[root] = { + frame = container, + add_warp_button = add_warp_button, + warp_table = warp_table, + cooldown_bar = cooldown_bar, + } + + return root + end) --[[@as any]] + +--- Get or create the data for a force +--- @param force LuaForce +--- @return ExpGui_WarpList.elements.container.force_data +function Elements.container._get_force_data(force) + local force_data = Elements.container.data[force] + if not force_data then + force_data = { warps = {}, next_warp_id = 1 } + Elements.container.data[force] = force_data + end + return force_data +end + +--- Get or create the data for a player +--- @param player LuaPlayer +--- @return ExpGui_WarpList.elements.container.player_data +function Elements.container._get_player_data(player) + local player_data = Elements.container.data[player] + if not player_data then + player_data = { cooldown = 0, keep_open = false } + Elements.container.data[player] = player_data + end + return player_data +end + +--- Get the warps of a force, the spawn warp first and then by name +--- @param force LuaForce +--- @return ExpGui_WarpList.Warp[] +function Elements.container.get_warps(force) + return Elements.container._get_force_data(force).warps +end + +--- Get the spawn warp of a force +--- @param force LuaForce +--- @return ExpGui_WarpList.Warp? +function Elements.container.get_spawn_warp(force) + return Elements.container._get_force_data(force).spawn +end + +--- Get what decides which warps a player can use +--- @param player LuaPlayer +--- @return ExpGui_WarpList.PlayerStatus +function Elements.container.get_status(player) + local player_data = Elements.container._get_player_data(player) + return { + in_range = player_data.in_range, + on_cooldown = player_data.cooldown > 0, + bypass_proximity = has_config_permission(player, "bypass_warp_proximity"), + } +end + +--- Keep the warps of a force in display order +--- @param force_data ExpGui_WarpList.elements.container.force_data +function Elements.container._sort_warps(force_data) + local spawn = force_data.spawn + table.sort(force_data.warps, function(a, b) + if a == spawn then return true end + if b == spawn then return false end + if a.name == b.name then return a.id < b.id end + return a.name < b.name + end) +end + +--- Add or update the map tag of a warp +--- @param warp ExpGui_WarpList.Warp +function Elements.container._make_tag(warp) + local tag = warp.tag + if tag and tag.valid then + tag.text = "Warp: " .. warp.name + tag.icon = warp.icon + return + end + + local position = warp.position + warp.tag = warp.force.add_chart_tag(warp.surface, { + position = { x = position.x + 0.5, y = position.y + 0.5 }, + text = "Warp: " .. warp.name, + icon = warp.icon, + }) +end + +--- Remove the map tag of a warp +--- @param warp ExpGui_WarpList.Warp +function Elements.container._remove_tag(warp) + local tag = warp.tag + if tag and tag.valid then + tag.destroy() + end + warp.tag = nil +end + +--- Place the tiles and entities which mark a warp on the ground +--- @param warp ExpGui_WarpList.Warp +function Elements.container._make_area(warp) + local surface = warp.surface + local position = warp.position + warp.old_tile = surface.get_tile(position.x, position.y).name + + local tiles = {} + for index, tile in ipairs(config.tiles) do + tiles[index] = { name = tile[1], position = { x = tile[2] + position.x, y = tile[3] + position.y } } + end + surface.set_tiles(tiles) + + for _, entity_config in ipairs(config.entities) do + local entity = assert(surface.create_entity{ + name = entity_config[1], + position = { x = entity_config[2] + position.x, y = entity_config[3] + position.y }, + force = "neutral", + }) + entity.destructible = false + entity.health = 0 + entity.minable_flag = false + entity.rotatable = false + + if entity.type == "electric-pole" then + warp.electric_pole = entity + end + end +end + +--- Restore the ground under a warp and remove its entities +--- @param warp ExpGui_WarpList.Warp +function Elements.container._remove_area(warp) + local old_tile = warp.old_tile + if not old_tile then return end + + local surface = warp.surface + local position = warp.position + local tiles = {} + for index, tile in ipairs(config.tiles) do + tiles[index] = { name = old_tile, position = { x = tile[2] + position.x, y = tile[3] + position.y } } + end + surface.set_tiles(tiles) + + local radius = config.standard_proximity_radius + local area = { + left_top = { x = position.x - radius, y = position.y - radius }, + right_bottom = { x = position.x + radius, y = position.y + radius }, + } + local entities = surface.find_entities_filtered{ force = "neutral", area = area, name = area_entity_names } + for _, entity in pairs(entities) do + if entity.destructible == false then + entity.destructible = true + entity.die(entity.force) + end + end + + -- The area may not be covered by a radar + warp.force.chart(surface, area) +end + +--- Add a warp for a force, when added by a player it is new until they confirm their first edit +--- @param force LuaForce +--- @param surface LuaSurface +--- @param position MapPosition.struct +--- @param player LuaPlayer? +--- @param name string? +--- @return ExpGui_WarpList.Warp +function Elements.container.add_warp(force, surface, position, player, name) + local force_data = Elements.container._get_force_data(force) + local id = force_data.next_warp_id + force_data.next_warp_id = id + 1 + + --- @type ExpGui_WarpList.Warp + local warp = { + id = id, + force = force, + name = name or "New warp", + icon = { type = config.default_icon.type, name = config.default_icon.name }, + surface = surface, + position = { x = floor(position.x), y = floor(position.y) }, + last_user = player, + last_edit_tick = game.tick, + editing = {}, + } + + -- Warps added by a player start in edit mode, discarding that edit removes them again + if player then + warp.new = true + Elements.container._make_area(warp) + end + + force_data.warps[#force_data.warps + 1] = warp + Elements.container._sort_warps(force_data) + Elements.container._make_tag(warp) + + Elements.warp_table.rebuild_force(force) + return warp +end + +--- Remove a warp along with its area and map tag +--- @param warp ExpGui_WarpList.Warp +function Elements.container.remove_warp(warp) + local force_data = Elements.container._get_force_data(warp.force) + table.remove_element(force_data.warps, warp) + if force_data.spawn == warp then + force_data.spawn = nil + end + + Elements.container._remove_tag(warp) + Elements.container._remove_area(warp) + + -- Offline players are corrected by their next proximity update + for _, player in pairs(warp.force.connected_players) do + local player_data = Elements.container._get_player_data(player) + if player_data.in_range == warp then player_data.in_range = nil end + if player_data.blocking == warp then player_data.blocking = nil end + end + + for _, warp_table in Elements.warp_table:online_elements(warp.force) do + Elements.warp_table.remove_row(warp_table, warp) + end +end + +--- Save a new name and icon for a warp, which also ends the player's edit +--- @param warp ExpGui_WarpList.Warp +--- @param name string +--- @param icon SignalID +--- @param player LuaPlayer +function Elements.container.update_warp(warp, name, icon, player) + warp.name = name + warp.icon = icon + warp.last_user = player + warp.last_edit_tick = game.tick + warp.editing[player.index] = nil + warp.new = nil + + Elements.container._sort_warps(Elements.container._get_force_data(warp.force)) + Elements.container._make_tag(warp) + Elements.warp_table.rebuild_force(warp.force) +end + +--- Start or stop a player editing a warp +--- @param warp ExpGui_WarpList.Warp +--- @param player LuaPlayer +--- @param editing boolean +function Elements.container.set_editing(warp, player, editing) + warp.editing[player.index] = editing and player.name or nil + Elements.warp_table.refresh_row_force(warp) +end + +--- Make a warp the spawn of its force, the force respawns there +--- @param warp ExpGui_WarpList.Warp +function Elements.container.set_spawn_warp(warp) + local force_data = Elements.container._get_force_data(warp.force) + force_data.spawn = warp + Elements.container._sort_warps(force_data) + warp.force.set_spawn_position(warp.position, warp.surface) +end + +--- Move a player to a warp and start their cooldown +--- @param player LuaPlayer +--- @param warp ExpGui_WarpList.Warp +function Elements.container.teleport_player(player, warp) + local surface = warp.surface + local position = { x = warp.position.x + 0.5, y = warp.position.y + 0.5 } + + local vehicle = player.vehicle + if vehicle then + local vehicle_position = surface.find_non_colliding_position(vehicle.name, position, 32, 1) + -- Only cars can be teleported between surfaces + if vehicle.type == "car" then + vehicle.teleport(vehicle_position, surface) + elseif surface == vehicle.surface then + if not vehicle.teleport(vehicle_position) then + player.driving = false + player.teleport(surface.find_non_colliding_position("character", position, 32, 1), surface) + end + end + else + player.teleport(surface.find_non_colliding_position("character", position, 32, 1), surface) + end + + local player_data = Elements.container._get_player_data(player) + if not has_config_permission(player, "bypass_warp_cooldown") then + player_data.cooldown = cooldown_ticks + Elements.cooldown_bar.refresh_player(player, cooldown_ticks) + end + player_data.in_range = warp + Elements.warp_table.refresh_status_player(player) +end + +--- Refresh the parts of a player's gui which depend on their permissions +--- @param player LuaPlayer +function Elements.container.refresh_player(player) + local allow_add_warp = has_config_permission(player, "allow_add_warp") + for _, container in Elements.container:tracked_elements(player) do + local elements = Elements.container.data[container] + elements.frame.style.minimal_width = allow_add_warp and 268 or 220 + elements.add_warp_button.visible = allow_add_warp + end + Elements.warp_table.rebuild_player(player) +end + +--- Count down the cooldown of a player +--- @param player LuaPlayer +--- @param player_data ExpGui_WarpList.elements.container.player_data +function Elements.container._update_cooldown(player, player_data) + if player_data.cooldown == 0 then return end + local cooldown = player_data.cooldown - update_interval + if cooldown < 0 then cooldown = 0 end + player_data.cooldown = cooldown + + Elements.cooldown_bar.refresh_player(player, cooldown) + if cooldown == 0 then + Elements.warp_table.refresh_status_player(player) + end +end + +--- Find which warp a player is standing on and whether one can be added where they are +--- @param player LuaPlayer +--- @param player_data ExpGui_WarpList.elements.container.player_data +function Elements.container._update_proximity(player, player_data) + local force_data = Elements.container._get_force_data(player.force --[[@as LuaForce]]) + local surface = player.physical_surface + local position = player.physical_position + local px, py = position.x, position.y + + local closest, closest_distance = nil, nil --- @type ExpGui_WarpList.Warp?, number? + for _, warp in ipairs(force_data.warps) do + if warp.surface == surface then + local dx, dy = px - warp.position.x, py - warp.position.y + local distance = dx * dx + dy * dy + if not closest_distance or distance < closest_distance then + closest, closest_distance = warp, distance + end + end + end + + local in_range = nil --- @type ExpGui_WarpList.Warp? + if closest and closest_distance < (closest == force_data.spawn and spawn_radius_sq or proximity_radius_sq) then + in_range = closest + end + if in_range ~= player_data.in_range then + player_data.in_range = in_range + if not player_data.keep_open then + Gui.toolbar.set_left_element_visible_state(Elements.container, player, in_range ~= nil) + end + Elements.warp_table.refresh_status_player(player) + end + + local blocking = nil --- @type ExpGui_WarpList.Warp? + if closest and closest_distance <= minimum_distance_sq then + blocking = closest + end + if blocking ~= player_data.blocking then + player_data.blocking = blocking + Elements.add_warp_button.refresh_player_blocking(player, blocking) + end +end + +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(Elements.container, false) +Gui.toolbar.create_button{ + name = "toggle_warp_list", + left_element = Elements.container, + sprite = config.default_icon.type .. "/" .. config.default_icon.name, + tooltip = { "exp-gui_warp-list.tooltip-main" }, + visible = function(player, element) + return Roles.player_has_permission(player, "exp_scenario.gui.warp_list") + end +}:on_click(function(def, player) + -- A list the player opened themselves stays open when they leave a warp + Elements.container._get_player_data(player).keep_open = Gui.toolbar.get_button_toggled_state(def, player) +end) + +--- Give a force a spawn warp when its first player is created +--- @param event EventData.on_player_created +local function on_player_created(event) + local player = Gui.get_player(event) + local force = player.force --[[@as LuaForce]] + if Elements.container.get_spawn_warp(force) then return end + + local surface = player.physical_surface + local position = force.get_spawn_position(surface) + local warp = Elements.container.add_warp(force, surface, position, nil, "Spawn") + Elements.container.set_spawn_warp(warp) + + -- Spawn has no warp area, so any pole nearby connects it to the network + local poles = surface.find_entities_filtered{ type = "electric-pole", position = position, radius = 20, limit = 1 } + warp.electric_pole = poles[1] +end + +--- Rebuild the gui for a player as it may be outdated +--- @param event EventData.on_player_joined_game | EventData.on_player_changed_force +local function refresh_player_warps(event) + local player = Gui.get_player(event) + Elements.container.refresh_player(player) + Elements.cooldown_bar.refresh_player(player, Elements.container._get_player_data(player).cooldown) +end + +--- Rebuild the gui for a player as their permissions may have changed +--- @param event EventData.ExpRoles.on_player_roles_changed +local function refresh_player_permissions(event) + Elements.container.refresh_player(Gui.get_player(event)) +end + +--- Restore the map tag of a warp when a player edits or removes it +--- @param event EventData.on_chart_tag_modified | EventData.on_chart_tag_removed +local function on_chart_tag_changed(event) + if not event.player_index then return end + for _, warp in ipairs(Elements.container.get_warps(event.force)) do + local tag = warp.tag + if not tag or not tag.valid or tag == event.tag then + if event.name == defines.events.on_chart_tag_removed then + warp.tag = nil + end + Elements.container._make_tag(warp) + end + end +end + +--- Count down cooldowns and check where each player is +local function update_players() + for _, player in pairs(game.connected_players) do + local player_data = Elements.container._get_player_data(player) + Elements.container._update_cooldown(player, player_data) + Elements.container._update_proximity(player, player_data) + end +end + +local e = defines.events + +return { + elements = Elements, + events = { + [e.on_player_created] = on_player_created, + [e.on_player_joined_game] = refresh_player_warps, + [e.on_player_changed_force] = refresh_player_warps, + [e.on_chart_tag_modified] = on_chart_tag_changed, + [e.on_chart_tag_removed] = on_chart_tag_changed, + [Roles.events.on_player_roles_changed] = refresh_player_permissions, + }, + on_nth_tick = { + [update_interval] = update_players, + } +} diff --git a/exp_scenario/module/locale/en.cfg b/exp_scenario/module/locale/en.cfg index 283460361e..8b27d48c85 100644 --- a/exp_scenario/module/locale/en.cfg +++ b/exp_scenario/module/locale/en.cfg @@ -490,6 +490,37 @@ caption-create-footer=Create task caption-edit-footer=Edit task caption-view-footer=Task details +[exp-gui_warp-list] +caption-main=Warp List [img=info] +tooltip-main=Warp List +tooltip-sub=Warps can only be used every __1__ seconds and when within __2__ tiles\n__3__\n__4__\n__5__\n__6__\n__7__\n__8__ +tooltip-sub-current= - __1__ This is your current warp point; +tooltip-sub-connected= - __1__ You can travel to this warp point; +tooltip-sub-different= - __1__ This warp is on a different network; +tooltip-sub-cooldown= - __1__ You are currently on cooldown; +tooltip-sub-not-available= - __1__ You are not in range of a warp point; +tooltip-sub-bypass= - __1__ Your role allows you to travel here; +tooltip-add=Add new warp +tooltip-add-too-close=Cannot create warp; too close to existing warp point: __1__ +add-warning-water=Cannot create warp; too close to water, please move __1__ tiles away from the water body or landfill it +add-warning-entities=Cannot create warp; too close to other entities, please move __1__ tiles away from the entities or remove them +tooltip-icon=Edit warp icon +tooltip-last-edit=Last edited by __1__ at __2__\nClick to view on map +tooltip-confirm=Save changes +tooltip-discard=Discard changes +tooltip-edit=Currently being edited by: __1__ +tooltip-edit-none=Currently being edited by: Nobody +tooltip-remove=Remove warp +tooltip-cooldown=Please wait __1__ seconds before you can warp +tooltip-cooldown-none=After each warp you will need to wait __1__ seconds before you can warp again +tooltip-goto=Go to x __1__ y __2__ +tooltip-goto-bypass=Go to x __1__ y __2__, bypass mode +tooltip-goto-bypass-different-network=Go to x __1__ y __2__, bypass mode different network +tooltip-goto-same-warp=You are already on this warp +tooltip-goto-different-network=This warp is on a different network, use power poles to connect it +tooltip-goto-cooldown=You are on cooldown, wait for your cooldown to recharge +tooltip-goto-disabled=You are not on a warp point, walk to a warp point + [exp_afk-kick] kick-message=All players were kicked because everyone was AFK. diff --git a/exp_scenario/module/locale/zh-CN.cfg b/exp_scenario/module/locale/zh-CN.cfg index a4783e0cb7..cf48db0bb5 100644 --- a/exp_scenario/module/locale/zh-CN.cfg +++ b/exp_scenario/module/locale/zh-CN.cfg @@ -481,6 +481,37 @@ caption-create-footer=加入工作流程 caption-edit-footer=修改工作流程 caption-view-footer=工作流程細節 +[exp-gui_warp-list] +caption-main=傳送陣清單 [img=info] +tooltip-main=傳送陣清單 +tooltip-sub=傳送陣在 __1__ 秒內只能使用一次。 亦需在 __2__ 格之內使用\n__3__\n__4__\n__5__\n__6__\n__7__\n__8__ +tooltip-sub-current= - __1__ 你目前的傳送陣; +tooltip-sub-connected= - __1__ 你可以到本傳送陣; +tooltip-sub-different= - __1__ 該傳送陣未連接; +tooltip-sub-cooldown= - __1__ 傳送冷卻中; +tooltip-sub-not-available= - __1__ 你不在傳送陣範圍; +tooltip-sub-bypass= - __1__ 你可以傳送到這裏; +tooltip-add=新建傳送陣 +tooltip-add-too-close=無法新建傳送陣; 太接近 __1__ 了 +add-warning-water=無法新建傳送陣; 太接近水源了, 請移動 __1__ 格又或者填平該地方 +add-warning-entities=無法新建傳送陣; 太接近其他東西, 請移動 __1__ 格又或者拆除該東西 +tooltip-icon=修改傳送陣圖案 +tooltip-last-edit=最後由 __1__ 在 __2__ 更改\n按這在地圖上看 +tooltip-confirm=儲存更改 +tooltip-discard=放棄更改 +tooltip-edit=現被 __1__ 修改中 +tooltip-edit-none=現沒有被人修改 +tooltip-remove=拆除傳送陣 +tooltip-cooldown=請等 __1__ 秒才再次傳送 +tooltip-cooldown-none=你要等 __1__ 秒才可以再次傳送 +tooltip-goto=到 X __1__ Y __2__ +tooltip-goto-bypass=到 X __1__ Y __2__, 繞行模式 +tooltip-goto-bypass-different-network=到 X __1__ Y __2__, 繞行模式 (不同網絡) +tooltip-goto-same-warp=你已在該傳送陣 +tooltip-goto-different-network=該傳送陣未連接, 你可使用電纜連接 +tooltip-goto-cooldown=傳送冷卻中, 請等候冷卻 +tooltip-goto-disabled=你不在傳送陣, 請移至任意傳送陣 + [exp_afk-kick] kick-message=因地圖中沒有活躍玩家,所以所有人都已被請離。 diff --git a/exp_scenario/module/locale/zh-TW.cfg b/exp_scenario/module/locale/zh-TW.cfg index a4783e0cb7..cf48db0bb5 100644 --- a/exp_scenario/module/locale/zh-TW.cfg +++ b/exp_scenario/module/locale/zh-TW.cfg @@ -481,6 +481,37 @@ caption-create-footer=加入工作流程 caption-edit-footer=修改工作流程 caption-view-footer=工作流程細節 +[exp-gui_warp-list] +caption-main=傳送陣清單 [img=info] +tooltip-main=傳送陣清單 +tooltip-sub=傳送陣在 __1__ 秒內只能使用一次。 亦需在 __2__ 格之內使用\n__3__\n__4__\n__5__\n__6__\n__7__\n__8__ +tooltip-sub-current= - __1__ 你目前的傳送陣; +tooltip-sub-connected= - __1__ 你可以到本傳送陣; +tooltip-sub-different= - __1__ 該傳送陣未連接; +tooltip-sub-cooldown= - __1__ 傳送冷卻中; +tooltip-sub-not-available= - __1__ 你不在傳送陣範圍; +tooltip-sub-bypass= - __1__ 你可以傳送到這裏; +tooltip-add=新建傳送陣 +tooltip-add-too-close=無法新建傳送陣; 太接近 __1__ 了 +add-warning-water=無法新建傳送陣; 太接近水源了, 請移動 __1__ 格又或者填平該地方 +add-warning-entities=無法新建傳送陣; 太接近其他東西, 請移動 __1__ 格又或者拆除該東西 +tooltip-icon=修改傳送陣圖案 +tooltip-last-edit=最後由 __1__ 在 __2__ 更改\n按這在地圖上看 +tooltip-confirm=儲存更改 +tooltip-discard=放棄更改 +tooltip-edit=現被 __1__ 修改中 +tooltip-edit-none=現沒有被人修改 +tooltip-remove=拆除傳送陣 +tooltip-cooldown=請等 __1__ 秒才再次傳送 +tooltip-cooldown-none=你要等 __1__ 秒才可以再次傳送 +tooltip-goto=到 X __1__ Y __2__ +tooltip-goto-bypass=到 X __1__ Y __2__, 繞行模式 +tooltip-goto-bypass-different-network=到 X __1__ Y __2__, 繞行模式 (不同網絡) +tooltip-goto-same-warp=你已在該傳送陣 +tooltip-goto-different-network=該傳送陣未連接, 你可使用電纜連接 +tooltip-goto-cooldown=傳送冷卻中, 請等候冷卻 +tooltip-goto-disabled=你不在傳送陣, 請移至任意傳送陣 + [exp_afk-kick] kick-message=因地圖中沒有活躍玩家,所以所有人都已被請離。