Merge branch 'main' into feature/auto-restart

This commit is contained in:
Cooldude2606 authored and GitHub committed 2026-09-26 23:06:25 +01:00
commit 2436fd2c5e
24 files changed
+2753 -3048

No files matched your search

+9 -1
View File
@@ -1,15 +1,23 @@
import { BaseControllerPlugin } from "@clusterio/controller";
import { BaseControllerPlugin, type Controller } from "@clusterio/controller";
import * as lib from "@clusterio/lib";
import * as messages from "./messages.js";
import * as path from "node:path";
const loaded = new WeakMap<Controller, ControllerPlugin>();
export class ControllerPlugin extends BaseControllerPlugin {
groups!: lib.SubscribableDatastore<messages.GroupRecord>;
roleMappings!: lib.SubscribableDatastore<messages.RoleMappingRecord>;
manualAssignments!: lib.SubscribableDatastore<messages.AssignmentRecord>;
resolvedAssignments!: lib.SubscribableDatastore<messages.AssignmentRecord>;
/** The plugin loaded on a controller, for the exp_scenario seed. */
static get(controller: Controller) {
return loaded.get(controller);
}
async init() {
loaded.set(this.controller, this);
const databaseDirectory = this.controller.config.get("controller.database_directory");
this.groups = new lib.SubscribableDatastore(
@@ -6,9 +6,6 @@
return {
"expcore.player_data", -- must be loaded first to register event handlers
-- Control
"modules.control.vlayer",
--- Data
"modules.data.statistics",
"modules.data.player-colours",
@@ -17,11 +14,5 @@ return {
"modules.data.alt-view",
"modules.data.tag",
"modules.data.language",
--"modules.data.toolbar",
--- GUI
"modules.gui.warp-list",
"modules.gui.vlayer",
"modules.graftorio.require", -- graftorio
}
-70
View File
@@ -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.
@@ -215,42 +184,3 @@ display-fwrsb=Robot Storage
display-fwrsb-tooltip=Force worker robots storage bonus
display-ffrlm=Robot Lifetime
display-ffrlm-tooltip=Force following robots lifetime modifier
[vlayer]
main-tooltip=vlayer
description-pbr=Recharge Player Battery upto a portion with vlayer
description-vi=Vlayer Info
pbr-not-running=vlayer need to be running
display-item-solar=[img=entity/solar-panel] Solar Panel
display-item-accumulator=[img=entity/accumulator] Accumulator
display-current-production=[virtual-signal=signal-P] Current Production
display-sustained-production=[virtual-signal=signal-S] Sustained Production
display-max-capacity=[virtual-signal=signal-C] Battery Capacity
display-current-capacity=[virtual-signal=signal-E] Battery Charge
display-remaining-surface-area=[virtual-signal=signal-R] Remaining Surface
display-item-solar-tooltip=The amount of Solar Panel
display-item-accumulator-tooltip=The amount of Accumulator
display-current-production-tooltip=Current Power Production
display-sustained-production-tooltip=Sustained Power Production
display-max-capacity-tooltip=Battery Max Capacity
display-current-capacity-tooltip=Battery Current Charge
display-remaining-surface-area-tooltip=Remaining Surface. Insert Landfill to increase
steel-chest-detect=No steel chest detected
steel-chest-empty=Chest is not emptied
control-refresh=Refresh List
control-see=See Interface
control-build=Build Interface
control-remove=Remove Interface
result-build=built
result-remove=removed
result-interface-location=__1__ interface was located at __2__
interface-result=__1__ __2__ __4__ interface at __3__
result-unable=Unable to build __1__ interface, because of __2__
result-multiple=Multiple steel chest detected
result-limit=Max limit exceeded
result-space=Not enough space
control-type-energy=Energy
control-type-circuit=Circuit
control-type-storage-input=Storage Input
control-type-storage-output=Storage Output
power-on-space-research=You need to research [technology=__1__] at __2__ level for the space platform vlayer feature.
+3 -73
View File
@@ -8,7 +8,7 @@ status=伺服器狀態: __1__
github=增加功能或回報錯誤: __1__
patreon=支持我們: __1__
custom-commands=這裹使用了自制指令,如 /tag 和 /me。可使用 /chelp 查看更多。
read-readme=確保你已閱讀相關資訊。按左上 i 圖標可再次查看。
read-readme=確保你已閱讀相關資訊。按左上 i 圖標可再次查看
softmod=這裹用了自設情境,是一種軟裝模組。
redmew=We don't talk about redmew here; they beat us to 1000 members ;-;
lhd=列車必須是左則通行。這是本服務器長久以來的規則。
@@ -18,7 +18,7 @@ lhd=列車必須是左則通行。這是本服務器長久以來的規則。
description-add=設定 / 取得你的加入信息。
description-remove=移除你的加入信息。
arg-message=你的加入信息。
greet=[color=0,1,0] 歡迎來到 __1__ 的服務器! 若果你喜歡本服務器可加入 DISCORD: __2__ [/color]
greet=[color=0,1,0] 歡迎來到 __1__ 的服務器! 若果你喜歡本服務器可加入 Discord: __2__ [/color]
message-set=你的加入信息已更新。
message-get=你的加入信息為: __1__
message-cleared=你的加入信息已清除。
@@ -127,7 +127,7 @@ Locale-tooltip=用戶介面語言
# expcore
[expcore-data]
description-preference=允許寫入資料。
description-data=把用戶資料寫入電腦
description-data=把用戶資料寫入電腦。
arg-option="All", "Statistics", "Settings", "Required" 之中
set-preference=你的喜好已設定為 __1__ 。在你重新加入前將不會有任何改動。
get-preference=你的喜好為 __1__ 。使用 /set-preference 來更改,使用 /save-data 來獲得一份資料副本。
@@ -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 來停用。
@@ -216,45 +185,6 @@ display-fwrsb-tooltip=勢力機械人儲存
display-ffrlm=機械人生命
display-ffrlm-tooltip=勢力戰鬥機械人生命時間
[vlayer]
main-tooltip=vlayer
description-pbr=使用 Vlayer 為玩家電池充電
description-vi=Vlayer 資訊
pbr-not-running=vlayer 沒有運行
display-item-solar=[img=entity/solar-panel] 太陽能板
display-item-accumulator=[img=entity/accumulator] 蓄電池
display-current-production=[virtual-signal=signal-P] 現時產能
display-sustained-production=[virtual-signal=signal-S] 持續產能
display-max-capacity=[virtual-signal=signal-C] 電池容量
display-current-capacity=[virtual-signal=signal-E] 電池電量
display-remaining-surface-area=[virtual-signal=signal-R] 餘下土地
display-item-solar-tooltip=太陽能板數量
display-item-accumulator-tooltip=蓄電池數量
display-current-production-tooltip=現時電力產能
display-sustained-production-tooltip=持續電力產能
display-max-capacity-tooltip=電池最大容量
display-current-capacity-tooltip=現時電池電量
display-remaining-surface-area-tooltip=餘下土地。 放入堆填來增加
steel-chest-detect=沒檢測到鋼箱
steel-chest-empty=鋼箱不是空的
control-refresh=刷新列表
control-see=查看介面
control-build=建造介面
control-remove=拆除介面
result-build=建造了
result-remove=拆除了
result-interface-location=__1__ 介面在 __2__
interface-result=__1__ 在 __2__ __3__ __4__ 介面
result-unable=無法建造 __1__ 介面,因 __2__
result-multiple=檢測到多個鋼箱
result-limit=超出最大限制
result-space=空間不足
control-type-energy=電力
control-type-circuit=回路
control-type-storage-input=放入箱
control-type-storage-output=提取箱
power-on-space-research=你要研究 [technology=__1__] 在 __2__ 級 才可使用太空平台 vlayer 功能。
[toolbar]
main-caption=工具箱
main-tooltip=工具箱設定\n選上來設定喜好
+3 -73
View File
@@ -8,7 +8,7 @@ status=伺服器狀態: __1__
github=增加功能或回報錯誤: __1__
patreon=支持我們: __1__
custom-commands=這裹使用了自制指令,如 /tag 和 /me。可使用 /chelp 查看更多。
read-readme=確保你已閱讀相關資訊。按左上 i 圖標可再次查看。
read-readme=確保你已閱讀相關資訊。按左上 i 圖標可再次查看
softmod=這裹用了自設情境,是一種軟裝模組。
redmew=We don't talk about redmew here; they beat us to 1000 members ;-;
lhd=列車必須是左則通行。這是本服務器長久以來的規則。
@@ -18,7 +18,7 @@ lhd=列車必須是左則通行。這是本服務器長久以來的規則。
description-add=設定 / 取得你的加入信息。
description-remove=移除你的加入信息。
arg-message=你的加入信息。
greet=[color=0,1,0] 歡迎來到 __1__ 的服務器! 若果你喜歡本服務器可加入 DISCORD: __2__ [/color]
greet=[color=0,1,0] 歡迎來到 __1__ 的服務器! 若果你喜歡本服務器可加入 Discord: __2__ [/color]
message-set=你的加入信息已更新。
message-get=你的加入信息為: __1__
message-cleared=你的加入信息已清除。
@@ -127,7 +127,7 @@ Locale-tooltip=用戶介面語言
# expcore
[expcore-data]
description-preference=允許寫入資料。
description-data=把用戶資料寫入電腦
description-data=把用戶資料寫入電腦。
arg-option="All", "Statistics", "Settings", "Required" 之中
set-preference=你的喜好已設定為 __1__ 。在你重新加入前將不會有任何改動。
get-preference=你的喜好為 __1__ 。使用 /set-preference 來更改,使用 /save-data 來獲得一份資料副本。
@@ -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 來停用。
@@ -216,45 +185,6 @@ display-fwrsb-tooltip=勢力機械人儲存
display-ffrlm=機械人生命
display-ffrlm-tooltip=勢力戰鬥機械人生命時間
[vlayer]
main-tooltip=vlayer
description-pbr=使用 Vlayer 為玩家電池充電
description-vi=Vlayer 資訊
pbr-not-running=vlayer 沒有運行
display-item-solar=[img=entity/solar-panel] 太陽能板
display-item-accumulator=[img=entity/accumulator] 蓄電池
display-current-production=[virtual-signal=signal-P] 現時產能
display-sustained-production=[virtual-signal=signal-S] 持續產能
display-max-capacity=[virtual-signal=signal-C] 電池容量
display-current-capacity=[virtual-signal=signal-E] 電池電量
display-remaining-surface-area=[virtual-signal=signal-R] 餘下土地
display-item-solar-tooltip=太陽能板數量
display-item-accumulator-tooltip=蓄電池數量
display-current-production-tooltip=現時電力產能
display-sustained-production-tooltip=持續電力產能
display-max-capacity-tooltip=電池最大容量
display-current-capacity-tooltip=現時電池電量
display-remaining-surface-area-tooltip=餘下土地。 放入堆填來增加
steel-chest-detect=沒檢測到鋼箱
steel-chest-empty=鋼箱不是空的
control-refresh=刷新列表
control-see=查看介面
control-build=建造介面
control-remove=拆除介面
result-build=建造了
result-remove=拆除了
result-interface-location=__1__ 介面在 __2__
interface-result=__1__ 在 __2__ __3__ __4__ 介面
result-unable=無法建造 __1__ 介面,因 __2__
result-multiple=檢測到多個鋼箱
result-limit=超出最大限制
result-space=空間不足
control-type-energy=電力
control-type-circuit=回路
control-type-storage-input=放入箱
control-type-storage-output=提取箱
power-on-space-research=你要研究 [technology=__1__] 在 __2__ 級 才可使用太空平台 vlayer 功能。
[toolbar]
main-caption=工具箱
main-tooltip=工具箱設定\n選上來設定喜好
@@ -1,807 +0,0 @@
--[[-- Control Module - vlayer
- Adds a virtual layer to store power to save space.
@control vlayer
@alias vlayer
]]
local ExpUtil = require("modules/exp_util")
local Storage = require("modules/exp_util/storage")
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local config = require("modules.exp_legacy.config.vlayer") --- @dep config.vlayer
local mega = 1000000
local vlayer = {}
local vlayer_data = {
entity_interfaces = {
energy = {}, --- @type LuaEntity[]
circuit = {}, --- @type LuaEntity[]
storage_input = {}, --- @type LuaEntity[]
storage_output = {}, --- @type LuaEntity[]
},
properties = {
total_surface_area = 0,
used_surface_area = 0,
total_production = 0,
production = 0,
discharge = 0,
capacity = 0,
},
storage = {
items = {}, --- @type table<string, number>
power_items = {}, --- @type table<string, { value: number, count: number }>
energy = 0,
unallocated = {}, --- @type table<string, number>
},
surface = table.deep_copy(config.surface),
}
Storage.register(vlayer_data, function(tbl)
vlayer_data = tbl
end)
for name, properties in pairs(config.allowed_items) do
properties.modded = false
if properties.power then
vlayer_data.storage.power_items[name] = {
value = assert(properties.fuel_value) * 1000000,
count = 0,
}
end
end
-- For all modded items, create a config for them
for item_name, properties in pairs(config.modded_items) do
local base_properties = config.allowed_items[properties.base_game_equivalent]
local m = properties.multiplier
config.allowed_items[item_name] = {
starting_value = properties.starting_value or 0,
required_area = base_properties.required_area or 0,
surface_area = (base_properties.surface_area or 0) * m,
production = (base_properties.production or 0) * m,
capacity = (base_properties.capacity or 0) * m,
modded = true,
}
end
--- Get all items in storage, do not modify
-- @treturn table a dictionary of all items stored in the vlayer
function vlayer.get_items()
return vlayer_data.storage.items
end
--- Get all unallocated items in storage
-- @treturn table a dictionary of all unallocated items stored in the vlayer
function vlayer.get_unallocated_items()
return vlayer_data.storage.unallocated
end
--- Get all allocated items in storage
-- @treturn table a dictionary of all allocated items stored in the vlayer
function vlayer.get_allocated_items()
local r = {}
for k, v in pairs(vlayer_data.storage.items) do
r[k] = v
if vlayer_data.storage.unallocated[k] then
r[k] = r[k] - vlayer_data.storage.unallocated[k]
end
end
return r
end
--- Get the actual defecit of land
local function get_actual_land_defecit()
local n = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area
for k, v in pairs(vlayer.get_unallocated_items()) do
n = n - (config.allowed_items[k].required_area * v)
end
return n
end
--- Get interface counts
-- @treturn table a dictionary of the vlayer interface counts
function vlayer.get_interface_counts()
local interfaces = vlayer_data.entity_interfaces
return {
energy = #interfaces.energy,
circuit = #interfaces.circuit,
storage_input = #interfaces.storage_input,
storage_output = #interfaces.storage_output,
}
end
--- Get interfaces
-- @treturn table a dictionary of the vlayer interfaces
function vlayer.get_interfaces()
local interfaces = vlayer_data.entity_interfaces
return {
energy = interfaces.energy,
circuit = interfaces.circuit,
storage_input = interfaces.storage_input,
storage_output = interfaces.storage_output,
}
end
--[[
25,200 / 420 s
昼 210秒 ソーラー効率100%
夕方 84秒 1秒ごとにソーラー発電量が約1.2%ずつ下がり、やがて0%になる
夜 42秒 ソーラー発電量が0%になる
朝方 84秒 1秒ごとにソーラー発電量が約1.2%ずつ上がり、やがて100%になる
(surface.dawn) 0.75 18,900 Day 12,600 210s
0.00 0 Noon
(surface.dusk) 0.25 6,300 Sunset 5,040 84s
(surface.evening) 0.45 11,340 Night 2,520 42s
(surface.morning) 0.55 13,860 Sunrise 5,040 84s
]]
--- Get the power multiplier based on the surface time
local function get_production_multiplier()
local mul = vlayer_data.surface.solar_power_multiplier
local surface = vlayer_data.surface
if surface.always_day then
-- Surface is always day, so full production is used
return mul
end
if surface.darkness then
-- We are using a real surface, our config does not contain "darkness"
local brightness = 1 - surface.darkness
if brightness >= surface.min_brightness then
return mul * (brightness - surface.min_brightness) / (1 - surface.min_brightness)
else
return 0
end
end
-- Caused by using a set config rather than a surface
local tick = game.tick % surface.ticks_per_day
local daytime = tick / surface.ticks_per_day
surface.daytime = daytime
if daytime <= surface.dusk then -- Noon to Sunset
return mul
elseif daytime <= surface.evening then -- Sunset to Night
return mul * (1 - ((daytime - surface.dusk) / (surface.evening - surface.dusk)))
elseif daytime <= surface.morning then -- Night to Sunrise
return 0
elseif daytime <= surface.dawn then -- Sunrise to Morning
return mul * ((surface.daytime - surface.morning) / (surface.dawn - surface.morning))
else -- Morning to Noon
return mul
end
end
--- Get the sustained power multiplier, this needs improving
local function get_sustained_multiplier()
local mul = vlayer_data.surface.solar_power_multiplier
local surface = vlayer_data.surface
if surface.always_day then
-- Surface is always day, so full production is used
return mul
end
-- For nauvis vanilla: 210s + (1/2 x (84s + 84s))
local day_duration = 1 - surface.dawn + surface.dusk
local sunset_duration = surface.evening - surface.dusk
local sunrise_duration = surface.dawn - surface.morning
return mul * (day_duration + (0.5 * (sunset_duration + sunrise_duration)))
end
--- Internal, Allocate items in the vlayer, this will increase the property values of the vlayer such as production and capacity
-- Does not increment item storage, so should not be called before insert_item unless during init
-- Does not validate area requirements, so checks must be performed before calling this function
-- Accepts negative count for deallocating items
-- @tparam string item_name The name of the item to allocate
-- @tparam number count The count of the item to allocate
function vlayer.allocate_item(item_name, count)
local item_properties = config.allowed_items[item_name]
assert(item_properties, "Item not allowed in vlayer: " .. tostring(item_name))
if item_properties.production then
local nc = item_properties.production * count
vlayer_data.properties.production = vlayer_data.properties.production + nc
vlayer_data.properties.total_production = vlayer_data.properties.total_production + nc
end
if item_properties.capacity then
vlayer_data.properties.capacity = vlayer_data.properties.capacity + item_properties.capacity * count
end
if item_properties.discharge then
vlayer_data.properties.discharge = vlayer_data.properties.discharge + item_properties.discharge * count
end
if item_properties.surface_area then
vlayer_data.properties.total_surface_area = vlayer_data.properties.total_surface_area + item_properties.surface_area * count
end
if item_properties.required_area and item_properties.required_area > 0 then
vlayer_data.properties.used_surface_area = vlayer_data.properties.used_surface_area + item_properties.required_area * count
end
end
function vlayer.unable_alloc_item_pwr_calc(item_name, count)
local item_properties = config.allowed_items[item_name]
if item_properties.production then
vlayer_data.properties.total_production = vlayer_data.properties.total_production + item_properties.production * count
end
end
-- For all allowed items, setup their starting values, default 0
for item_name, properties in pairs(config.allowed_items) do
vlayer_data.storage.items[item_name] = properties.starting_value or 0
if properties.required_area and properties.required_area > 0 then
vlayer_data.storage.unallocated[item_name] = 0
end
vlayer.allocate_item(item_name, properties.starting_value)
end
--- Insert an item into the vlayer, this will increment its count in storage and allocate it if possible
-- @tparam string item_name The name of the item to insert
-- @tparam number count The count of the item to insert
function vlayer.insert_item(item_name, count)
local item_properties = config.allowed_items[item_name]
assert(item_properties, "Item not allowed in vlayer: " .. tostring(item_name))
vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] + count
if not config.unlimited_surface_area and item_properties.required_area and item_properties.required_area > 0 then
-- Calculate how many can be allocated
local surplus_area = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area
local allocate_count = math.min(count, math.floor(surplus_area / item_properties.required_area))
if allocate_count > 0 then
vlayer.allocate_item(item_name, allocate_count)
end
local unallocated = count - allocate_count
vlayer_data.storage.unallocated[item_name] = vlayer_data.storage.unallocated[item_name] + unallocated
vlayer.unable_alloc_item_pwr_calc(item_name, unallocated)
else
vlayer.allocate_item(item_name, count)
end
end
--- Remove an item from the vlayer, this will decrement its count in storage and prioritise unallocated items over deallocation
-- Can not always fulfil the remove request for items which provide surface area, therefore returns the amount actually removed
-- @tparam string item_name The name of the item to remove
-- @tparam number count The count of the item to remove
-- @treturn number The count of the item actually removed
function vlayer.remove_item(item_name, count)
local item_properties = config.allowed_items[item_name]
assert(item_properties, "Item not allowed in vlayer: " .. tostring(item_name))
local remove_unallocated = 0
if not config.unlimited_surface_area and item_properties.required_area and item_properties.required_area > 0 then
-- Remove from the unallocated storage first
remove_unallocated = math.min(count, vlayer_data.storage.unallocated[item_name])
if remove_unallocated > 0 then
vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] - count
vlayer_data.storage.unallocated[item_name] = vlayer_data.storage.unallocated[item_name] - count
end
-- Check if any more items need to be removed
count = count - remove_unallocated
if count == 0 then
return remove_unallocated
end
end
-- Calculate the amount to remove based on items in storage
local remove_count = math.min(count, vlayer_data.storage.items[item_name])
if item_properties.surface_area and item_properties.surface_area > 0 then
-- If the item provides surface area then it has additional limitations
local surplus_area = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area
remove_count = math.min(remove_count, math.floor(surplus_area / item_properties.surface_area))
if remove_count <= 0 then
return remove_unallocated
end
end
-- Remove the item from allocated storage
vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] - remove_count
vlayer.allocate_item(item_name, -remove_count)
return remove_unallocated + remove_count
end
--- Create a new storage input interface
-- @tparam LuaSurface surface The surface to place the interface onto
-- @tparam MapPosition position The position on the surface to place the interface at
-- @tparam[opt] LuaPlayer player The player to show as the last user of the interface
-- @treturn LuaEntity The entity that was created for the interface
function vlayer.create_input_interface(surface, position, circuit, last_user)
local interface = surface.create_entity{ name = "storage-chest", position = position, force = "neutral" }
interface.storage_filter = { name = "deconstruction-planner", quality = "normal" }
table.insert(vlayer_data.entity_interfaces.storage_input, interface)
if last_user then
interface.last_user = last_user
end
if circuit then
for k, _ in pairs(circuit) do
for _, v in pairs(circuit[k]) do
interface.connect_neighbour{ wire = defines.wire_type[k], target_entity = v }
end
end
end
interface.destructible = false
interface.minable_flag = false
interface.operable = true
return interface
end
--- Handle all input interfaces, will take their contents and insert it into the vlayer storage
local function handle_input_interfaces()
for index, interface in pairs(vlayer_data.entity_interfaces.storage_input) do
if not interface.valid then
vlayer_data.entity_interfaces.storage_input[index] = nil
else
local inventory = assert(interface.get_inventory(defines.inventory.chest))
for _, v in pairs(inventory.get_contents()) do
if config.allowed_items[v.name] then
--[[
there are no quality support currently.
so instead, using the stats projection value for higher quality.
]]
local count_deduct
local count_add
if prototypes.quality[v.quality].level == 0 then
count_deduct = v.count
count_add = v.count
elseif prototypes.quality[v.quality].level > 0 and v.count >= 10 then
local batch = math.floor(v.count / 10)
count_deduct = batch * 10
count_add = batch * (10 + (prototypes.quality[v.quality].level * 3))
end
if count_deduct and count_add then
if config.allowed_items[v.name].modded then
local modded_item = assert(config.modded_items[v.name])
if config.modded_auto_downgrade then
vlayer.insert_item(modded_item.base_game_equivalent, count_add * modded_item.multiplier)
else
vlayer.insert_item(v.name, count_add)
end
else
if vlayer_data.storage.power_items[v.name] then
vlayer_data.storage.power_items[v.name].count = vlayer_data.storage.power_items[v.name].count + count_add
else
vlayer.insert_item(v.name, count_add)
end
end
inventory.remove{ name = v.name, count = count_deduct, quality = v.quality }
end
end
end
end
end
end
--- Create a new storage output interface
-- @tparam LuaSurface surface The surface to place the interface onto
-- @tparam MapPosition position The position on the surface to place the interface at
-- @tparam[opt] LuaPlayer player The player to show as the last user of the interface
-- @treturn LuaEntity The entity that was created for the interface
function vlayer.create_output_interface(surface, position, circuit, last_user)
local interface = surface.create_entity{ name = "requester-chest", position = position, force = "neutral" }
table.insert(vlayer_data.entity_interfaces.storage_output, interface)
if last_user then
interface.last_user = last_user
end
if circuit then
for k, _ in pairs(circuit) do
for _, v in pairs(circuit[k]) do
interface.connect_neighbour{ wire = defines.wire_type[k], target_entity = v }
end
end
end
interface.destructible = false
interface.minable_flag = false
interface.operable = true
return interface
end
--- Handle all output interfaces, will take their requests and remove it from the vlayer storage
local function handle_output_interfaces()
for index, interface in pairs(vlayer_data.entity_interfaces.storage_output) do
if not interface.valid then
vlayer_data.entity_interfaces.storage_output[index] = nil
else
local inventory = assert(interface.get_inventory(defines.inventory.chest))
local inventory_request_sections = assert(interface.get_logistic_sections()).sections
for i = 1, #inventory_request_sections do
for _, v in pairs(inventory_request_sections[i].filters) do
if v.value and config.allowed_items[v.value.name] then
local current_amount = inventory.get_item_count(v.value.name)
local request_amount = math.min(assert(v.min) - current_amount, vlayer_data.storage.items[v.value.name])
if request_amount > 0 and inventory.can_insert{ name = v.value.name, count = request_amount } then
local removed_item_count = vlayer.remove_item(v.value.name, request_amount)
if removed_item_count > 0 then
inventory.insert{ name = v.value.name, count = removed_item_count, quality = "normal" }
end
end
end
end
end
end
end
end
--- Handle the unallocated items because more surface area may have been added
local function handle_unallocated()
-- unallocated cant happen when its unlimited
if config.unlimited_surface_area then
return
end
-- Get the total unallocated area so items can be allocated in equal amounts
local unallocated_area = 0
for item_name, count in pairs(vlayer_data.storage.unallocated) do
local item_properties = config.allowed_items[item_name]
unallocated_area = unallocated_area + item_properties.required_area * count
end
if unallocated_area == 0 then
return
end
-- Allocate items in an equal distribution
local surplus_area = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area
for item_name, count in pairs(vlayer_data.storage.unallocated) do
local allocation_count = math.min(count, math.floor(count * surplus_area / unallocated_area))
if allocation_count > 0 then
vlayer_data.storage.unallocated[item_name] = vlayer_data.storage.unallocated[item_name] - allocation_count
vlayer.allocate_item(item_name, allocation_count)
vlayer.unable_alloc_item_pwr_calc(item_name, -allocation_count)
end
end
end
--- Get the statistics for the vlayer
function vlayer.get_statistics()
local vdp = vlayer_data.properties.production * mega
local gdm = get_production_multiplier()
local gsm = get_sustained_multiplier()
local gald = get_actual_land_defecit()
return {
total_surface_area = vlayer_data.properties.total_surface_area,
used_surface_area = vlayer_data.properties.used_surface_area,
remaining_surface_area = gald,
surface_area = vlayer_data.properties.total_surface_area - gald,
production_multiplier = gdm,
energy_max = vdp,
energy_production = vdp * gdm,
energy_total_production = vlayer_data.properties.total_production * gsm * mega,
energy_sustained = vdp * gsm,
energy_capacity = vlayer_data.properties.capacity * mega,
energy_storage = vlayer_data.storage.energy,
}
end
--- add or reduce vlayer power
--- @param power number
--- @return number
function vlayer.energy_changed(power)
local new_value = vlayer_data.storage.energy + power
vlayer_data.storage.energy = new_value
return new_value
end
--- Circuit signals used for the statistics
function vlayer.get_circuits()
return {
total_surface_area = "signal-A",
used_surface_area = "signal-U",
remaining_surface_area = "signal-R",
production_multiplier = "signal-M",
energy_production = "signal-P",
energy_sustained = "signal-S",
energy_capacity = "signal-C",
energy_storage = "signal-E",
}
end
local vlayer_circuits_string = ""
for key, value in pairs(vlayer.get_circuits()) do
vlayer_circuits_string = vlayer_circuits_string .. string.format("[virtual-signal=%s] = %s\n", value, key:gsub("_", " "))
end
--- Create a new circuit interface
-- @tparam LuaSurface surface The surface to place the interface onto
-- @tparam MapPosition position The position on the surface to place the interface at
-- @tparam[opt] LuaPlayer player The player to show as the last user of the interface
-- @treturn LuaEntity The entity that was created for the interface
function vlayer.create_circuit_interface(surface, position, circuit, last_user)
local interface = surface.create_entity{ name = "constant-combinator", position = position, force = "neutral" }
table.insert(vlayer_data.entity_interfaces.circuit, interface)
if last_user then
interface.last_user = last_user
end
if circuit then
for k, _ in pairs(circuit) do
for _, v in pairs(circuit[k]) do
interface.connect_neighbour{ wire = defines.wire_type[k], target_entity = v }
end
end
end
interface.destructible = false
interface.minable_flag = false
interface.operable = true
return interface
end
--- Handle all circuit interfaces, updating their signals to match the vlayer statistics
local function handle_circuit_interfaces()
local stats = vlayer.get_statistics()
for index, interface in pairs(vlayer_data.entity_interfaces.circuit) do
if not interface.valid then
vlayer_data.entity_interfaces.circuit[index] = nil
else
local control = interface.get_or_create_control_behavior() --[[@as LuaConstantCombinatorControlBehavior]]
if control.sections_count == 0 then
control.add_section()
end
local circuit_oc = assert(control.sections[1])
local signal_index = 1
local circuit = vlayer.get_circuits()
-- Set the virtual signals based on the vlayer stats
for stat_name, signal_name in pairs(circuit) do
if stat_name:find("energy") then
circuit_oc.set_slot(signal_index, { value = { type = "virtual", name = signal_name, quality = "normal" }, min = math.floor(stats[stat_name] / mega) })
elseif stat_name == "production_multiplier" then
circuit_oc.set_slot(signal_index, { value = { type = "virtual", name = signal_name, quality = "normal" }, min = math.floor(stats[stat_name] * 10000) })
else
circuit_oc.set_slot(signal_index, { value = { type = "virtual", name = signal_name, quality = "normal" }, min = math.floor(stats[stat_name]) })
end
signal_index = signal_index + 1
end
-- Set the item signals based on stored items
for item_name, count in pairs(vlayer_data.storage.items) do
if prototypes.item[item_name] and count > 0 then
local existing_index = circuit_oc.set_slot(signal_index, { value = { type = "item", name = item_name, quality = "normal" }, min = count })
if existing_index then
circuit_oc.clear_slot(existing_index)
circuit_oc.set_slot(signal_index, { value = { type = "item", name = item_name, quality = "normal" }, min = count })
end
signal_index = signal_index + 1
end
end
-- Clear remaining signals to prevent outdated values being present (caused by count > 0 check)
for clear_index = signal_index, #circuit do
if not circuit_oc.get_slot(clear_index).value then
break -- There are no more signals to clear
end
circuit_oc.clear_slot(clear_index)
end
interface.combinator_description = vlayer_circuits_string
end
end
end
--- Create a new energy interface
-- @tparam LuaSurface surface The surface to place the interface onto
-- @tparam MapPosition position The position on the surface to place the interface at
-- @tparam[opt] LuaPlayer player The player to show as the last user of the interface
-- @treturn LuaEntity The entity that was created for the interface, or nil if it could not be created
function vlayer.create_energy_interface(surface, position, last_user)
if not surface.can_place_entity{ name = "electric-energy-interface", position = position } then
return nil
end
local interface = surface.create_entity{ name = "electric-energy-interface", position = position, force = "neutral" }
table.insert(vlayer_data.entity_interfaces.energy, interface)
if last_user then
interface.last_user = last_user
end
interface.destructible = false
interface.minable_flag = false
interface.operable = false
interface.electric_buffer_size = 0
interface.power_production = 0
interface.power_usage = 0
interface.energy = 0
return interface
end
--- Handle all energy interfaces as well as the energy storage
local function handle_energy_interfaces()
-- Add the newly produced power
local production = vlayer_data.properties.production * mega * (config.update_tick_energy / 60)
vlayer_data.storage.energy = vlayer_data.storage.energy + math.floor(production * get_production_multiplier())
-- Calculate how much power is present in the network, that is storage + all interfaces
if #vlayer_data.entity_interfaces.energy > 0 then
local available_energy = vlayer_data.storage.energy
for index, interface in pairs(vlayer_data.entity_interfaces.energy) do
if not interface.valid then
vlayer_data.entity_interfaces.energy[index] = nil
else
available_energy = available_energy + interface.energy
end
end
-- Distribute the energy between all interfaces
local discharge_rate = 2 * (production + vlayer_data.properties.discharge * mega) / #vlayer_data.entity_interfaces.energy
local fill_to = math.min(discharge_rate, math.floor(available_energy / #vlayer_data.entity_interfaces.energy))
for _, interface in pairs(vlayer_data.entity_interfaces.energy) do
interface.electric_buffer_size = math.max(discharge_rate, interface.energy) -- prevent energy loss
local delta = fill_to - interface.energy -- positive means storage to interface
vlayer_data.storage.energy = vlayer_data.storage.energy - delta
interface.energy = interface.energy + delta
end
end
-- Cap the stored energy to the allowed capacity
if not config.unlimited_capacity and vlayer_data.storage.energy > vlayer_data.properties.capacity * mega then
vlayer_data.storage.energy = vlayer_data.properties.capacity * mega
-- burn the trash to produce power
elseif vlayer_data.storage.power_items then
for k, v in pairs(vlayer_data.storage.power_items) do
local max_burning = (vlayer_data.properties.capacity * mega / 2) - vlayer_data.storage.energy
if v.count > 0 and max_burning > 0 then
local to_burn = math.min(v.count, math.floor(max_burning / v.value))
vlayer_data.storage.energy = vlayer_data.storage.energy + (to_burn * v.value)
vlayer_data.storage.power_items[k].count = vlayer_data.storage.power_items[k].count - to_burn
end
end
end
end
--- Remove the entity interface using the given position
-- @tparam LuaSurface surface The surface to search for an interface on
-- @tparam MapPosition position The position of the item
-- @treturn string The type of interface that was removed, or nil if no interface was found
-- @treturn MapPosition The position the interface was at, or nil if no interface was found
function vlayer.remove_interface(surface, position)
local entities = surface.find_entities_filtered{
name = { "storage-chest", "requester-chest", "constant-combinator", "electric-energy-interface" },
force = "neutral",
position = position,
radius = 2,
limit = 1,
}
-- Get the details which will be returned
if #entities == 0 then
return nil, nil, nil
end
local interface = entities[1]
local name = interface.name
local sur = interface.surface
local pos = interface.position
-- Return the type of interface removed and do some clean up
if name == "storage-chest" then
local inventory = assert(interface.get_inventory(defines.inventory.chest))
ExpUtil.transfer_inventory_to_surface{
inventory = inventory,
surface = interface.surface,
name = "iron-chest",
allow_creation = true,
}
table.remove_element(vlayer_data.entity_interfaces.storage_input, interface)
interface.destroy()
return "storage-input", sur, pos
elseif name == "requester-chest" then
local inventory = assert(interface.get_inventory(defines.inventory.chest))
ExpUtil.transfer_inventory_to_surface{
inventory = inventory,
surface = interface.surface,
name = "iron-chest",
allow_creation = true,
}
table.remove_element(vlayer_data.entity_interfaces.storage_output, interface)
interface.destroy()
return "storage-output", sur, pos
elseif name == "constant-combinator" then
table.remove_element(vlayer_data.entity_interfaces.circuit, interface)
interface.destroy()
return "circuit", sur, pos
elseif name == "electric-energy-interface" then
vlayer_data.storage.energy = vlayer_data.storage.energy + interface.energy
table.remove_element(vlayer_data.entity_interfaces.energy, interface)
interface.destroy()
return "energy", sur, pos
end
end
local function on_surface_event()
if config.mimic_surface then
local surface = game.get_surface(config.mimic_surface)
if surface then
vlayer_data.surface = surface
return
end
end
if not vlayer_data.surface.index then
-- Our fake surface data never has an index, we test for this to avoid unneeded copies from the config
vlayer_data.surface = table.deep_copy(config.surface)
end
end
--- Handle all storage IO and attempt allocation of unallocated items
Event.on_nth_tick(config.update_tick_storage, function(_)
handle_input_interfaces()
handle_output_interfaces()
handle_unallocated()
end)
--- Handle all energy and circuit updates
Event.on_nth_tick(config.update_tick_energy, function(_)
handle_circuit_interfaces()
handle_energy_interfaces()
end)
Event.add(defines.events.on_surface_created, on_surface_event)
Event.add(defines.events.on_surface_renamed, on_surface_event)
Event.add(defines.events.on_surface_imported, on_surface_event)
Event.on_init(on_surface_event) -- Default surface always exists, does not trigger on_surface_created
return vlayer
-488
View File
@@ -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<string, ExpScenario_Warps.force_warps>
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<string, string>
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 "<server>",
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 "<server>"
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
-547
View File
@@ -1,547 +0,0 @@
--[[-- Gui Module - Virtual Layer
- Adds a virtual layer to store power to save space.
@gui Virtual Layer
@alias vlayer_container
]]
local Gui = require("modules/exp_gui")
local Roles = require("modules/exp_roles")
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local format_number = require("util").format_number --- @dep util
local config = require("modules.exp_legacy.config.vlayer") --- @dep config.vlayer
local vlayer = require("modules.exp_legacy.modules.control.vlayer")
local Selection = require("modules/exp_util/selection")
local SelectArea = Selection.connect("ExpGui_VLayer")
--- Align an aabb to the grid by expanding it
local function aabb_align_expand(aabb)
return {
left_top = { x = math.floor(aabb.left_top.x), y = math.floor(aabb.left_top.y) },
right_bottom = { x = math.ceil(aabb.right_bottom.x), y = math.ceil(aabb.right_bottom.y) },
}
end
local vlayer_container --- @type ExpElement
local vlayer_gui_control_type --- @type ExpElement
local vlayer_gui_control_list --- @type ExpElement
local vlayer_control_type_list = {
[1] = "energy",
[2] = "circuit",
[3] = "storage_input",
[4] = "storage_output",
}
local function pos_to_gps_string(pos, surface_name)
return "[gps=" .. string.format("%.1f", pos.x) .. "," .. string.format("%.1f", pos.y) .. "," .. surface_name .. "]"
end
local function format_energy(amount, unit)
if amount < 1 then
return "0 " .. unit
end
local suffix = ""
local suffix_list = {
["P"] = 1000000000000000,
["T"] = 1000000000000,
["G"] = 1000000000,
["M"] = 1000000,
["k"] = 1000,
}
for letter, limit in pairs(suffix_list) do
if math.abs(amount) >= limit then
amount = string.format("%.1f", amount / limit)
suffix = letter
break
end
end
local k
local formatted = amount
while true do
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", "%1,%2")
if (k == 0) then
break
end
end
return formatted .. " " .. suffix .. unit
end
local ExpUtil = require("modules/exp_util")
--- When an area is selected to add protection to the area
SelectArea:on_selection(function(event)
log(ExpUtil.format_any(event))
local area = aabb_align_expand(event.area)
local player = game.players[event.player_index]
if not player then
return
end
local container = Gui.get_left_element(vlayer_container, player)
local disp = container.frame["vlayer_st_2"].disp.table
local target = vlayer_control_type_list[disp[vlayer_gui_control_type.name].selected_index]
local entities --- @type LuaEntity[]
if config.power_on_space and event.surface and event.surface.platform and target == "energy" then
if (player.force --[[@as LuaForce]]).technologies[config.power_on_space_research.name].level >= config.power_on_space_research.level then
entities = event.surface.find_entities_filtered{ area = area, name = "constant-combinator", force = player.force }
else
player.print{ "vlayer.power-on-space-research", config.power_on_space_research.name, config.power_on_space_research.level }
return
end
else
entities = event.surface.find_entities_filtered{ area = area, name = "steel-chest", force = player.force }
end
if #entities == 0 then
player.print{ "vlayer.steel-chest-detect" }
return
elseif #entities > 1 then
player.print{ "vlayer.result-unable", { "vlayer.control-type-" .. target:gsub("_", "-") }, { "vlayer.result-multiple" } }
return
end
if not entities[1] then
return
end
local e = entities[1]
local e_pos = { x = string.format("%.1f", e.position.x), y = string.format("%.1f", e.position.y) }
local e_circ = nil -- e.get_wire_connectors{ or_create = false }
if e.name and e.name == "steel-chest" and (not assert(e.get_inventory(defines.inventory.chest)).is_empty()) then
player.print{ "vlayer.steel-chest-empty" }
return
end
if (vlayer.get_interface_counts()[target] >= config.interface_limit[target]) then
player.print{ "vlayer.result-unable", { "vlayer.control-type-" .. target:gsub("_", "-") }, { "vlayer.result-limit" } }
return
end
e.destroy()
if target == "energy" then
if not vlayer.create_energy_interface(event.surface, e_pos, player) then
player.print{ "vlayer.result-unable", { "vlayer.control-type-energy" }, { "vlayer.result-space" } }
return
end
elseif target == "circuit" then
vlayer.create_circuit_interface(event.surface, e_pos, e_circ, player)
elseif target == "storage_input" then
vlayer.create_input_interface(event.surface, e_pos, e_circ, player)
elseif target == "storage_output" then
vlayer.create_output_interface(event.surface, e_pos, e_circ, player)
end
game.print{ "vlayer.interface-result", player.name, pos_to_gps_string(e_pos, event.surface.name), { "vlayer.result-build" }, { "vlayer.control-type-" .. target:gsub("_", "-") } }
end)
--- Display label for the number of solar panels
-- @element vlayer_gui_display_item_solar_name
local vlayer_gui_display_item_solar_name = Gui.define("vlayer_gui_display_item_solar_name")
:draw{
type = "label",
name = Gui.from_name,
caption = { "vlayer.display-item-solar" },
style = "heading_2_label",
}:style{
width = 200,
}
local vlayer_gui_display_item_solar_count = Gui.define("vlayer_gui_display_item_solar_count")
:draw{
type = "progressbar",
name = Gui.from_name,
caption = "",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
}:style{
width = 200,
font = "heading-2",
}
--- Display label for the number of accumulators
-- @element vlayer_gui_display_item_accumulator_name
local vlayer_gui_display_item_accumulator_name = Gui.define("vlayer_gui_display_item_accumulator_name")
:draw{
type = "label",
name = Gui.from_name,
caption = { "vlayer.display-item-accumulator" },
style = "heading_2_label",
}:style{
width = 200,
}
local vlayer_gui_display_item_accumulator_count = Gui.define("vlayer_gui_display_item_accumulator_count")
:draw{
type = "progressbar",
name = Gui.from_name,
caption = "",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
}:style{
width = 200,
font = "heading-2",
}
--- Display label for the surface area
-- @element vlayer_gui_display_signal_surface_area_name
local vlayer_gui_display_signal_surface_area_name = Gui.define("vlayer_gui_display_signal_surface_area_name")
:draw{
type = "label",
name = Gui.from_name,
caption = { "vlayer.display-remaining-surface-area" },
tooltip = { "vlayer.display-remaining-surface-area-tooltip" },
style = "heading_2_label",
}:style{
width = 200,
}
local vlayer_gui_display_signal_surface_area_count = Gui.define("vlayer_gui_display_signal_surface_area_count")
:draw{
type = "progressbar",
name = Gui.from_name,
caption = "",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
}:style{
width = 200,
font = "heading-2",
}
--- Display label for the sustained energy production
-- @element vlayer_gui_display_signal_sustained_name
local vlayer_gui_display_signal_sustained_name = Gui.define("vlayer_gui_display_signal_sustained_name")
:draw{
type = "label",
name = Gui.from_name,
caption = { "vlayer.display-sustained-production" },
tooltip = { "vlayer.display-sustained-production-tooltip" },
style = "heading_2_label",
}:style{
width = 200,
}
local vlayer_gui_display_signal_sustained_count = Gui.define("vlayer_gui_display_signal_sustained_count")
:draw{
type = "progressbar",
name = Gui.from_name,
caption = "",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
}:style{
width = 200,
font = "heading-2",
}
--- Display label for the current energy production
-- @element vlayer_gui_display_signal_production_name
local vlayer_gui_display_signal_production_name = Gui.define("vlayer_gui_display_signal_production_name")
:draw{
type = "label",
name = Gui.from_name,
caption = { "vlayer.display-current-production" },
tooltip = { "vlayer.display-current-production-tooltip" },
style = "heading_2_label",
}:style{
width = 200,
}
local vlayer_gui_display_signal_production_count = Gui.define("vlayer_gui_display_signal_production_count")
:draw{
type = "progressbar",
name = Gui.from_name,
caption = "",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
}:style{
width = 200,
font = "heading-2",
}
--- Display label for the sustained energy capacity
-- @element vlayer_gui_display_signal_capacity_name
local vlayer_gui_display_signal_capacity_name = Gui.define("vlayer_gui_display_signal_capacity_name")
:draw{
type = "label",
name = Gui.from_name,
caption = { "vlayer.display-current-capacity" },
tooltip = { "vlayer.display-current-capacity-tooltip" },
style = "heading_2_label",
}:style{
width = 200,
}
local vlayer_gui_display_signal_capacity_count = Gui.define("vlayer_gui_display_signal_capacity_count")
:draw{
type = "progressbar",
name = Gui.from_name,
caption = "",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
}:style{
width = 200,
font = "heading-2",
}
--- A vertical flow containing all the displays labels and their counts
-- @element vlayer_display_set
local vlayer_display_set = Gui.define("vlayer_display_set")
:draw(function(_, parent, name)
local vlayer_set = parent.add{ type = "flow", direction = "vertical", name = name }
local disp = Gui.elements.scroll_table(vlayer_set, 400, 2, "disp")
vlayer_gui_display_item_solar_name(disp)
vlayer_gui_display_item_solar_count(disp)
vlayer_gui_display_item_accumulator_name(disp)
vlayer_gui_display_item_accumulator_count(disp)
vlayer_gui_display_signal_surface_area_name(disp)
vlayer_gui_display_signal_surface_area_count(disp)
vlayer_gui_display_signal_sustained_name(disp)
vlayer_gui_display_signal_sustained_count(disp)
vlayer_gui_display_signal_production_name(disp)
vlayer_gui_display_signal_production_count(disp)
vlayer_gui_display_signal_capacity_name(disp)
vlayer_gui_display_signal_capacity_count(disp)
return vlayer_set
end)
local function vlayer_gui_list_refresh(player)
local container = Gui.get_left_element(vlayer_container, player)
local disp = container.frame["vlayer_st_2"].disp.table
local target = disp[vlayer_gui_control_type.name].selected_index
local full_list = {}
if target then
local interface = vlayer.get_interfaces()[vlayer_control_type_list[target]]
for i = 1, vlayer.get_interface_counts()[vlayer_control_type_list[target]], 1 do
local entity = assert(interface[i])
local entity_position = entity.position
table.insert(full_list, i .. " X " .. entity_position.x .. " Y " .. entity_position.y)
end
disp[vlayer_gui_control_list.name].items = full_list
end
end
--- A drop down list filter by this type
-- @element vlayer_gui_control_type
vlayer_gui_control_type = Gui.define("vlayer_gui_control_type")
:draw{
type = "drop-down",
name = Gui.from_name,
items = { { "vlayer.control-type-energy" }, { "vlayer.control-type-circuit" }, { "vlayer.control-type-storage-input" }, { "vlayer.control-type-storage-output" } },
selected_index = 1,
}:style{
width = 200,
}:on_selection_state_changed(function(def, player, element)
vlayer_gui_list_refresh(player)
end)
--- A drop down list to see the exact item to remove
-- @element vlayer_gui_control_list
vlayer_gui_control_list = Gui.define("vlayer_gui_control_list")
:draw{
type = "drop-down",
name = Gui.from_name,
}:style{
width = 200,
}
--- A button to refresh the remove list
-- @element vlayer_gui_control_refresh
local vlayer_gui_control_refresh = Gui.define("vlayer_gui_control_refresh")
:draw{
type = "button",
name = Gui.from_name,
caption = { "vlayer.control-refresh" },
}:style{
width = 200,
}:on_click(function(def, player, element)
vlayer_gui_list_refresh(player)
end)
--- A button to check if the item is the one wanted to remove
-- @element vlayer_gui_control_see
local vlayer_gui_control_see = Gui.define("vlayer_gui_control_see")
:draw{
type = "button",
name = Gui.from_name,
caption = { "vlayer.control-see" },
}:style{
width = 200,
}:on_click(function(def, player, element, event)
local target = assert(element.parent)[vlayer_gui_control_type.name].selected_index
local n = assert(element.parent)[vlayer_gui_control_list.name].selected_index
if target and vlayer_control_type_list[target] and n > 0 then
local i = vlayer.get_interfaces()
local entity = i[vlayer_control_type_list[target]][n]
if entity and entity.valid then
player.set_controller{ type = defines.controllers.remote, position = entity.position, surface = entity.surface }
player.print{ "vlayer.result-interface-location", { "vlayer.control-type-" .. vlayer_control_type_list[target]:gsub("_", "-") }, pos_to_gps_string(entity.position, entity.surface.name) }
end
end
end)
--- A button used to build the vlayer interface
-- @element vlayer_gui_control_build
local vlayer_gui_control_build = Gui.define("vlayer_gui_control_build")
:draw{
type = "button",
name = Gui.from_name,
caption = { "vlayer.control-build" },
}:style{
width = 200,
}:on_click(function(def, player, element)
if SelectArea:stop(player) then
player.print{ "exp_util.selection_exit", { "vlayer.main-tooltip" } }
else
SelectArea:start(player)
player.print{ "exp_util.selection_enter", { "vlayer.main-tooltip" } }
end
vlayer_gui_list_refresh(player)
end)
--- A button used to remove the vlayer interface
-- @element vlayer_gui_control_remove
local vlayer_gui_control_remove = Gui.define("vlayer_gui_control_remove")
:draw{
type = "button",
name = Gui.from_name,
caption = { "vlayer.control-remove" },
}:style{
width = 200,
}:on_click(function(def, player, element)
local target = assert(element.parent)[vlayer_gui_control_type.name].selected_index
local n = assert(element.parent)[vlayer_gui_control_list.name].selected_index
if target and vlayer_control_type_list[target] and n > 0 then
local i = vlayer.get_interfaces()
if i and i[vlayer_control_type_list[target]] then
local entity = assert(i[vlayer_control_type_list[target]][n])
local interface_type, interface_surface, interface_position = vlayer.remove_interface(entity.surface, entity.position)
if interface_type then
game.print{ "vlayer.interface-result", player.name, pos_to_gps_string(interface_position, interface_surface.name), { "vlayer.result-remove" }, { "vlayer.control-type-" .. interface_type } }
end
end
end
vlayer_gui_list_refresh(player)
end)
--- A vertical flow containing all the control buttons
-- @element vlayer_control_set
local vlayer_control_set = Gui.define("vlayer_control_set")
:draw(function(_, parent, name)
local player = Gui.get_player(parent)
local vlayer_set = parent.add{ type = "flow", direction = "vertical", name = name }
local disp = Gui.elements.scroll_table(vlayer_set, 400, 2, "disp")
vlayer_gui_control_type(disp)
vlayer_gui_control_list(disp)
vlayer_gui_control_refresh(disp)
vlayer_gui_control_see(disp)
local b = vlayer_gui_control_build(disp)
local r = vlayer_gui_control_remove(disp)
local v = Roles.player_has_permission(player, "exp_scenario.gui.vlayer_edit")
b.visible = v
r.visible = v
return vlayer_set
end)
--- The main container for the vlayer gui
-- @element vlayer_container
vlayer_container = Gui.define("vlayer_container")
:draw(function(definition, parent)
local container = Gui.elements.container(parent, 400)
vlayer_display_set(container, "vlayer_st_1")
vlayer_control_set(container, "vlayer_st_2")
return container.parent
end)
--- Add the element to the left flow with a toolbar button
Gui.add_left_element(vlayer_container, false)
Gui.toolbar.create_button{
name = "vlayer_toggle",
left_element = vlayer_container,
sprite = "entity/solar-panel",
tooltip = { "vlayer.main-tooltip" },
visible = function(player, element)
return Roles.player_has_permission(player, "exp_scenario.gui.vlayer")
end
}
--- Update the visibly of the buttons based on a players roles
local function role_update_event(event)
local player = game.players[event.player_index]
local visible = Roles.player_has_permission(player, "exp_scenario.gui.vlayer_edit")
local container = Gui.get_left_element(vlayer_container, player)
local disp = container.frame["vlayer_st_2"].disp.table
disp[vlayer_gui_control_build.name].visible = visible
disp[vlayer_gui_control_remove.name].visible = visible
end
Event.add(Roles.events.on_player_roles_changed, role_update_event)
Event.on_nth_tick(config.update_tick_gui, function(_)
local stats = vlayer.get_statistics()
local items = vlayer.get_items()
local items_alloc = vlayer.get_allocated_items()
local vlayer_display = {
[vlayer_gui_display_item_solar_count.name] = {
val = (items_alloc["solar-panel"] / math.max(items["solar-panel"], 1)),
cap = format_number(items_alloc["solar-panel"], false) .. " / " .. format_number(items["solar-panel"], false),
},
[vlayer_gui_display_item_accumulator_count.name] = {
val = (items_alloc["accumulator"] / math.max(items["accumulator"], 1)),
cap = format_number(items_alloc["accumulator"], false) .. " / " .. format_number(items["accumulator"], false),
},
[vlayer_gui_display_signal_surface_area_count.name] = {
val = (stats.total_surface_area / math.max(stats.surface_area, 1)),
cap = format_number(stats.remaining_surface_area)
},
[vlayer_gui_display_signal_sustained_count.name] = {
val = (stats.energy_sustained / math.max(stats.energy_total_production, 1)),
cap = format_energy(stats.energy_sustained, "W") .. " / " .. format_energy(stats.energy_total_production, "W")
},
[vlayer_gui_display_signal_production_count.name] = {
val = (stats.energy_production / math.max(stats.energy_max, 1)),
cap = format_energy(stats.energy_production, "W") .. " / " .. format_energy(stats.energy_max, "W"),
},
[vlayer_gui_display_signal_capacity_count.name] = {
val = (stats.energy_storage / math.max(stats.energy_capacity, 1)),
cap = format_energy(stats.energy_storage, "J") .. " / " .. format_energy(stats.energy_capacity, "J"),
},
}
for _, player in pairs(game.connected_players) do
local container = Gui.get_left_element(vlayer_container, player)
local disp = container.frame["vlayer_st_1"].disp.table
for k, v in pairs(vlayer_display) do
disp[k].caption = v.cap
if v.val then
disp[k].value = v.val
end
end
end
end)
-909
View File
@@ -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)
+9 -1
View File
@@ -1,12 +1,20 @@
import { BaseControllerPlugin, InstanceRecord } from "@clusterio/controller";
import { BaseControllerPlugin, InstanceRecord, type Controller } from "@clusterio/controller";
import * as lib from "@clusterio/lib";
import * as messages from "./messages.js";
import * as path from "node:path";
const loaded = new WeakMap<Controller, ControllerPlugin>();
export class ControllerPlugin extends BaseControllerPlugin {
roleMeta!: lib.SubscribableDatastore<messages.RoleMetaRecord>;
/** The plugin loaded on a controller, for the exp_scenario seed. */
static get(controller: Controller) {
return loaded.get(controller);
}
async init() {
loaded.set(this.controller, this);
const databaseDirectory = this.controller.config.get("controller.database_directory");
this.roleMeta = new lib.SubscribableDatastore(
+3 -3
View File
@@ -5,7 +5,7 @@ import * as lib from "@clusterio/lib";
import { ControlContext, SectionHeader, useAccount, notifyErrorHandler } from "@clusterio/web_ui";
import { RoleColor, RoleMetaRecord, RoleMetaUpdateRequest } from "../../messages.js";
import type { WebPlugin } from "..";
import { useRoles } from "..";
const MS_PER_HOUR = 3600000;
@@ -25,13 +25,13 @@ type RoleFormValues = {
* The name, description and permissions of a role are owned by clusterio, this
* only covers the properties which only mean something in game.
*/
export default function RoleProperties(props: { plugin: WebPlugin, role?: lib.Role }) {
export default function RoleProperties(props: { role?: lib.Role }) {
const control = useContext(ControlContext);
const account = useAccount();
const [roles] = useRoles();
const [form] = Form.useForm<RoleFormValues>();
const [applying, setApplying] = useState(false);
const [roles] = props.plugin.useRoles();
const record = props.role ? roles.get(props.role.id) : undefined;
const meta = record?.meta;
const canUpdate = account.hasPermission("core.role.update");
+12 -14
View File
@@ -1,24 +1,22 @@
import React, { useCallback, useSyncExternalStore } from "react";
import { BaseWebPlugin } from "@clusterio/web_ui";
import { WebPluginContext } from "@clusterio/web_ui";
import * as lib from "@clusterio/lib";
import * as messages from "../messages.js";
import RoleProperties from "./components/RoleProperties";
export class WebPlugin extends BaseWebPlugin {
roles = new lib.MapSubscriber(messages.RoleUpdatedEvent, this.control);
let roles: lib.MapSubscriber<messages.RoleUpdatedEvent> | undefined;
async init() {
// The core components pass a role and the plugin, which componentExtra
// does not carry in its type
this.componentExtra = {
RoleViewPage: RoleProperties as React.ComponentType,
};
}
export function useRoles() {
const subscribe = useCallback((cb: () => void) => roles!.subscribe(cb), []);
return useSyncExternalStore(subscribe, () => roles!.getSnapshot());
}
useRoles() {
const subscribe = useCallback((cb: () => void) => this.roles.subscribe(cb), []);
return useSyncExternalStore(subscribe, () => this.roles.getSnapshot());
}
export default function registerPlugin(context: WebPluginContext) {
context.control.hooks.extensionComponents.attach("exp_roles", () => ({
RoleViewPage: RoleProperties,
}));
roles = new lib.MapSubscriber(messages.RoleUpdatedEvent, context.control);
}
+6 -6
View File
@@ -1,9 +1,9 @@
import * as lib from "@clusterio/lib";
import { BaseControllerPlugin } from "@clusterio/controller";
import { RoleMetaRecord, type ControllerPlugin as RolesPlugin } from "@expcluster/roles";
import {
GroupRecord, GroupPermissions, RoleMappingRecord, type ControllerPlugin as GroupsPlugin,
} from "@expcluster/permission-groups";
import { RoleMetaRecord } from "@expcluster/roles";
import { GroupRecord, GroupPermissions, RoleMappingRecord } from "@expcluster/permission-groups";
import { ControllerPlugin as RolesPlugin } from "@expcluster/roles/dist/node/controller.js";
import { ControllerPlugin as GroupsPlugin } from "@expcluster/permission-groups/dist/node/controller.js";
import * as messages from "./messages.js";
import { SeedRole, SeedGroup, seedRoles, seedGroups, flattenSeedPermissions } from "./seed.js";
@@ -19,8 +19,8 @@ export class ControllerPlugin extends BaseControllerPlugin {
* permissions, groups which already exist by name are reset to the seed.
*/
async handleSeedRequest() {
const rolesPlugin = this.controller.plugins.get("exp_roles") as RolesPlugin | undefined;
const groupsPlugin = this.controller.plugins.get("exp_groups") as GroupsPlugin | undefined;
const rolesPlugin = RolesPlugin.get(this.controller);
const groupsPlugin = GroupsPlugin.get(this.controller);
if (!rolesPlugin || !groupsPlugin) {
throw new lib.RequestError("Seeding requires the exp_roles and exp_groups plugins");
}
+2 -2
View File
@@ -3,7 +3,7 @@ Adds a virtual layer to store power to save space.
]]
local Commands = require("modules/exp_commands")
local vlayer = require("modules.exp_legacy.modules.control.vlayer")
local Vlayer = require("modules/exp_scenario/control/vlayer")
--- @class ExpCommand_vlayer.commands
local commands = {}
@@ -14,7 +14,7 @@ commands.vlayer = Commands.new("vlayer-info", { "exp-commands_vlayer.description
:register(function(player)
local index = 3
local response = { "", "exp-commands_vlayer.title" } --- @type LocalisedString
for title, value in pairs(vlayer.get_circuits()) do
for title, value in pairs(Vlayer.get_circuits()) do
response[index] = { "exp-commands_vlayer.result", title, value }
index = index + 1
end
+3
View File
@@ -67,6 +67,7 @@ add(require("modules/exp_scenario/control/roles"))
add(require("modules/exp_scenario/control/spawn_area"))
add(require("modules/exp_scenario/control/spectate"))
add(require("modules/exp_scenario/control/station_auto_name"))
add(require("modules/exp_scenario/control/vlayer"))
--- Guis
add(require("modules/exp_scenario/gui/autofill"))
@@ -83,5 +84,7 @@ 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/vlayer"))
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"))
-3
View File
@@ -9,9 +9,6 @@ Roles.define_permission_trigger("exp_scenario.player.admin", function(player, st
player.admin = state
end)
Roles.define_permission_trigger("exp_scenario.player.spectator", function(player, state)
player.spectator = state
end)
return {
events = {
+673
View File
@@ -0,0 +1,673 @@
--[[-- Control - Virtual Layer
Stores solar panels, accumulators and landfill in a virtual layer which produces power through interface entities
]]
local ExpUtil = require("modules/exp_util")
local Storage = require("modules/exp_util/storage")
local config = require("modules.exp_legacy.config.vlayer")
local floor = math.floor
local min = math.min
local max = math.max
local mega = 1000000
--- @alias ExpScenario_Vlayer.InterfaceType "energy" | "circuit" | "storage_input" | "storage_output"
--- @class ExpScenario_Vlayer.Interfaces
--- @field energy LuaEntity[]
--- @field circuit LuaEntity[]
--- @field storage_input LuaEntity[]
--- @field storage_output LuaEntity[]
--- @class ExpScenario_Vlayer.Properties
--- @field total_surface_area number Tiles provided by landfill
--- @field used_surface_area number Tiles taken by allocated items
--- @field total_production number MW of every solar panel stored, allocated or not
--- @field production number MW of the allocated solar panels
--- @field discharge number MW the allocated accumulators can discharge
--- @field capacity number MJ the allocated accumulators can hold
--- @class ExpScenario_Vlayer.PowerItem
--- @field value number J released by burning one
--- @field count number
--- @class ExpScenario_Vlayer.Storage
--- @field items table<string, number> Every stored item, allocated or not
--- @field unallocated table<string, number> Items waiting for surface area
--- @field power_items table<string, ExpScenario_Vlayer.PowerItem> Fuel burned when the stored energy is low
--- @field energy number J
--- @class ExpScenario_Vlayer.Statistics
--- @field total_surface_area number
--- @field used_surface_area number
--- @field remaining_surface_area number Surface area left after the unallocated items are counted
--- @field surface_area number
--- @field production_multiplier number
--- @field energy_max number W when the sun is up
--- @field energy_production number W right now
--- @field energy_total_production number W sustained if every stored solar panel was allocated
--- @field energy_sustained number W averaged over a day
--- @field energy_capacity number J
--- @field energy_storage number J
--- @class ExpScenario_Vlayer.Data
--- @field interfaces ExpScenario_Vlayer.Interfaces
--- @field properties ExpScenario_Vlayer.Properties
--- @field storage ExpScenario_Vlayer.Storage
--- @field surface LuaSurface | table The surface the day cycle is taken from, or a copy of the config settings
--- @class ExpScenario_Vlayer
local Vlayer = {
--- @package
events = {},
--- @package
on_nth_tick = {},
}
--- The properties of every allowed item, modded items are derived from their base game equivalent
local allowed_items = {} --- @type table<string, Vlayer.ItemProperties>
for name, properties in pairs(config.allowed_items) do
allowed_items[name] = properties
end
for name, properties in pairs(config.modded_items) do
local base = config.allowed_items[properties.base_game_equivalent]
local multiplier = properties.multiplier
allowed_items[name] = {
starting_value = properties.starting_value or 0,
required_area = base.required_area or 0,
surface_area = (base.surface_area or 0) * multiplier,
production = (base.production or 0) * multiplier,
capacity = (base.capacity or 0) * multiplier,
}
end
--- @type ExpScenario_Vlayer.Data
local vlayer_data = {
interfaces = {
energy = {},
circuit = {},
storage_input = {},
storage_output = {},
},
properties = {
total_surface_area = 0,
used_surface_area = 0,
total_production = 0,
production = 0,
discharge = 0,
capacity = 0,
},
storage = {
items = {},
unallocated = {},
power_items = {},
energy = 0,
},
surface = table.deep_copy(config.surface),
}
Storage.register(vlayer_data, function(tbl)
vlayer_data = tbl
end)
--- Interface entities are neutral so players can not mine them, these are the prototypes used for each type
local interface_names = {
energy = "electric-energy-interface",
circuit = "constant-combinator",
storage_input = "storage-chest",
storage_output = "requester-chest",
} --- @type table<ExpScenario_Vlayer.InterfaceType, string>
local interface_types = {} --- @type table<string, ExpScenario_Vlayer.InterfaceType>
for interface_type, name in pairs(interface_names) do
interface_types[name] = interface_type
end
--- Statistics exposed on circuit interfaces and the signal each is sent as
local circuit_signals = {
total_surface_area = "signal-A",
used_surface_area = "signal-U",
remaining_surface_area = "signal-R",
production_multiplier = "signal-M",
energy_production = "signal-P",
energy_sustained = "signal-S",
energy_capacity = "signal-C",
energy_storage = "signal-E",
} --- @type table<string, string>
local circuit_description = {}
for stat_name, signal_name in pairs(circuit_signals) do
circuit_description[#circuit_description + 1] = string.format("[virtual-signal=%s] = %s", signal_name, stat_name:gsub("_", " "))
end
circuit_description = table.concat(circuit_description, "\n")
--- Get every stored item keyed by name, allocated or not
--- @return table<string, number>
function Vlayer.get_items()
return vlayer_data.storage.items
end
--- Get the items waiting for surface area keyed by name
--- @return table<string, number>
function Vlayer.get_unallocated_items()
return vlayer_data.storage.unallocated
end
--- Get the items which contribute to the properties keyed by name
--- @return table<string, number>
function Vlayer.get_allocated_items()
local allocated = {}
local unallocated = vlayer_data.storage.unallocated
for name, count in pairs(vlayer_data.storage.items) do
allocated[name] = count - (unallocated[name] or 0)
end
return allocated
end
--- Get the interface entities of each type, do not modify
--- @return ExpScenario_Vlayer.Interfaces
function Vlayer.get_interfaces()
return vlayer_data.interfaces
end
--- Get the signal each statistic is sent as on circuit interfaces
--- @return table<string, string>
function Vlayer.get_circuits()
return circuit_signals
end
--- Surface area left once the unallocated items have taken what they need
--- @return number
local function get_remaining_surface_area()
local properties = vlayer_data.properties
local remaining = properties.total_surface_area - properties.used_surface_area
for name, count in pairs(vlayer_data.storage.unallocated) do
remaining = remaining - allowed_items[name].required_area * count
end
return remaining
end
--- Fraction of solar production available right now, from the darkness of a real surface or the config day cycle
--- @return number
local function get_production_multiplier()
local surface = vlayer_data.surface
local multiplier = surface.solar_power_multiplier
if surface.always_day then
return multiplier
end
if surface.darkness then
local brightness = 1 - surface.darkness
if brightness < surface.min_brightness then
return 0
end
return multiplier * (brightness - surface.min_brightness) / (1 - surface.min_brightness)
end
local daytime = (game.tick % surface.ticks_per_day) / surface.ticks_per_day
surface.daytime = daytime
if daytime <= surface.dusk then
return multiplier
elseif daytime <= surface.evening then
return multiplier * (1 - (daytime - surface.dusk) / (surface.evening - surface.dusk))
elseif daytime <= surface.morning then
return 0
elseif daytime <= surface.dawn then
return multiplier * (daytime - surface.morning) / (surface.dawn - surface.morning)
end
return multiplier
end
--- Fraction of solar production averaged over a whole day, sunrise and sunset count for half
--- @return number
local function get_sustained_multiplier()
local surface = vlayer_data.surface
local multiplier = surface.solar_power_multiplier
if surface.always_day then
return multiplier
end
local day_duration = 1 - surface.dawn + surface.dusk
local sunset_duration = surface.evening - surface.dusk
local sunrise_duration = surface.dawn - surface.morning
return multiplier * (day_duration + 0.5 * (sunset_duration + sunrise_duration))
end
--- Apply the properties of an item to the layer, a negative count deallocates
--- @param name string
--- @param count number
local function allocate_item(name, count)
local item = allowed_items[name]
local properties = vlayer_data.properties
if item.production then
properties.production = properties.production + item.production * count
properties.total_production = properties.total_production + item.production * count
end
if item.capacity then
properties.capacity = properties.capacity + item.capacity * count
end
if item.discharge then
properties.discharge = properties.discharge + item.discharge * count
end
if item.surface_area then
properties.total_surface_area = properties.total_surface_area + item.surface_area * count
end
if item.required_area > 0 then
properties.used_surface_area = properties.used_surface_area + item.required_area * count
end
end
--- Count the production of items which are stored but not allocated, so the total shows what more surface area would give
--- @param name string
--- @param count number
local function count_unallocated_production(name, count)
local item = allowed_items[name]
if item.production then
vlayer_data.properties.total_production = vlayer_data.properties.total_production + item.production * count
end
end
for name, item in pairs(allowed_items) do
vlayer_data.storage.items[name] = item.starting_value
if item.required_area > 0 then
vlayer_data.storage.unallocated[name] = 0
end
if item.power then
vlayer_data.storage.power_items[name] = { value = assert(item.fuel_value) * mega, count = 0 }
end
allocate_item(name, item.starting_value)
end
--- Store an item, it is allocated straight away when there is surface area for it
--- @param name string
--- @param count number
function Vlayer.insert_item(name, count)
local item = assert(allowed_items[name], "Item not allowed in the vlayer: " .. name)
local storage = vlayer_data.storage
storage.items[name] = storage.items[name] + count
if config.unlimited_surface_area or item.required_area == 0 then
allocate_item(name, count)
return
end
local properties = vlayer_data.properties
local surplus_area = properties.total_surface_area - properties.used_surface_area
local allocated = min(count, floor(surplus_area / item.required_area))
if allocated > 0 then
allocate_item(name, allocated)
end
local unallocated = count - allocated
storage.unallocated[name] = storage.unallocated[name] + unallocated
count_unallocated_production(name, unallocated)
end
--- Take an item out, unallocated items go first and landfill can only go while nothing is using its area
--- @param name string
--- @param count number
--- @return number # The count actually removed
function Vlayer.remove_item(name, count)
local item = assert(allowed_items[name], "Item not allowed in the vlayer: " .. name)
local storage = vlayer_data.storage
local properties = vlayer_data.properties
local removed = 0
if not config.unlimited_surface_area and item.required_area > 0 then
removed = min(count, storage.unallocated[name])
storage.items[name] = storage.items[name] - removed
storage.unallocated[name] = storage.unallocated[name] - removed
count_unallocated_production(name, -removed)
count = count - removed
if count == 0 then
return removed
end
end
local deallocate = min(count, storage.items[name])
if item.surface_area and item.surface_area > 0 then
local surplus_area = properties.total_surface_area - properties.used_surface_area
deallocate = min(deallocate, floor(surplus_area / item.surface_area))
if deallocate <= 0 then
return removed
end
end
storage.items[name] = storage.items[name] - deallocate
allocate_item(name, -deallocate)
return removed + deallocate
end
--- Add or take energy, used by other modules which spend the stored energy
--- @param energy number J, negative to take
--- @return number # J stored afterwards
function Vlayer.energy_changed(energy)
local storage = vlayer_data.storage
storage.energy = storage.energy + energy
return storage.energy
end
--- Get the statistics of the layer
--- @return ExpScenario_Vlayer.Statistics
function Vlayer.get_statistics()
local properties = vlayer_data.properties
local production = properties.production * mega
local production_multiplier = get_production_multiplier()
local sustained_multiplier = get_sustained_multiplier()
local remaining_surface_area = get_remaining_surface_area()
return {
total_surface_area = properties.total_surface_area,
used_surface_area = properties.used_surface_area,
remaining_surface_area = remaining_surface_area,
surface_area = properties.total_surface_area - remaining_surface_area,
production_multiplier = production_multiplier,
energy_max = production,
energy_production = production * production_multiplier,
energy_total_production = properties.total_production * sustained_multiplier * mega,
energy_sustained = production * sustained_multiplier,
energy_capacity = properties.capacity * mega,
energy_storage = vlayer_data.storage.energy,
}
end
--- Make an entity into an interface which players can not mine or damage
--- @param interface LuaEntity
--- @param interface_type ExpScenario_Vlayer.InterfaceType
--- @param player LuaPlayer?
local function register_interface(interface, interface_type, player)
local interfaces = vlayer_data.interfaces[interface_type]
interfaces[#interfaces + 1] = interface
interface.last_user = player
interface.destructible = false
interface.minable_flag = false
end
--- Create an interface which powers the electric network it is placed in
--- @param surface LuaSurface
--- @param position MapPosition
--- @param player LuaPlayer?
--- @return LuaEntity? # Nil when the interface does not fit
function Vlayer.create_energy_interface(surface, position, player)
if not surface.can_place_entity{ name = interface_names.energy, position = position } then
return nil
end
local interface = assert(surface.create_entity{ name = interface_names.energy, position = position, force = "neutral" })
register_interface(interface, "energy", player)
interface.operable = false
interface.electric_buffer_size = 0
interface.power_production = 0
interface.power_usage = 0
interface.energy = 0
return interface
end
--- Create an interface which outputs the statistics and stored items as signals
--- @param surface LuaSurface
--- @param position MapPosition
--- @param player LuaPlayer?
--- @return LuaEntity
function Vlayer.create_circuit_interface(surface, position, player)
local interface = assert(surface.create_entity{ name = interface_names.circuit, position = position, force = "neutral" })
register_interface(interface, "circuit", player)
interface.combinator_description = circuit_description
return interface
end
--- Create an interface which stores every allowed item put into it
--- @param surface LuaSurface
--- @param position MapPosition
--- @param player LuaPlayer?
--- @return LuaEntity
function Vlayer.create_input_interface(surface, position, player)
local interface = assert(surface.create_entity{ name = interface_names.storage_input, position = position, force = "neutral" })
register_interface(interface, "storage_input", player)
-- A filter nothing can match stops robots filling it with anything
interface.storage_filter = { name = "deconstruction-planner", quality = "normal" }
return interface
end
--- Create an interface which takes stored items out to fulfil its requests
--- @param surface LuaSurface
--- @param position MapPosition
--- @param player LuaPlayer?
--- @return LuaEntity
function Vlayer.create_output_interface(surface, position, player)
local interface = assert(surface.create_entity{ name = interface_names.storage_output, position = position, force = "neutral" })
register_interface(interface, "storage_output", player)
return interface
end
--- Remove an interface, anything it holds is dropped in a chest next to it and stored energy is kept
--- @param interface LuaEntity
--- @return ExpScenario_Vlayer.InterfaceType
function Vlayer.remove_interface(interface)
local interface_type = assert(interface_types[interface.name], "Entity is not a vlayer interface: " .. interface.name)
if interface_type == "storage_input" or interface_type == "storage_output" then
ExpUtil.transfer_inventory_to_surface{
inventory = assert(interface.get_inventory(defines.inventory.chest)),
surface = interface.surface,
name = "iron-chest",
allow_creation = true,
}
elseif interface_type == "energy" then
vlayer_data.storage.energy = vlayer_data.storage.energy + interface.energy
end
table.remove_element(vlayer_data.interfaces[interface_type], interface)
interface.destroy()
return interface_type
end
--- Forget interfaces which no longer exist
--- @param interfaces LuaEntity[]
local function prune_interfaces(interfaces)
for index = #interfaces, 1, -1 do
if not interfaces[index].valid then
table.remove(interfaces, index)
end
end
end
--- Store the contents of the input interfaces
local function handle_input_interfaces()
local interfaces = vlayer_data.interfaces.storage_input
prune_interfaces(interfaces)
for _, interface in ipairs(interfaces) do
local inventory = assert(interface.get_inventory(defines.inventory.chest))
for _, item in pairs(inventory.get_contents()) do
local name = item.name
if allowed_items[name] then
-- Quality is not tracked, so higher quality is stored as extra normal items in batches of ten
local quality_level = prototypes.quality[item.quality].level
local removed, stored = 0, 0
if quality_level == 0 then
removed, stored = item.count, item.count
elseif item.count >= 10 then
local batches = floor(item.count / 10)
removed, stored = batches * 10, batches * (10 + quality_level * 3)
end
if removed > 0 then
local modded = config.modded_items[name]
if modded and config.modded_auto_downgrade then
Vlayer.insert_item(modded.base_game_equivalent, stored * modded.multiplier)
elseif vlayer_data.storage.power_items[name] then
local power_item = vlayer_data.storage.power_items[name]
power_item.count = power_item.count + stored
else
Vlayer.insert_item(name, stored)
end
inventory.remove{ name = name, count = removed, quality = item.quality }
end
end
end
end
end
--- Fill the requests of the output interfaces from storage
local function handle_output_interfaces()
local interfaces = vlayer_data.interfaces.storage_output
prune_interfaces(interfaces)
for _, interface in ipairs(interfaces) do
local inventory = assert(interface.get_inventory(defines.inventory.chest))
for _, section in ipairs(assert(interface.get_logistic_sections()).sections) do
for _, filter in pairs(section.filters) do
local name = filter.value and filter.value.name
if name and allowed_items[name] then
local wanted = min(assert(filter.min) - inventory.get_item_count(name), vlayer_data.storage.items[name])
if wanted > 0 and inventory.can_insert{ name = name, count = wanted } then
local removed = Vlayer.remove_item(name, wanted)
if removed > 0 then
inventory.insert{ name = name, count = removed, quality = "normal" }
end
end
end
end
end
end
end
--- Allocate waiting items in proportion to the surface area that has become available
local function handle_unallocated()
if config.unlimited_surface_area then return end
local unallocated_area = 0
for name, count in pairs(vlayer_data.storage.unallocated) do
unallocated_area = unallocated_area + allowed_items[name].required_area * count
end
if unallocated_area == 0 then return end
local properties = vlayer_data.properties
local surplus_area = properties.total_surface_area - properties.used_surface_area
for name, count in pairs(vlayer_data.storage.unallocated) do
local allocated = min(count, floor(count * surplus_area / unallocated_area))
if allocated > 0 then
vlayer_data.storage.unallocated[name] = count - allocated
allocate_item(name, allocated)
count_unallocated_production(name, -allocated)
end
end
end
--- Set the signals of the circuit interfaces to the statistics and stored items
local function handle_circuit_interfaces()
local interfaces = vlayer_data.interfaces.circuit
prune_interfaces(interfaces)
if #interfaces == 0 then return end
local stats = Vlayer.get_statistics()
for _, interface in ipairs(interfaces) do
local control = interface.get_or_create_control_behavior() --[[@as LuaConstantCombinatorControlBehavior]]
if control.sections_count == 0 then
control.add_section()
end
local section = assert(control.sections[1])
-- Slots are cleared first because an item can not be set in two slots at once
for index = section.filters_count, 1, -1 do
section.clear_slot(index)
end
local index = 1
for stat_name, signal_name in pairs(circuit_signals) do
local value = stats[stat_name] --[[@as number]]
if stat_name:find("energy") then
value = value / mega
elseif stat_name == "production_multiplier" then
value = value * 10000
end
section.set_slot(index, { value = { type = "virtual", name = signal_name, quality = "normal" }, min = floor(value) })
index = index + 1
end
for name, count in pairs(vlayer_data.storage.items) do
if count > 0 and prototypes.item[name] then
section.set_slot(index, { value = { type = "item", name = name, quality = "normal" }, min = count })
index = index + 1
end
end
end
end
--- Produce energy, share it between the energy interfaces, and burn fuel when storage runs low
local function handle_energy_interfaces()
local storage = vlayer_data.storage
local properties = vlayer_data.properties
local production = properties.production * mega * (config.update_tick_energy / 60)
storage.energy = storage.energy + floor(production * get_production_multiplier())
local interfaces = vlayer_data.interfaces.energy
prune_interfaces(interfaces)
if #interfaces > 0 then
local available_energy = storage.energy
for _, interface in ipairs(interfaces) do
available_energy = available_energy + interface.energy
end
local discharge_rate = 2 * (production + properties.discharge * mega) / #interfaces
local fill_to = min(discharge_rate, floor(available_energy / #interfaces))
for _, interface in ipairs(interfaces) do
interface.electric_buffer_size = max(discharge_rate, interface.energy) -- A smaller buffer would lose energy
local delta = fill_to - interface.energy
storage.energy = storage.energy - delta
interface.energy = interface.energy + delta
end
end
local capacity = properties.capacity * mega
if not config.unlimited_capacity and storage.energy > capacity then
storage.energy = capacity
return
end
for _, power_item in pairs(storage.power_items) do
local max_burn = capacity / 2 - storage.energy
if power_item.count > 0 and max_burn > 0 then
local burn = min(power_item.count, floor(max_burn / power_item.value))
storage.energy = storage.energy + burn * power_item.value
power_item.count = power_item.count - burn
end
end
end
--- Take the day cycle from the mimic surface when it exists, otherwise from the config
local function update_surface()
if config.mimic_surface then
local surface = game.get_surface(config.mimic_surface)
if surface then
vlayer_data.surface = surface
return
end
end
-- The config copy never has an index, so this avoids replacing it every time a surface changes
if not vlayer_data.surface.index then
vlayer_data.surface = table.deep_copy(config.surface)
end
end
--- Move items between the interfaces and storage
local function update_storage()
handle_input_interfaces()
handle_output_interfaces()
handle_unallocated()
end
--- Update the signals and energy of the interfaces
local function update_energy()
handle_circuit_interfaces()
handle_energy_interfaces()
end
local e = defines.events
Vlayer.on_init = update_surface -- The default surface exists before on_surface_created can fire
Vlayer.events[e.on_surface_created] = update_surface
Vlayer.events[e.on_surface_renamed] = update_surface
Vlayer.events[e.on_surface_imported] = update_surface
Vlayer.on_nth_tick[config.update_tick_storage] = update_storage
Vlayer.on_nth_tick[config.update_tick_energy] = update_energy
return Vlayer
+3 -3
View File
@@ -5,7 +5,7 @@ Adds a gui that allows players to apply various bonuses
local Gui = require("modules/exp_gui")
local Roles = require("modules/exp_roles")
local config = require("modules/exp_legacy/config/bonus")
local vlayer = require("modules/exp_legacy/modules/control/vlayer")
local Vlayer = require("modules/exp_scenario/control/vlayer")
local format_number = require("util").format_number
--- @class ExpGui_PlayerBonus.elements
@@ -486,7 +486,7 @@ end
--- Apply periodic bonus to a player
--- @param player LuaPlayer
local function apply_personal_battery_recharge(player)
local available_energy = vlayer.get_statistics()["energy_storage"]
local available_energy = Vlayer.get_statistics().energy_storage
if available_energy <= 0 then
return -- No power to give
end
@@ -508,7 +508,7 @@ local function apply_personal_battery_recharge(player)
local energy_to_give = math.min(math.floor(equipment.max_energy - equipment.energy), available_energy, recharge_amount)
equipment.energy = equipment.energy + energy_to_give
recharge_amount = recharge_amount - energy_to_give
available_energy = vlayer.energy_changed(-energy_to_give)
available_energy = Vlayer.energy_changed(-energy_to_give)
end
end
end
+531
View File
@@ -0,0 +1,531 @@
--[[-- Gui - Virtual Layer
Shows what the virtual layer holds and lets players build and remove its interfaces
]]
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.vlayer")
local Vlayer = require("modules/exp_scenario/control/vlayer")
local format_number = require("util").format_number
local Selection = require("modules/exp_util/selection")
local SelectArea = Selection.connect("ExpGui_Vlayer")
local floor = math.floor
local ceil = math.ceil
local max = math.max
local format_string = string.format
--- @class ExpGui_Vlayer.elements
local Elements = {}
--- Interface types in the order the type dropdown lists them
local interface_types = { "energy", "circuit", "storage_input", "storage_output" } --- @type ExpScenario_Vlayer.InterfaceType[]
--- Locale key of each interface type
local type_captions = {} --- @type table<ExpScenario_Vlayer.InterfaceType, LocalisedString>
for _, interface_type in ipairs(interface_types) do
type_captions[interface_type] = { "exp-gui_vlayer.type-" .. interface_type:gsub("_", "-") }
end
--- Statistics shown as progress bars, in display order
--- @type { name: string, caption: LocalisedString, tooltip: LocalisedString }[]
local stats = {}
for _, name in ipairs{ "solar", "accumulator", "surface-area", "sustained", "production", "capacity" } do
stats[#stats + 1] = {
name = name,
caption = { "exp-gui_vlayer.caption-stat-" .. name },
tooltip = { "exp-gui_vlayer.tooltip-stat-" .. name },
}
end
local energy_suffixes = {
{ 1e15, "P" }, { 1e12, "T" }, { 1e9, "G" }, { 1e6, "M" }, { 1e3, "k" },
}
--- Format an amount of energy with an SI prefix, util.format_number uses B for giga
--- @param amount number
--- @param unit string
--- @return string
local function format_energy(amount, unit)
if amount < 1 then
return "0 " .. unit
end
for _, suffix in ipairs(energy_suffixes) do
if amount >= suffix[1] then
return format_string("%.1f %s%s", amount / suffix[1], suffix[2], unit)
end
end
return format_number(floor(amount), false) .. " " .. unit
end
--- Expand an area to whole tiles
--- @param area BoundingBox
--- @return BoundingBox
local function expand_area(area)
return {
left_top = { x = floor(area.left_top.x), y = floor(area.left_top.y) },
right_bottom = { x = ceil(area.right_bottom.x), y = ceil(area.right_bottom.y) },
}
end
--- Rich text link to a position
--- @param position MapPosition
--- @param surface LuaSurface
--- @return string
local function gps_string(position, surface)
return format_string("[gps=%.1f,%.1f,%s]", position.x, position.y, surface.name)
end
--- Label naming a statistic
--- @class ExpGui_Vlayer.elements.stat_label: ExpElement
--- @overload fun(parent: LuaGuiElement, caption: LocalisedString, tooltip: LocalisedString): LuaGuiElement
Elements.stat_label = Gui.define("vlayer/stat_label")
:draw{
type = "label",
caption = Gui.from_argument(1),
tooltip = Gui.from_argument(2),
style = "heading_2_label",
}
:style{
width = 200,
} --[[@as any]]
--- Bar showing a statistic as a fraction with the values as its caption
--- @class ExpGui_Vlayer.elements.stat_bar: ExpElement
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.stat_bar = Gui.define("vlayer/stat_bar")
:draw{
type = "progressbar",
caption = "",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
}
:style{
width = 200,
font = "heading-2",
} --[[@as any]]
--- @class ExpGui_Vlayer.elements.stat_bar.display_data
--- @field value number Fraction the bar is filled
--- @field caption string
--- Refresh a bar
--- @param stat_bar LuaGuiElement
--- @param display_data ExpGui_Vlayer.elements.stat_bar.display_data
function Elements.stat_bar.refresh(stat_bar, display_data)
stat_bar.value = display_data.value
stat_bar.caption = display_data.caption
end
--- Table of a label and bar for each statistic
--- @class ExpGui_Vlayer.elements.stats_table: ExpElement
--- @field data table<LuaGuiElement, table<string, LuaGuiElement>> Bars keyed by statistic name
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.stats_table = Gui.define("vlayer/stats_table")
:track_all_elements()
:draw(function(def, parent)
--- @cast def ExpGui_Vlayer.elements.stats_table
local stats_table = Gui.elements.scroll_table(parent, 400, 2, "stats")
local stat_bars = {}
for _, stat in ipairs(stats) do
Elements.stat_label(stats_table, stat.caption, stat.tooltip)
stat_bars[stat.name] = Elements.stat_bar(stats_table)
end
def.data[stats_table] = stat_bars
return stats_table
end) --[[@as any]]
--- Calculate the display data of every statistic, the same for every player
--- @return table<string, ExpGui_Vlayer.elements.stat_bar.display_data>
function Elements.stats_table.calculate_display_data()
local statistics = Vlayer.get_statistics()
local items = Vlayer.get_items()
local allocated = Vlayer.get_allocated_items()
return {
solar = {
value = allocated["solar-panel"] / max(items["solar-panel"], 1),
caption = format_number(allocated["solar-panel"], false) .. " / " .. format_number(items["solar-panel"], false),
},
accumulator = {
value = allocated["accumulator"] / max(items["accumulator"], 1),
caption = format_number(allocated["accumulator"], false) .. " / " .. format_number(items["accumulator"], false),
},
["surface-area"] = {
value = statistics.total_surface_area / max(statistics.surface_area, 1),
caption = format_number(statistics.remaining_surface_area, false),
},
sustained = {
value = statistics.energy_sustained / max(statistics.energy_total_production, 1),
caption = format_energy(statistics.energy_sustained, "W") .. " / " .. format_energy(statistics.energy_total_production, "W"),
},
production = {
value = statistics.energy_production / max(statistics.energy_max, 1),
caption = format_energy(statistics.energy_production, "W") .. " / " .. format_energy(statistics.energy_max, "W"),
},
capacity = {
value = statistics.energy_storage / max(statistics.energy_capacity, 1),
caption = format_energy(statistics.energy_storage, "J") .. " / " .. format_energy(statistics.energy_capacity, "J"),
},
}
end
--- Refresh every bar of a table
--- @param stats_table LuaGuiElement
--- @param display_data table<string, ExpGui_Vlayer.elements.stat_bar.display_data>
function Elements.stats_table.refresh(stats_table, display_data)
for name, stat_bar in pairs(Elements.stats_table.data[stats_table]) do
Elements.stat_bar.refresh(stat_bar, display_data[name])
end
end
--- Refresh the tables of every online player
function Elements.stats_table.refresh_online()
local display_data = Elements.stats_table.calculate_display_data()
for _, stats_table in Elements.stats_table:online_elements() do
Elements.stats_table.refresh(stats_table, display_data)
end
end
--- @class ExpGui_Vlayer.elements.type_dropdown.element_data
--- @field controls LuaGuiElement
--- Dropdown which picks the type of interface to build or list
--- @class ExpGui_Vlayer.elements.type_dropdown: ExpElement
--- @field data table<LuaGuiElement, ExpGui_Vlayer.elements.type_dropdown.element_data>
--- @overload fun(parent: LuaGuiElement, controls: LuaGuiElement): LuaGuiElement
Elements.type_dropdown = Gui.define("vlayer/type_dropdown")
:draw{
type = "drop-down",
items = { type_captions.energy, type_captions.circuit, type_captions.storage_input, type_captions.storage_output },
selected_index = 1,
}
:style{
width = 200,
}
:element_data{
controls = Gui.from_argument(1),
}
:on_selection_state_changed(function(def, _, type_dropdown)
--- @cast def ExpGui_Vlayer.elements.type_dropdown
Elements.controls.refresh_interfaces(def.data[type_dropdown].controls)
end) --[[@as any]]
--- Get the selected interface type
--- @param type_dropdown LuaGuiElement
--- @return ExpScenario_Vlayer.InterfaceType
function Elements.type_dropdown.get_selected(type_dropdown)
return (assert(interface_types[type_dropdown.selected_index]))
end
--- Dropdown listing the interfaces of the selected type by position
--- @class ExpGui_Vlayer.elements.interface_dropdown: ExpElement
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.interface_dropdown = Gui.define("vlayer/interface_dropdown")
:draw{
type = "drop-down",
}
:style{
width = 200,
} --[[@as any]]
--- Refresh the listed interfaces
--- @param interface_dropdown LuaGuiElement
--- @param interfaces LuaEntity[]
function Elements.interface_dropdown.refresh(interface_dropdown, interfaces)
local items = {}
for index, interface in ipairs(interfaces) do
local position = interface.position
items[index] = format_string("%d X %.1f Y %.1f", index, position.x, position.y)
end
interface_dropdown.items = items
end
--- Get the selected interface
--- @param interface_dropdown LuaGuiElement
--- @param interfaces LuaEntity[]
--- @return LuaEntity?
function Elements.interface_dropdown.get_selected(interface_dropdown, interfaces)
local interface = interfaces[interface_dropdown.selected_index]
if interface and interface.valid then
return interface
end
return nil
end
--- @class ExpGui_Vlayer.elements.control_button.element_data
--- @field controls LuaGuiElement
--- Button which refreshes the listed interfaces
--- @class ExpGui_Vlayer.elements.refresh_button: ExpElement
--- @field data table<LuaGuiElement, ExpGui_Vlayer.elements.control_button.element_data>
--- @overload fun(parent: LuaGuiElement, controls: LuaGuiElement): LuaGuiElement
Elements.refresh_button = Gui.define("vlayer/refresh_button")
:draw{
type = "button",
caption = { "exp-gui_vlayer.caption-refresh" },
}
:style{
width = 200,
}
:element_data{
controls = Gui.from_argument(1),
}
:on_click(function(def, _, refresh_button)
--- @cast def ExpGui_Vlayer.elements.refresh_button
Elements.controls.refresh_interfaces(def.data[refresh_button].controls)
end) --[[@as any]]
--- Button which opens the map at the selected interface
--- @class ExpGui_Vlayer.elements.view_button: ExpElement
--- @field data table<LuaGuiElement, ExpGui_Vlayer.elements.control_button.element_data>
--- @overload fun(parent: LuaGuiElement, controls: LuaGuiElement): LuaGuiElement
Elements.view_button = Gui.define("vlayer/view_button")
:draw{
type = "button",
caption = { "exp-gui_vlayer.caption-view" },
}
:style{
width = 200,
}
:element_data{
controls = Gui.from_argument(1),
}
:on_click(function(def, player, view_button)
--- @cast def ExpGui_Vlayer.elements.view_button
local controls = def.data[view_button].controls
local interface_type, interface = Elements.controls.get_selected_interface(controls)
if not interface then return end
player.set_controller{ type = defines.controllers.remote, position = interface.position, surface = interface.surface }
player.print{ "exp-gui_vlayer.message-located", type_captions[interface_type], gps_string(interface.position, interface.surface) }
end) --[[@as any]]
--- Button which toggles selecting an area to build an interface in
--- @class ExpGui_Vlayer.elements.build_button: ExpElement
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.build_button = Gui.define("vlayer/build_button")
:track_all_elements()
:draw{
type = "button",
caption = { "exp-gui_vlayer.caption-build" },
}
:style{
width = 200,
}
:on_click(function(_, player)
if SelectArea:stop(player) then
player.print{ "exp_util.selection_exit", { "exp-gui_vlayer.selection-name" } }
else
SelectArea:start(player)
player.print{ "exp_util.selection_enter", { "exp-gui_vlayer.selection-name" } }
end
end) --[[@as any]]
--- Button which removes the selected interface
--- @class ExpGui_Vlayer.elements.remove_button: ExpElement
--- @field data table<LuaGuiElement, ExpGui_Vlayer.elements.control_button.element_data>
--- @overload fun(parent: LuaGuiElement, controls: LuaGuiElement): LuaGuiElement
Elements.remove_button = Gui.define("vlayer/remove_button")
:track_all_elements()
:draw{
type = "button",
caption = { "exp-gui_vlayer.caption-remove" },
}
:style{
width = 200,
}
:element_data{
controls = Gui.from_argument(1),
}
:on_click(function(def, player, remove_button)
--- @cast def ExpGui_Vlayer.elements.remove_button
local controls = def.data[remove_button].controls
local _, interface = Elements.controls.get_selected_interface(controls)
if not interface then return end
local position, surface = interface.position, interface.surface
local interface_type = Vlayer.remove_interface(interface)
game.print{ "exp-gui_vlayer.message-removed", player.name, type_captions[interface_type], gps_string(position, surface) }
Elements.controls.refresh_interfaces_online()
end) --[[@as any]]
--- @class ExpGui_Vlayer.elements.controls.element_data
--- @field type_dropdown LuaGuiElement
--- @field interface_dropdown LuaGuiElement
--- Table of the dropdowns and buttons used to manage interfaces
--- @class ExpGui_Vlayer.elements.controls: ExpElement
--- @field data table<LuaGuiElement, ExpGui_Vlayer.elements.controls.element_data>
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.controls = Gui.define("vlayer/controls")
:track_all_elements()
:draw(function(def, parent)
--- @cast def ExpGui_Vlayer.elements.controls
local controls = Gui.elements.scroll_table(parent, 400, 2, "controls")
local player = Gui.get_player(parent)
local can_edit = Roles.player_has_permission(player, "exp_scenario.gui.vlayer_edit")
local type_dropdown = Elements.type_dropdown(controls, controls)
local interface_dropdown = Elements.interface_dropdown(controls)
Elements.refresh_button(controls, controls)
Elements.view_button(controls, controls)
Elements.build_button(controls).visible = can_edit
Elements.remove_button(controls, controls).visible = can_edit
def.data[controls] = {
type_dropdown = type_dropdown,
interface_dropdown = interface_dropdown,
}
Elements.controls.refresh_interfaces(controls)
return controls
end) --[[@as any]]
--- Get the selected interface type
--- @param controls LuaGuiElement
--- @return ExpScenario_Vlayer.InterfaceType
function Elements.controls.get_selected_type(controls)
return Elements.type_dropdown.get_selected(Elements.controls.data[controls].type_dropdown)
end
--- Get the selected interface and its type
--- @param controls LuaGuiElement
--- @return ExpScenario_Vlayer.InterfaceType, LuaEntity?
function Elements.controls.get_selected_interface(controls)
local elements = Elements.controls.data[controls]
local interface_type = Elements.type_dropdown.get_selected(elements.type_dropdown)
local interfaces = Vlayer.get_interfaces()[interface_type]
return interface_type, Elements.interface_dropdown.get_selected(elements.interface_dropdown, interfaces)
end
--- Refresh the listed interfaces to match the selected type
--- @param controls LuaGuiElement
function Elements.controls.refresh_interfaces(controls)
local elements = Elements.controls.data[controls]
local interface_type = Elements.type_dropdown.get_selected(elements.type_dropdown)
Elements.interface_dropdown.refresh(elements.interface_dropdown, Vlayer.get_interfaces()[interface_type])
end
--- Refresh the listed interfaces of every online player, used when an interface is built or removed
function Elements.controls.refresh_interfaces_online()
for _, controls in Elements.controls:online_elements() do
Elements.controls.refresh_interfaces(controls)
end
end
--- Refresh whether a player can see the edit buttons
--- @param player LuaPlayer
function Elements.controls.refresh_permissions(player)
local can_edit = Roles.player_has_permission(player, "exp_scenario.gui.vlayer_edit")
for _, build_button in Elements.build_button:tracked_elements(player) do
build_button.visible = can_edit
end
for _, remove_button in Elements.remove_button:tracked_elements(player) do
remove_button.visible = can_edit
end
end
--- Container added to the left gui flow
--- @class ExpGui_Vlayer.elements.container: ExpElement
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.container = Gui.define("vlayer/container")
:draw(function(_, parent)
local container = Gui.elements.container(parent, 400)
local stats_table = Elements.stats_table(container)
Elements.stats_table.refresh(stats_table, Elements.stats_table.calculate_display_data())
Elements.controls(container)
return Gui.elements.container.get_root_element(container)
end) --[[@as any]]
--- Add the element to the left flow with a toolbar button
Gui.add_left_element(Elements.container, false)
Gui.toolbar.create_button{
name = "toggle_vlayer",
left_element = Elements.container,
sprite = "entity/solar-panel",
tooltip = { "exp-gui_vlayer.tooltip-main" },
visible = function(player, element)
return Roles.player_has_permission(player, "exp_scenario.gui.vlayer")
end
}
--- Replace the one marker entity in a selected area with an interface of the type the player picked
--- @param event EventData.on_player_selected_area
local function on_area_selected(event)
local player = Gui.get_player(event)
local surface = event.surface
local area = expand_area(event.area)
local force = player.force --[[@as LuaForce]]
local interface_type = "energy" --- @type ExpScenario_Vlayer.InterfaceType
for _, controls in Elements.controls:tracked_elements(player) do
interface_type = Elements.controls.get_selected_type(controls)
end
-- Platforms have no chests, so a combinator marks the spot once the research allows it
local marker_name = "steel-chest"
if config.power_on_space and surface.platform and interface_type == "energy" then
local research = config.power_on_space_research
if force.technologies[research.name].level < research.level then
player.print({ "exp-gui_vlayer.message-research", research.name, research.level }, Colors.orange_red)
return
end
marker_name = "constant-combinator"
end
local markers = surface.find_entities_filtered{ area = area, name = marker_name, force = force }
if #markers == 0 then
player.print({ "exp-gui_vlayer.message-no-chest" }, Colors.orange_red)
return
elseif #markers > 1 then
player.print({ "exp-gui_vlayer.message-multiple", type_captions[interface_type] }, Colors.orange_red)
return
end
local marker = assert(markers[1])
if marker.name == "steel-chest" and not assert(marker.get_inventory(defines.inventory.chest)).is_empty() then
player.print({ "exp-gui_vlayer.message-chest-not-empty" }, Colors.orange_red)
return
end
if #Vlayer.get_interfaces()[interface_type] >= config.interface_limit[interface_type] then
player.print({ "exp-gui_vlayer.message-limit", type_captions[interface_type] }, Colors.orange_red)
return
end
local position = marker.position
marker.destroy()
if interface_type == "energy" then
if not Vlayer.create_energy_interface(surface, position, player) then
player.print({ "exp-gui_vlayer.message-no-space" }, Colors.orange_red)
return
end
elseif interface_type == "circuit" then
Vlayer.create_circuit_interface(surface, position, player)
elseif interface_type == "storage_input" then
Vlayer.create_input_interface(surface, position, player)
else
Vlayer.create_output_interface(surface, position, player)
end
game.print{ "exp-gui_vlayer.message-built", player.name, type_captions[interface_type], gps_string(position, surface) }
Elements.controls.refresh_interfaces_online()
end
SelectArea:on_selection(on_area_selected)
--- Refresh the edit buttons as the permissions of a player may have changed
--- @param event EventData.ExpRoles.on_player_roles_changed
local function refresh_player_permissions(event)
Elements.controls.refresh_permissions(Gui.get_player(event))
end
return {
elements = Elements,
events = {
[Roles.events.on_player_roles_changed] = refresh_player_permissions,
},
on_nth_tick = {
[config.update_tick_gui] = Elements.stats_table.refresh_online,
}
}
File diff suppressed because it is too large. Load diff
+72 -8
View File
@@ -490,6 +490,70 @@ caption-create-footer=Create task
caption-edit-footer=Edit task
caption-view-footer=Task details
[exp-gui_vlayer]
tooltip-main=Virtual Layer
selection-name=vlayer
caption-stat-solar=[img=entity/solar-panel] Solar Panels
tooltip-stat-solar=Solar panels in use out of those stored
caption-stat-accumulator=[img=entity/accumulator] Accumulators
tooltip-stat-accumulator=Accumulators in use out of those stored
caption-stat-surface-area=[virtual-signal=signal-R] Remaining Surface
tooltip-stat-surface-area=Remaining surface area, insert landfill to increase it
caption-stat-sustained=[virtual-signal=signal-S] Sustained Production
tooltip-stat-sustained=Power production averaged over a day, out of what every stored solar panel could give
caption-stat-production=[virtual-signal=signal-P] Current Production
tooltip-stat-production=Power production right now, out of the production in full daylight
caption-stat-capacity=[virtual-signal=signal-E] Battery Charge
tooltip-stat-capacity=Stored energy out of the accumulator capacity
type-energy=Energy
type-circuit=Circuit
type-storage-input=Storage Input
type-storage-output=Storage Output
caption-refresh=Refresh List
caption-view=View Interface
caption-build=Build Interface
caption-remove=Remove Interface
message-located=The __1__ interface is at __2__
message-built=__1__ built a __2__ interface at __3__
message-removed=__1__ removed a __2__ interface at __3__
message-no-chest=No steel chest was selected
message-chest-not-empty=The steel chest is not empty
message-multiple=Cannot build a __1__ interface, more than one steel chest was selected
message-limit=Cannot build a __1__ interface, the limit has been reached
message-no-space=Cannot build an energy interface here, there is not enough space
message-research=You need to research [technology=__1__] to level __2__ to use the vlayer on a space platform
[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.
@@ -507,23 +571,23 @@ reply-evolution=Current evolution factor is __1__
reply-magic=Fear the admin magic (ノ゚∀゚)ノ⌒・*:.。. .。.:*・゜゚・*☆
reply-aids=≖ ‿ ≖ Fear the aids of a public server ≖ ‿ ≖
reply-riot=(admins) ┬┴┬┴┤ᵒ_ᵒ)├┬┴┬┴ ‹ ‹\(´ω` )/››‹‹\ (  ´)/››‹‹\ ( ´ω`)/›› (rest of server)
reply-loops=NO LOOPS; LOOPS ARE BAD; JUST NO LOOPS!!!!!; IF YOU MAKE A LOOP.... IT WILL NOT END WELL!!!!!!!
reply-loops=NO LOOPS; LOOPS ARE BAD; JUST NO LOOPS!!!; IF YOU MAKE A LOOP… IT WILL NOT END WELL!!!
reply-lenny=( ͡° ͜ʖ ͡°)
reply-hodor=Hodor
reply-popcorn-1=Heating the oil and waiting for the popping sound...
reply-popcorn-1=Heating the oil and waiting for the popping sound…
reply-popcorn-2=__1__ your popcorn is finished. Lean backwards and watch the drama unfold.
reply-snaps-1=Pouring the glasses and finding the correct song book...
reply-snaps-2=Singing a song...🎤🎶
reply-snaps-1=Pouring the glasses and finding the correct song book…
reply-snaps-2=Singing a song…🎤🎶
reply-snaps-3=schkål, my friends!
reply-cocktail-1= 🍸 Inintiating mind reading unit... 🍸
reply-cocktail-1= 🍸 Inintiating mind reading unit… 🍸
reply-cocktail-2= 🍸 Mixing favourite ingredients of __1__ 🍸
reply-cocktail-3=🍸 __1__ your cocktail is done.🍸
reply-coffee-1= ☕ Boiling the water and grinding the coffee beans... ☕
reply-coffee-1= ☕ Boiling the water and grinding the coffee beans… ☕
reply-coffee-2= ☕ __1__ we ran out of coffe beans! Have some tea instead. ☕
reply-pizza-1= 🍕 Finding nearest pizza supplier... 🍕
reply-pizza-1= 🍕 Finding nearest pizza supplier… 🍕
reply-pizza-2= 🍕 Figuring out the favourite pizza of __1__ 🍕
reply-pizza-3= 🍕 __1__ your pizza is here! 🍕
reply-tea-1= ☕ Boiling the water... ☕
reply-tea-1= ☕ Boiling the water… ☕
reply-tea-2= ☕ __1__ your tea is done! ☕
reply-mead-1= Filling the drinking horn
reply-mead-2= Skål!
+101 -13
View File
@@ -84,7 +84,7 @@ home-get=你的回程傳送點在 __1__ 上, x: __2__ y: __3__
description=把用戶送監而禁止其行動。
arg-player=用戶
arg-reason=原因
jailed=__1__ 已被 __2__ ,因 __3__ 而被禁止行動。
jailed=__1__ 已被 __2__ ,因 __3__ 而被禁止行動
already-jailed=__1__ 已被禁止行動。
[exp-commands_unjail]
@@ -129,6 +129,8 @@ already-protected=本地區已受保護。
protected-area=本地區現已受保護。
unprotected-area=本地區現不受保護。
repeat-offence=__1__ 在 [gps=__3__,__4__] 拆除了 __2__
enter-entity-selection = Entered entity selection, select entites to protect, hold shift to remove protection.
enter-area-selection = Entered area selection, select areas to protect, hold shift to remove protection.
[exp-commands_tag-protection]
description=切換管理員標記模式,新標記只能由管理員編輯。
@@ -153,6 +155,7 @@ fluid-in=你每秒需要 __1__ 來做 [fluid=__2__]。
item-out=結果: __1__ [item=__2__] 每秒。
fluid-out=結果: __1__ [fluid=__2__] 每秒。
machines=你需要 __1__ 部同樣的機器。
machine-count = And you will need __1__ machines (with the same speed as this one) for this.
[exp-commands_repair]
selection-name=維修
@@ -235,9 +238,9 @@ response=__1__ 把 __2__ 個火車設置為自動模式。
description-teleport=將一名玩家傳送到另一名玩家。
description-bring=將一名玩家傳送到你身邊。
description-goto=將你傳送到玩家身邊。
description-spawn=傳送到出生點。
description-spawn=傳送到出生點
arg-player-teleport=傳送的玩家,若沒有目標,你會被傳送到這。
arg-player-from=傳送的玩家
arg-player-from=傳送的玩家。
arg-player-to=傳送的玩家目標
unavailable=沒找到空間進行傳送
same-player=沒辦法傳送到自己
@@ -389,6 +392,7 @@ data-statistics=數據
data-required=需要
data-misc=雜項
data-format=__1____2__
welcome-title = Welcome to __1__
[exp-gui_research-milestones]
tooltip-main=研究介面
@@ -480,16 +484,81 @@ tooltip-edit-none=現沒有被人修改
caption-create-footer=加入工作流程
caption-edit-footer=修改工作流程
caption-view-footer=工作流程細節
tooltip-confirm = Save changes (minimum of 5 characters long)
[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-gui_vlayer]
tooltip-main=Vlayer 介面
selection-name=vlayer
caption-stat-solar=[img=entity/solar-panel] 太陽能板
tooltip-stat-solar=使用中的太陽能板數量 / 儲存的太陽能板數量
caption-stat-accumulator=[img=entity/accumulator] 蓄電池
tooltip-stat-accumulator=使用中的蓄電池數量 / 儲存的蓄電池數量
caption-stat-surface-area=[virtual-signal=signal-R] 餘下土地
tooltip-stat-surface-area=餘下土地。 放入堆填來增加
caption-stat-sustained=[virtual-signal=signal-S] 持續產能
tooltip-stat-sustained=持續電力產能
caption-stat-production=[virtual-signal=signal-P] 現時產能
tooltip-stat-production=現時電力產能
caption-stat-capacity=[virtual-signal=signal-E] 電池電量
tooltip-stat-capacity=現時電池電量 / 電池最大容量
type-energy=電力
type-circuit=回路
type-storage-input=放入箱
type-storage-output=提取箱
caption-refresh=刷新列表
caption-view=查看介面
caption-build=建造介面
caption-remove=拆除介面
message-located=__1__ 介面在 __2__
message-built=__1__ 在 __3__ 建造了 __2__ 介面
message-removed=__1__ 在 __3__ 拆除了 __2__ 介面
message-no-chest=沒檢測到鋼箱
message-chest-not-empty=鋼箱不是空的
message-multiple=無法建造 __1__ 介面,檢測到多個鋼箱
message-limit=無法建造 __1__ 介面,超出最大限制
message-no-space=無法在此建造電力介面,空間不足
message-research=你要研究 [technology=__1__] 在 __2__ 級 才可使用太空平台 vlayer 功能。
[exp_afk-kick]
kick-message=因地圖中沒有活躍玩家,所以所有人都已被請離。
[exp_chat-auto-reply]
chat-reply=[Compilatron] __1__
chat-disallowed=你沒有權限使用這個指令。
chat-disallowed=You can't use global chat commands
reply-online=現在有 __1__ 人在線
reply-players=這張地圖上共有__1__名用戶
reply-dev=Cooldude2606 只是本場境的開發者
reply-dev=Cooldude2606 is a dev for this server and makes the scenario and is not a factorio dev.
reply-softmod=這裹用了自設情境。
reply-clusterio=Clusterio is a factorio server hosting platform allowing internet enabled mods.
reply-blame=責怪 __1__ 吧!
@@ -498,23 +567,23 @@ reply-evolution=現在敵人進化度為 __1__
reply-magic=Fear the admin magic (ノ゚∀゚)ノ⌒・*:.。. .。.:*・゜゚・*☆
reply-aids=≖ ‿ ≖ Fear the aids of a public server ≖ ‿ ≖
reply-riot=(admins) ┬┴┬┴┤ᵒ_ᵒ)├┬┴┬┴ ‹ ‹\(´ω` )/››‹‹\ (  ´)/››‹‹\ ( ´ω`)/›› (rest of server)
reply-loops=架設迴旋處最終後果都不好!
reply-loops=NO LOOPS; LOOPS ARE BAD; JUST NO LOOPS!!!; IF YOU MAKE A LOOP… IT WILL NOT END WELL!!!
reply-lenny=( ͡° ͜ʖ ͡°)
reply-hodor=Hodor
reply-popcorn-1=Heating the oil and waiting for the popping sound...
reply-popcorn-1=Heating the oil and waiting for the popping sound…
reply-popcorn-2=__1__ your popcorn is finished. Lean backwards and watch the drama unfold.
reply-snaps-1=Pouring the glasses and finding the correct song book...
reply-snaps-2=Singing a song...🎤🎶
reply-snaps-1=Pouring the glasses and finding the correct song book…
reply-snaps-2=Singing a song…🎤🎶
reply-snaps-3=schkål, my friends!
reply-cocktail-1= 🍸 Inintiating mind reading unit... 🍸
reply-cocktail-1= 🍸 Inintiating mind reading unit… 🍸
reply-cocktail-2= 🍸 Mixing favourite ingredients of __1__ 🍸
reply-cocktail-3=🍸 __1__ your cocktail is done.🍸
reply-coffee-1= ☕ Boiling the water and grinding the coffee beans... ☕
reply-coffee-1= ☕ Boiling the water and grinding the coffee beans… ☕
reply-coffee-2= ☕ __1__ we ran out of coffe beans! Have some tea instead. ☕
reply-pizza-1= 🍕 Finding nearest pizza supplier... 🍕
reply-pizza-1= 🍕 Finding nearest pizza supplier… 🍕
reply-pizza-2= 🍕 Figuring out the favourite pizza of __1__ 🍕
reply-pizza-3= 🍕 __1__ your pizza is here! 🍕
reply-tea-1= ☕ Boiling the water... ☕
reply-tea-1= ☕ Boiling the water… ☕
reply-tea-2= ☕ __1__ your tea is done! ☕
reply-mead-1= Filling the drinking horn
reply-mead-2= Skål!
@@ -546,3 +615,22 @@ chat-jailed=__1__ 因被多次拆除受保護物體而被禁止行動。請等
[exp_report-jail]
chat-jailed=__1__ 因被多次舉報而被禁止行動。請等候管理員作出下一步處理。
[exp-gui_player-list]
main-tooltip = Player List
open-action-bar = Options
close-action-bar = Close Options
reason-confirm = Confirm Reason
reason-entry = Enter Reason
goto-player = Goto player
bring-player = Bring player
report-player = Report player
jail-player = Jail player
kick-player = Kick player
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
[exp_spectate]
follow-label = Following __1__.\nClick here or press escape to stop following.
+101 -13
View File
@@ -84,7 +84,7 @@ home-get=你的回程傳送點在 __1__ 上, x: __2__ y: __3__
description=把用戶送監而禁止其行動。
arg-player=用戶
arg-reason=原因
jailed=__1__ 已被 __2__ ,因 __3__ 而被禁止行動。
jailed=__1__ 已被 __2__ ,因 __3__ 而被禁止行動
already-jailed=__1__ 已被禁止行動。
[exp-commands_unjail]
@@ -129,6 +129,8 @@ already-protected=本地區已受保護。
protected-area=本地區現已受保護。
unprotected-area=本地區現不受保護。
repeat-offence=__1__ 在 [gps=__3__,__4__] 拆除了 __2__
enter-entity-selection = Entered entity selection, select entites to protect, hold shift to remove protection.
enter-area-selection = Entered area selection, select areas to protect, hold shift to remove protection.
[exp-commands_tag-protection]
description=切換管理員標記模式,新標記只能由管理員編輯。
@@ -153,6 +155,7 @@ fluid-in=你每秒需要 __1__ 來做 [fluid=__2__]。
item-out=結果: __1__ [item=__2__] 每秒。
fluid-out=結果: __1__ [fluid=__2__] 每秒。
machines=你需要 __1__ 部同樣的機器。
machine-count = And you will need __1__ machines (with the same speed as this one) for this.
[exp-commands_repair]
selection-name=維修
@@ -235,9 +238,9 @@ response=__1__ 把 __2__ 個火車設置為自動模式。
description-teleport=將一名玩家傳送到另一名玩家。
description-bring=將一名玩家傳送到你身邊。
description-goto=將你傳送到玩家身邊。
description-spawn=傳送到出生點。
description-spawn=傳送到出生點
arg-player-teleport=傳送的玩家,若沒有目標,你會被傳送到這。
arg-player-from=傳送的玩家
arg-player-from=傳送的玩家。
arg-player-to=傳送的玩家目標
unavailable=沒找到空間進行傳送
same-player=沒辦法傳送到自己
@@ -389,6 +392,7 @@ data-statistics=數據
data-required=需要
data-misc=雜項
data-format=__1____2__
welcome-title = Welcome to __1__
[exp-gui_research-milestones]
tooltip-main=研究介面
@@ -480,16 +484,81 @@ tooltip-edit-none=現沒有被人修改
caption-create-footer=加入工作流程
caption-edit-footer=修改工作流程
caption-view-footer=工作流程細節
tooltip-confirm = Save changes (minimum of 5 characters long)
[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-gui_vlayer]
tooltip-main=Vlayer 介面
selection-name=vlayer
caption-stat-solar=[img=entity/solar-panel] 太陽能板
tooltip-stat-solar=使用中的太陽能板數量 / 儲存的太陽能板數量
caption-stat-accumulator=[img=entity/accumulator] 蓄電池
tooltip-stat-accumulator=使用中的蓄電池數量 / 儲存的蓄電池數量
caption-stat-surface-area=[virtual-signal=signal-R] 餘下土地
tooltip-stat-surface-area=餘下土地。 放入堆填來增加
caption-stat-sustained=[virtual-signal=signal-S] 持續產能
tooltip-stat-sustained=持續電力產能
caption-stat-production=[virtual-signal=signal-P] 現時產能
tooltip-stat-production=現時電力產能
caption-stat-capacity=[virtual-signal=signal-E] 電池電量
tooltip-stat-capacity=現時電池電量 / 電池最大容量
type-energy=電力
type-circuit=回路
type-storage-input=放入箱
type-storage-output=提取箱
caption-refresh=刷新列表
caption-view=查看介面
caption-build=建造介面
caption-remove=拆除介面
message-located=__1__ 介面在 __2__
message-built=__1__ 在 __3__ 建造了 __2__ 介面
message-removed=__1__ 在 __3__ 拆除了 __2__ 介面
message-no-chest=沒檢測到鋼箱
message-chest-not-empty=鋼箱不是空的
message-multiple=無法建造 __1__ 介面,檢測到多個鋼箱
message-limit=無法建造 __1__ 介面,超出最大限制
message-no-space=無法在此建造電力介面,空間不足
message-research=你要研究 [technology=__1__] 在 __2__ 級 才可使用太空平台 vlayer 功能。
[exp_afk-kick]
kick-message=因地圖中沒有活躍玩家,所以所有人都已被請離。
[exp_chat-auto-reply]
chat-reply=[Compilatron] __1__
chat-disallowed=你沒有權限使用這個指令。
chat-disallowed=You can't use global chat commands
reply-online=現在有 __1__ 人在線
reply-players=這張地圖上共有__1__名用戶
reply-dev=Cooldude2606 只是本場境的開發者
reply-dev=Cooldude2606 is a dev for this server and makes the scenario and is not a factorio dev.
reply-softmod=這裹用了自設情境。
reply-clusterio=Clusterio is a factorio server hosting platform allowing internet enabled mods.
reply-blame=責怪 __1__ 吧!
@@ -498,23 +567,23 @@ reply-evolution=現在敵人進化度為 __1__
reply-magic=Fear the admin magic (ノ゚∀゚)ノ⌒・*:.。. .。.:*・゜゚・*☆
reply-aids=≖ ‿ ≖ Fear the aids of a public server ≖ ‿ ≖
reply-riot=(admins) ┬┴┬┴┤ᵒ_ᵒ)├┬┴┬┴ ‹ ‹\(´ω` )/››‹‹\ (  ´)/››‹‹\ ( ´ω`)/›› (rest of server)
reply-loops=架設迴旋處最終後果都不好!
reply-loops=NO LOOPS; LOOPS ARE BAD; JUST NO LOOPS!!!; IF YOU MAKE A LOOP… IT WILL NOT END WELL!!!
reply-lenny=( ͡° ͜ʖ ͡°)
reply-hodor=Hodor
reply-popcorn-1=Heating the oil and waiting for the popping sound...
reply-popcorn-1=Heating the oil and waiting for the popping sound…
reply-popcorn-2=__1__ your popcorn is finished. Lean backwards and watch the drama unfold.
reply-snaps-1=Pouring the glasses and finding the correct song book...
reply-snaps-2=Singing a song...🎤🎶
reply-snaps-1=Pouring the glasses and finding the correct song book…
reply-snaps-2=Singing a song…🎤🎶
reply-snaps-3=schkål, my friends!
reply-cocktail-1= 🍸 Inintiating mind reading unit... 🍸
reply-cocktail-1= 🍸 Inintiating mind reading unit… 🍸
reply-cocktail-2= 🍸 Mixing favourite ingredients of __1__ 🍸
reply-cocktail-3=🍸 __1__ your cocktail is done.🍸
reply-coffee-1= ☕ Boiling the water and grinding the coffee beans... ☕
reply-coffee-1= ☕ Boiling the water and grinding the coffee beans… ☕
reply-coffee-2= ☕ __1__ we ran out of coffe beans! Have some tea instead. ☕
reply-pizza-1= 🍕 Finding nearest pizza supplier... 🍕
reply-pizza-1= 🍕 Finding nearest pizza supplier… 🍕
reply-pizza-2= 🍕 Figuring out the favourite pizza of __1__ 🍕
reply-pizza-3= 🍕 __1__ your pizza is here! 🍕
reply-tea-1= ☕ Boiling the water... ☕
reply-tea-1= ☕ Boiling the water… ☕
reply-tea-2= ☕ __1__ your tea is done! ☕
reply-mead-1= Filling the drinking horn
reply-mead-2= Skål!
@@ -546,3 +615,22 @@ chat-jailed=__1__ 因被多次拆除受保護物體而被禁止行動。請等
[exp_report-jail]
chat-jailed=__1__ 因被多次舉報而被禁止行動。請等候管理員作出下一步處理。
[exp-gui_player-list]
main-tooltip = Player List
open-action-bar = Options
close-action-bar = Close Options
reason-confirm = Confirm Reason
reason-entry = Enter Reason
goto-player = Goto player
bring-player = Bring player
report-player = Report player
jail-player = Jail player
kick-player = Kick player
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
[exp_spectate]
follow-label = Following __1__.\nClick here or press escape to stop following.
+4 -5
View File
@@ -42,7 +42,6 @@ async function startPlugin(t2, { withPlugins = true } = {}) {
for (const [name, Plugin] of [["exp_roles", RolesPlugin], ["exp_groups", GroupsPlugin]]) {
const plugin = new Plugin({ name }, controller, undefined, logger);
await plugin.init();
controller.plugins.set(name, plugin);
}
}
@@ -65,7 +64,7 @@ t.test("class ControllerPlugin", t2 => {
t2.test(".handleSeedRequest() creates the roles and reuses them by name", async t3 => {
const { plugin, controller } = await startPlugin(t3);
const rolesPlugin = controller.plugins.get("exp_roles");
const rolesPlugin = RolesPlugin.get(controller);
await plugin.handleSeedRequest();
t3.strictSame(controller.roles.size, seedRoles.length, "every seed role exists");
@@ -80,7 +79,7 @@ t.test("class ControllerPlugin", t2 => {
t2.test(".handleSeedRequest() creates the groups and resets them by name", async t3 => {
const { plugin, controller } = await startPlugin(t3);
const groupsPlugin = controller.plugins.get("exp_groups");
const groupsPlugin = GroupsPlugin.get(controller);
await plugin.handleSeedRequest();
t3.strictSame(groupsPlugin.groups.size, seedGroups.length, "every seed group exists");
@@ -101,7 +100,7 @@ t.test("class ControllerPlugin", t2 => {
t2.test(".handleSeedRequest() maps each role onto its group by rank", async t3 => {
const { plugin, controller } = await startPlugin(t3);
const groupsPlugin = controller.plugins.get("exp_groups");
const groupsPlugin = GroupsPlugin.get(controller);
await plugin.handleSeedRequest();
const mapped = seedRoles.filter(role => role.group !== undefined);
@@ -123,7 +122,7 @@ t.test("class ControllerPlugin", t2 => {
t2.test(".handleSeedRequest() reuses mappings and keeps clear of other mappings", async t3 => {
const { plugin, controller } = await startPlugin(t3);
const groupsPlugin = controller.plugins.get("exp_groups");
const groupsPlugin = GroupsPlugin.get(controller);
groupsPlugin.roleMappings.set(new RoleMappingRecord(99, new Set([0, 1]), 5, 2, true));
await plugin.handleSeedRequest();