mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-09-28 19:24:00 +00:00
Compare commits
179
Commits
26ee6cee64
...
translate
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4eeb9b6b36 | ||
|
|
1627fcba8f | ||
|
|
06fc1086e3 | ||
|
|
7fd66c6ac7 | ||
|
|
797d7501c7 | ||
|
|
30f1b5ea75 | ||
|
|
24d2f76a35 | ||
|
|
2048de6244 | ||
|
|
5487976a21 | ||
|
|
23ee68c50a | ||
|
|
910285ee2d | ||
|
|
51786c5df9 | ||
|
|
3a9155697a | ||
|
|
24efb82d0f | ||
|
|
0854a2b7d5 | ||
|
|
f805a1e760 | ||
|
|
142d296a02 | ||
|
|
58c2ad6fb5 | ||
|
|
bad80ed77a | ||
|
|
397033e8a6 | ||
|
|
1de50da0be | ||
|
|
4d976a9e35 | ||
|
|
e14e27c158 | ||
|
|
01bc4cd14d | ||
|
|
09fe5d5f8d | ||
|
|
c4cd7d690d | ||
|
|
98545f1828 | ||
|
|
42f177a58e | ||
|
|
e1966dec9c | ||
|
|
bbf036b14b | ||
|
|
702016b3e2 | ||
|
|
ac7cdbe01a | ||
|
|
d5c0c1005f | ||
|
|
c12f0a8f89 | ||
|
|
00253de11f | ||
|
|
f961a8d82e | ||
|
|
ce7a6ae1bf | ||
|
|
37eff4b3e9 | ||
|
|
a59b2a8414 | ||
|
|
8014cb1e77 | ||
|
|
0c52532c05 | ||
|
|
2e53e4668d | ||
|
|
810269971b | ||
|
|
0d0f08bcdc | ||
|
|
72cd203c25 | ||
|
|
f8ccc87edc | ||
|
|
08fe6b688e | ||
|
|
59df8bdff3 | ||
|
|
574a0a4b52 | ||
|
|
3218351db6 | ||
|
|
e6552a39be | ||
|
|
7adcdde9dc | ||
|
|
24d36db601 | ||
|
|
59a0e0be13 | ||
|
|
e79822b26e | ||
|
|
8a15745cf8 | ||
|
|
bea8d46c82 | ||
|
|
78a533a6f6 | ||
|
|
48d06ee996 | ||
|
|
7034f17b5d | ||
|
|
eb715cc176 | ||
|
|
ad9a072661 | ||
|
|
893334b273 | ||
|
|
fd38816ff6 | ||
|
|
5890155331 | ||
|
|
1e861c5c0b | ||
|
|
d495a345d6 | ||
|
|
619ed4b39f | ||
|
|
f907007d14 | ||
|
|
2fece14a45 | ||
|
|
f5656025ee | ||
|
|
76ecc37d3f | ||
|
|
300196bc76 | ||
|
|
b5bbf73332 | ||
|
|
231c3a9ea0 | ||
|
|
3c0b5069fc | ||
|
|
f9ce3c4145 | ||
|
|
0b31a70801 | ||
|
|
aca8bc79ae | ||
|
|
d44381bac1 | ||
|
|
7d8de94c2f | ||
|
|
b75e283fc0 | ||
|
|
981eed7f34 | ||
|
|
d25fc42538 | ||
|
|
7bb874f73f | ||
|
|
ce3b490554 | ||
|
|
bad00a9cef | ||
|
|
a4213dadf5 | ||
|
|
3f46055b70 | ||
|
|
1868616a4d | ||
|
|
f39da0c585 | ||
|
|
5aeafa0e11 | ||
|
|
b07052307d | ||
|
|
4745b6de0b | ||
|
|
19fd1eabbe | ||
|
|
d78ae40c5d | ||
|
|
1387b8a2db | ||
|
|
589b876865 | ||
|
|
164672602d | ||
|
|
fd2f5a9589 | ||
|
|
8ae1144585 | ||
|
|
2032cadc1b | ||
|
|
ecf236d831 | ||
|
|
ab89b9feca | ||
|
|
0bb92966eb | ||
|
|
c233a9ab06 | ||
|
|
b9cfbc9036 | ||
|
|
90f32699c7 | ||
|
|
c6da1fd9ac | ||
|
|
70471c5085 | ||
|
|
a324388bc8 | ||
|
|
486151723e | ||
|
|
1943d8743d | ||
|
|
dcba1eef1c | ||
|
|
4255522c57 | ||
|
|
fcfa174794 | ||
|
|
bbd3b7c1f2 | ||
|
|
e852abc309 | ||
|
|
a07be7b7ed | ||
|
|
50daa81a4a | ||
|
|
c4f9c97130 | ||
|
|
4fe0e9c0a6 | ||
|
|
27b1a34a83 | ||
|
|
1f3689a0ac | ||
|
|
7890540a3e | ||
|
|
8b3c437847 | ||
|
|
95bb8ac304 | ||
|
|
7be2f8d490 | ||
|
|
9019be307c | ||
|
|
3b6163451c | ||
|
|
147c586c4b | ||
|
|
f4766cd20e | ||
|
|
9755ec8e3a | ||
|
|
6b99378fa2 | ||
|
|
aa75a43aa9 | ||
|
|
71ab3e7be4 | ||
|
|
cb89c08c9f | ||
|
|
3a46ec4fa4 | ||
|
|
31c18c7851 | ||
|
|
3a8d83f981 | ||
|
|
21779988f0 | ||
|
|
d548cebc17 | ||
|
|
8190b2165d | ||
|
|
2a1a5c952b | ||
|
|
aa7fe73bb0 | ||
|
|
9863412177 | ||
|
|
7c9d47b6db | ||
|
|
8c351a8411 | ||
|
|
dc015bc609 | ||
|
|
f3a04fd26c | ||
|
|
df129b5e9a | ||
|
|
068548889b | ||
|
|
817069fbad | ||
|
|
4bd5394268 | ||
|
|
05cb831b30 | ||
|
|
05b586fb8c | ||
|
|
b1c0f2535b | ||
|
|
c669e36bec | ||
|
|
8f6d6e1caa | ||
|
|
0ead1a51f9 | ||
|
|
1fcf351566 | ||
|
|
2c7b88bb81 | ||
|
|
7318fc3602 | ||
|
|
b5cae0b0b7 | ||
|
|
bb788f33da | ||
|
|
2248f23a8d | ||
|
|
a521cdd901 | ||
|
|
230c835103 | ||
|
|
dca5086ce3 | ||
|
|
7ddfa8e735 | ||
|
|
01af5dbc68 | ||
|
|
90d63952d3 | ||
|
|
b64e109bf1 | ||
|
|
538714acdb | ||
|
|
73e53a656d | ||
|
|
cfa9cc980a | ||
|
|
620161938f | ||
|
|
1a561f9d1d | ||
|
|
6cbc4e5cfb |
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/EmmyLuaLs/emmylua-analyzer-rust/refs/heads/main/crates/emmylua_code_analysis/resources/schema.json",
|
||||||
|
"runtime": {
|
||||||
|
"version": "Lua5.2",
|
||||||
|
"requirePattern": [ "?", "?.lua" ]
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"$comment-moduleMap": "Replaces the fmtk --clusterio-modules plugin, rewrites each file's own module path rather than the require string",
|
||||||
|
"$comment-library": "clusterio is outside this repo, set CLUSTERIO to your checkout. The factorio typedefs come from .luarc.json, which the factoriomod-debug extension manages",
|
||||||
|
"library": [ "$CLUSTERIO/packages/host" ],
|
||||||
|
"ignoreDir": [ ".github", "web", "dist", "factorio" ],
|
||||||
|
"ignoreGlobs": [ "**/node_modules/**", "**/dist/**", "**/campaigns/**" ],
|
||||||
|
"moduleMap": [
|
||||||
|
{ "pattern": "^([^.]+)\\.module\\.module_exports$", "replace": "modules.$1" },
|
||||||
|
{ "pattern": "^([^.]+)\\.module\\.(.+)$", "replace": "modules.$1.$2" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"doc": {
|
||||||
|
"privateName": [ "__*" ]
|
||||||
|
},
|
||||||
|
"$comment-disable": "Carried over from the luals config, which had these as editor only rather than checked in ci",
|
||||||
|
"$comment-unnecessary-if": "Disabled by the config fmtk generates for itself, the api union types make it unreliable",
|
||||||
|
"diagnostics": {
|
||||||
|
"globals": [ "__DebugAdapter", "__Profiler" ],
|
||||||
|
"disable": [
|
||||||
|
"assign-type-mismatch",
|
||||||
|
"param-type-mismatch",
|
||||||
|
"preferred-local-alias",
|
||||||
|
"unnecessary-assert",
|
||||||
|
"unnecessary-if",
|
||||||
|
"unused"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+52
-34
@@ -1,76 +1,94 @@
|
|||||||
name: FMTK Lint
|
name: Lua Lint
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
EMMYLUA_CHECK_VERSION: "0.24.0"
|
||||||
|
CLUSTERIO: ${{ github.workspace }}/.clusterio
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- name: Checkout clusterio modules
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: clusterio/clusterio
|
||||||
|
path: .clusterio
|
||||||
|
sparse-checkout: packages/host/modules
|
||||||
|
sparse-checkout-cone-mode: false
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: "pnpm"
|
- name: Generate Factorio typedefs
|
||||||
- name: Install FMTK
|
|
||||||
run: |
|
run: |
|
||||||
pnpm install factoriomod-debug
|
pnpm install factoriomod-debug
|
||||||
pnpm exec fmtk luals-addon
|
pnpm exec fmtk luals-addon "$RUNNER_TEMP"
|
||||||
jq '.["workspace.library"] += ["${{ github.workspace }}/factorio/library"] | .["runtime.plugin"] = "${{ github.workspace }}/factorio/plugin.lua"' .luarc.json > temp.luarc.json
|
- name: Install emmylua_check
|
||||||
jq -s '.[0] * .[1].settings' temp.luarc.json ${{ github.workspace }}/factorio/config.json > check.luarc.json
|
|
||||||
- name: Install LuaLS
|
|
||||||
run: |
|
run: |
|
||||||
wget https://github.com/LuaLS/lua-language-server/releases/download/3.15.0/lua-language-server-3.15.0-linux-x64.tar.gz -q -O lusls.tar.gz
|
curl -fsSL "https://github.com/EmmyLuaLs/emmylua-analyzer-rust/releases/download/${EMMYLUA_CHECK_VERSION}/emmylua_check-linux-x64.tar.gz" \
|
||||||
mkdir luals && tar -xf lusls.tar.gz -C luals && rm lusls.tar.gz
|
| tar -xz -C "$RUNNER_TEMP"
|
||||||
- name: Run Lint Report
|
- name: Run Lint Report
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
./luals/bin/lua-language-server --check=. --logpath=. --configpath=check.luarc.json --checklevel=Information --check_out_path=check.json
|
# The typedefs are generated per run, clusterio comes from CLUSTERIO above
|
||||||
|
jq -n --arg lib "$RUNNER_TEMP/factorio/library" \
|
||||||
|
'{ workspace: { library: [ $lib ], ignoreGlobs: [ ".clusterio/**" ] } }' \
|
||||||
|
> "$RUNNER_TEMP/ci.emmyrc.json"
|
||||||
|
|
||||||
|
# Exits non zero on error severity, so the gate below is used instead
|
||||||
|
"$RUNNER_TEMP/emmylua_check" . --config ".emmyrc.json,$RUNNER_TEMP/ci.emmyrc.json" \
|
||||||
|
--output-format json --output check.json || true
|
||||||
|
|
||||||
# Credit to https://github.com/Krealle/luals-check-action/blob/main/action.yml
|
# Credit to https://github.com/Krealle/luals-check-action/blob/main/action.yml
|
||||||
# Although some minor fixes were needed
|
# Although some minor fixes were needed
|
||||||
|
|
||||||
# Format and print the messages (would be nice to format as a table, but that's too complex for jq)
|
# Format and print the messages (would be nice to format as a table, but that's too complex for jq)
|
||||||
cat check.json | \
|
jq -r --arg root "$PWD/" \
|
||||||
jq -r \
|
'.[] | (.file | ltrimstr($root)) as $file | .diagnostics[] |
|
||||||
'to_entries | map(.key as $file | .value[] |
|
|
||||||
{
|
{
|
||||||
file: $file | sub("file://${{ github.workspace }}/./"; ""),
|
file: $file,
|
||||||
code: .code,
|
code: .code,
|
||||||
line: .range.start.line,
|
line: .range.start.line,
|
||||||
message: (.message | split("\n")
|
message: (.message | split("\n")
|
||||||
| map(if startswith("- ") then .[2:] else . end)
|
| map(if startswith("- ") then .[2:] else . end)
|
||||||
| join("\n")),
|
| join("\n")),
|
||||||
severity: (if .severity <= 1 then "ERR" else "WARN" end)
|
severity: (if .severity <= 1 then "ERR" else "WARN" end)
|
||||||
}) |
|
} |
|
||||||
map("**[\(.severity)] \(.code):** \(.file)#L\(.line)<br><code>\(.message)</code>") | .[]' \
|
"**[\(.severity)] \(.code):** \(.file)#L\(.line + 1)<br><code>\(.message)</code>"' \
|
||||||
> $GITHUB_STEP_SUMMARY
|
check.json > "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Github Annotations (limited to 10)
|
# Github Annotations (limited to 10)
|
||||||
cat check.json | \
|
jq -r --arg root "$PWD/" \
|
||||||
jq -r \
|
'.[] | (.file | ltrimstr($root)) as $file | .diagnostics[] |
|
||||||
'to_entries | map(.key as $file | .value[] |
|
{
|
||||||
{
|
file: $file,
|
||||||
file: $file | sub("file://${{ github.workspace }}/./"; ""),
|
title: .code,
|
||||||
title: .code,
|
line: .range.start.line,
|
||||||
line: .range.start.line,
|
endLine: .range.end.line,
|
||||||
endLine: .range.end.line,
|
col: .range.start.character,
|
||||||
col: .range.start.character,
|
endColumn: .range.end.character,
|
||||||
endColumn: .range.end.character,
|
message: (.message | gsub("\n"; "%0A"; "m")),
|
||||||
message: .message | gsub("\n"; "%0A"; "m"),
|
level: (if .severity <= 1 then "error" else "warning" end)
|
||||||
level: (if .severity <= 1 then "error" else "warning" end)
|
} |
|
||||||
}) |
|
"::\(.level) file=\(.file),line=\(.line + 1),endLine=\(.endLine + 1),col=\(.col + 1),endColumn=\(.endColumn + 1),title=\(.title)::\(.message) (\(.title))"' \
|
||||||
map("::\(.level) file=\(.file),line=\(.line+1),endLine=\(.line+1),col=\(.col+1),endColumn=\(.endColumn+1),title=\(.title)::\(.message) (\(.title))") | .[]'
|
check.json
|
||||||
|
|
||||||
if [[ $(wc -l < check.json) > 1 ]] ; then
|
# An entry is emitted for every file checked, including clean ones, so
|
||||||
|
# the gate counts diagnostics and asserts the workspace was actually read
|
||||||
|
if [[ $(jq 'length' check.json) -lt 100 ]] ; then
|
||||||
|
echo "::error::Only $(jq 'length' check.json) files were checked, expected the whole workspace"
|
||||||
|
exit 1
|
||||||
|
elif [[ $(jq '[.[].diagnostics[]] | length' check.json) -gt 0 ]] ; then
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
echo "✅ All checks succeeded!" | tee $GITHUB_STEP_SUMMARY
|
echo "✅ All checks succeeded!" | tee "$GITHUB_STEP_SUMMARY"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
name: Node.js CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
# The packages use workspace:^ and catalog: versions, so they only resolve
|
||||||
|
# inside a clusterio checkout with this repository under external_plugins
|
||||||
|
CLUSTERIO_REPOSITORY: clusterio/clusterio
|
||||||
|
CLUSTERIO_REF: master
|
||||||
|
# Node version to use for the tests. This is used by the setup-node action
|
||||||
|
NODE_VERSION: lts/*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: external_plugins/ExpCluster
|
||||||
|
steps:
|
||||||
|
- name: Checkout Clusterio
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: ${{ env.CLUSTERIO_REPOSITORY }}
|
||||||
|
ref: ${{ env.CLUSTERIO_REF }}
|
||||||
|
- name: Checkout ExpCluster
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: external_plugins/ExpCluster
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
- name: Install and build
|
||||||
|
working-directory: .
|
||||||
|
run: pnpm install --no-frozen-lockfile
|
||||||
|
- name: Run tests
|
||||||
|
run: pnpm --filter "@expcluster/*" run test
|
||||||
|
- name: Run reference checks
|
||||||
|
run: |
|
||||||
|
# pnpm install runs prepare in dependency order, which hides missing
|
||||||
|
# project references. Building from an empty dist one package at a time
|
||||||
|
# will fail unless every cross package import is referenced correctly.
|
||||||
|
for pkg in exp_*/; do
|
||||||
|
rm -rf exp_*/dist
|
||||||
|
echo "::group::${pkg%/}"
|
||||||
|
(cd "$pkg" && pnpm exec tsc --build)
|
||||||
|
echo "::endgroup::"
|
||||||
|
done
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
dist/
|
dist/
|
||||||
node_modules/
|
node_modules/
|
||||||
.vscode
|
.vscode
|
||||||
|
.luarc.json
|
||||||
|
.tap/
|
||||||
|
|||||||
-103
@@ -1,103 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
|
||||||
"workspace.ignoreDir": [
|
|
||||||
".vscode", ".github", "luals", "factorio"
|
|
||||||
],
|
|
||||||
"completion.requireSeparator": "/",
|
|
||||||
"runtime.pluginArgs": [
|
|
||||||
"--clusterio-modules"
|
|
||||||
],
|
|
||||||
"doc": {
|
|
||||||
"regengine": "glob",
|
|
||||||
"privateName": [ "__*" ],
|
|
||||||
"packageName": [ "_*" ]
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"checkTableShape": true,
|
|
||||||
"inferParamType": true
|
|
||||||
},
|
|
||||||
"$comment-name-style-check": "Disabled below until config issue fixed: https://github.com/LuaLS/lua-language-server/issues/2643",
|
|
||||||
"$comment-type-mismatch": "Disabled unless opened due to performance concerns, time reduced to 10s from 400s",
|
|
||||||
"diagnostics": {
|
|
||||||
"unusedLocalExclude": [ "_*", "i", "j", "k", "v" ],
|
|
||||||
"groupFileStatus": {
|
|
||||||
"ambiguity": "Any",
|
|
||||||
"await": "None",
|
|
||||||
"codestyle": "Opened",
|
|
||||||
"conventions": "Any",
|
|
||||||
"duplicate": "Any",
|
|
||||||
"global": "Any",
|
|
||||||
"luadoc": "Opened",
|
|
||||||
"redefined": "Opened",
|
|
||||||
"strict": "Any",
|
|
||||||
"strong": "Any",
|
|
||||||
"type-check": "Any",
|
|
||||||
"unbalanced": "Opened",
|
|
||||||
"unused": "Opened"
|
|
||||||
},
|
|
||||||
"neededFileStatus": {
|
|
||||||
"no-unknown": "None!",
|
|
||||||
"spell-check": "None!",
|
|
||||||
"name-style-check": "None!",
|
|
||||||
"assign-type-mismatch": "Opened!",
|
|
||||||
"return-type-mismatch": "Opened!",
|
|
||||||
"param-type-mismatch": "Opened!"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nameStyle.config": {
|
|
||||||
"local_name_style": [{
|
|
||||||
"type" : "pattern",
|
|
||||||
"param": "_?_?(\\S*)",
|
|
||||||
"$1": "snake_case"
|
|
||||||
}],
|
|
||||||
"module_local_name_style": [{
|
|
||||||
"type" : "pattern",
|
|
||||||
"param": "_?_?(\\S*)",
|
|
||||||
"$1": "snake_case"
|
|
||||||
}, {
|
|
||||||
"type" : "pattern",
|
|
||||||
"param": "_?_?(\\S*)",
|
|
||||||
"$1": "upper_snake_case"
|
|
||||||
}, {
|
|
||||||
"type" : "pattern",
|
|
||||||
"param": "_?_?(\\S*)",
|
|
||||||
"$1": "pascal_case"
|
|
||||||
}],
|
|
||||||
"function_param_name_style": [{
|
|
||||||
"type" : "pattern",
|
|
||||||
"param": "_?_?(\\S*)",
|
|
||||||
"$1": "snake_case"
|
|
||||||
}],
|
|
||||||
"function_name_style": [{
|
|
||||||
"type" : "pattern",
|
|
||||||
"param": "_?_?(\\S*)",
|
|
||||||
"$1": "snake_case"
|
|
||||||
}],
|
|
||||||
"local_function_name_style": [{
|
|
||||||
"type" : "pattern",
|
|
||||||
"param": "_?_?(\\S*)",
|
|
||||||
"$1": "snake_case"
|
|
||||||
}],
|
|
||||||
"table_field_name_style": [{
|
|
||||||
"type" : "pattern",
|
|
||||||
"param": "_?_?(\\S*)",
|
|
||||||
"$1": "snake_case"
|
|
||||||
}],
|
|
||||||
"global_variable_name_style": [{
|
|
||||||
"type" : "pattern",
|
|
||||||
"param": "_?_?(\\S*)",
|
|
||||||
"$1": "snake_case"
|
|
||||||
}, {
|
|
||||||
"type" : "pattern",
|
|
||||||
"param": "_?_?(\\S*)",
|
|
||||||
"$1": "upper_snake_case"
|
|
||||||
}],
|
|
||||||
"module_name_style": [ "pascal_case", "snake_case" ],
|
|
||||||
"require_module_name_style": [ "pascal_case", "snake_case" ],
|
|
||||||
"class_name_style": "pascal_case",
|
|
||||||
"const_variable_name_style": "upper_snake_case"
|
|
||||||
},
|
|
||||||
"format.defaultConfig": {
|
|
||||||
"continuation_indent.in_expr": "0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+3
-1
@@ -5,9 +5,11 @@ All are welcome to make bug reports, feature requests, and pull requests for our
|
|||||||
For developers wanting to add features please follow these guidelines:
|
For developers wanting to add features please follow these guidelines:
|
||||||
|
|
||||||
- All lua code is documented using ldoc.
|
- All lua code is documented using ldoc.
|
||||||
|
- Set the `CLUSTERIO` environment variable to your clusterio development install path, the lint needs it to resolve `modules/clusterio/*`.
|
||||||
|
- Lua is checked with [emmylua](https://github.com/EmmyLuaLs/emmylua-analyzer-rust), configured by `.emmyrc.json`. Install the EmmyLua extension rather than sumnekolua / luals, and use factoriomod-debug to generate the factorio api types. Note that an inline cast must be written `--[[@as T]]`, the spaced form is not parsed.
|
||||||
- Changes should be made on your own fork and merged into `main` through a pull request.
|
- Changes should be made on your own fork and merged into `main` through a pull request.
|
||||||
- Each pull request should be limited to one feature or a few bug fixes and link to the related issue page.
|
- Each pull request should be limited to one feature or a few bug fixes and link to the related issue page.
|
||||||
- Pull requests are automatically linted and documentation checked.
|
- Pull requests are automatically linted, then built and tested against the latest clusterio `master`.
|
||||||
- Pull requests are manually reviewed to maintain code and language quality.
|
- Pull requests are manually reviewed to maintain code and language quality.
|
||||||
- New features should have the branch names: `feature/feature-name`
|
- New features should have the branch names: `feature/feature-name`
|
||||||
- Bug fixes should have the branch names: `fix/bug-name`
|
- Bug fixes should have the branch names: `fix/bug-name`
|
||||||
|
|||||||
@@ -42,10 +42,17 @@ This module is currently not published and therefore can not be installed via `n
|
|||||||
|
|
||||||
See [Contributing](CONTRIBUTING.md) for how to make pull requests and issues.
|
See [Contributing](CONTRIBUTING.md) for how to make pull requests and issues.
|
||||||
|
|
||||||
|
Details of the scenario can also be seen on [Wiki](https://wiki.aperx.org/game/factorio/scenario).
|
||||||
|
|
||||||
|
For the locale, access to [Translation](https://translate.aperx.org).
|
||||||
|
|
||||||
|
<p align="center"><a href="https://translate.aperx.org/engage/expcluster/"><img src="https://translate.aperx.org/widget/expcluster/multi-auto.svg" alt="translation status"></a></p>
|
||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
| Release* | Release Name | Factorio Version** |
|
| Release* | Release Name | Factorio Version** |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
| [7.0][s7.0] | Clusterio Plugin (Beta) | [2.1.7][f2.1.7] |
|
||||||
| [6.4][s6.4] | Farewell Factorio 1.1 | [1.1.110][f1.1.110] |
|
| [6.4][s6.4] | Farewell Factorio 1.1 | [1.1.110][f1.1.110] |
|
||||||
| [6.3][s6.3] | Feature Bundle 2: Electric Boogaloo | [1.1.101][f1.1.101] |
|
| [6.3][s6.3] | Feature Bundle 2: Electric Boogaloo | [1.1.101][f1.1.101] |
|
||||||
| [6.2][s6.2] | Mega Feature Bundle | [1.1.32][f1.1.32] |
|
| [6.2][s6.2] | Mega Feature Bundle | [1.1.32][f1.1.32] |
|
||||||
@@ -72,6 +79,7 @@ See [Contributing](CONTRIBUTING.md) for how to make pull requests and issues.
|
|||||||
|
|
||||||
\*\* Factorio versions show the version they were made for, often the minimum requirement to run the scenario.
|
\*\* Factorio versions show the version they were made for, often the minimum requirement to run the scenario.
|
||||||
|
|
||||||
|
[s7.0]: https://github.com/explosivegaming/scenario/releases/tag/7.0.0
|
||||||
[s6.4]: https://github.com/explosivegaming/scenario/releases/tag/6.4.0
|
[s6.4]: https://github.com/explosivegaming/scenario/releases/tag/6.4.0
|
||||||
[s6.3]: https://github.com/explosivegaming/scenario/releases/tag/6.3.0
|
[s6.3]: https://github.com/explosivegaming/scenario/releases/tag/6.3.0
|
||||||
[s6.2]: https://github.com/explosivegaming/scenario/releases/tag/6.2.0
|
[s6.2]: https://github.com/explosivegaming/scenario/releases/tag/6.2.0
|
||||||
@@ -94,6 +102,7 @@ See [Contributing](CONTRIBUTING.md) for how to make pull requests and issues.
|
|||||||
[s1.0]: https://github.com/explosivegaming/scenario/releases/tag/v1.0
|
[s1.0]: https://github.com/explosivegaming/scenario/releases/tag/v1.0
|
||||||
[s0.1]: https://github.com/explosivegaming/scenario/releases/tag/v0.1
|
[s0.1]: https://github.com/explosivegaming/scenario/releases/tag/v0.1
|
||||||
|
|
||||||
|
[f2.1.7]: https://wiki.factorio.com/Version_history/2.1.0#2.1.7
|
||||||
[f1.1.110]: https://wiki.factorio.com/Version_history/1.1.0#1.1.110
|
[f1.1.110]: https://wiki.factorio.com/Version_history/1.1.0#1.1.110
|
||||||
[f1.1.101]: https://wiki.factorio.com/Version_history/1.1.0#1.1.101
|
[f1.1.101]: https://wiki.factorio.com/Version_history/1.1.0#1.1.101
|
||||||
[f1.1.32]: https://wiki.factorio.com/Version_history/1.1.0#1.1.32
|
[f1.1.32]: https://wiki.factorio.com/Version_history/1.1.0#1.1.32
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ export const plugin: lib.PluginDeclaration = {
|
|||||||
name: "exp_commands",
|
name: "exp_commands",
|
||||||
title: "exp_commands",
|
title: "exp_commands",
|
||||||
description: "Example Description. Plugin. Change me in index.ts",
|
description: "Example Description. Plugin. Change me in index.ts",
|
||||||
instanceEntrypoint: "./dist/node/instance",
|
instanceEntrypoint: "./dist/node/instance.js",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ The default permission authorities controlled by the flags: admin_only, system_o
|
|||||||
|
|
||||||
local Storage = require("modules/exp_util/storage")
|
local Storage = require("modules/exp_util/storage")
|
||||||
|
|
||||||
local Commands = require("modules/exp_commands") --- @class Commands
|
local Commands = require("modules/exp_commands") --- @class (partial) Commands
|
||||||
local add, allow, deny = Commands.add_permission_authority, Commands.status.success, Commands.status.unauthorised
|
local add, allow, deny = Commands.add_permission_authority, Commands.status.success, Commands.status.unauthorised
|
||||||
|
|
||||||
local authorities = {}
|
local authorities = {}
|
||||||
@@ -28,13 +28,13 @@ end)
|
|||||||
--- Allow a player access to system commands, use for debug purposes only
|
--- Allow a player access to system commands, use for debug purposes only
|
||||||
--- @param player_name string? The name of the player to give access to, default is the current player
|
--- @param player_name string? The name of the player to give access to, default is the current player
|
||||||
function Commands.unlock_system_commands(player_name)
|
function Commands.unlock_system_commands(player_name)
|
||||||
system_players[player_name or game.player.name] = true
|
system_players[player_name or assert(game.player).name] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Remove access from system commands for a player, use for debug purposes only
|
--- Remove access from system commands for a player, use for debug purposes only
|
||||||
--- @param player_name string? The name of the player to give access to, default is the current player
|
--- @param player_name string? The name of the player to give access to, default is the current player
|
||||||
function Commands.lock_system_commands(player_name)
|
function Commands.lock_system_commands(player_name)
|
||||||
system_players[player_name or game.player.name] = nil
|
system_players[player_name or assert(game.player).name] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get a list of all players who have system commands unlocked
|
--- Get a list of all players who have system commands unlocked
|
||||||
|
|||||||
@@ -25,18 +25,18 @@ end)
|
|||||||
--- @param page_size number The number of requests to show per page
|
--- @param page_size number The number of requests to show per page
|
||||||
--- @return LocalisedString[][], number
|
--- @return LocalisedString[][], number
|
||||||
local function format_as_pages(commands, page_size)
|
local function format_as_pages(commands, page_size)
|
||||||
local pages = { {} }
|
local current_page = {}
|
||||||
|
local pages = { current_page }
|
||||||
local page_length = 0
|
local page_length = 0
|
||||||
local current_page = 1
|
|
||||||
local total = 0
|
local total = 0
|
||||||
|
|
||||||
for _, command in pairs(commands) do
|
for _, command in pairs(commands) do
|
||||||
total = total + 1
|
total = total + 1
|
||||||
page_length = page_length + 1
|
page_length = page_length + 1
|
||||||
if page_length > page_size then
|
if page_length > page_size then
|
||||||
current_page = current_page + 1
|
|
||||||
pages[current_page] = {}
|
|
||||||
page_length = 1
|
page_length = 1
|
||||||
|
current_page = {}
|
||||||
|
pages[#pages + 1] = current_page
|
||||||
end
|
end
|
||||||
|
|
||||||
local description
|
local description
|
||||||
@@ -48,7 +48,7 @@ local function format_as_pages(commands, page_size)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local aliases = #command.aliases > 0 and { "exp-commands_help.aliases", table.concat(command.aliases, ", ") } or ""
|
local aliases = #command.aliases > 0 and { "exp-commands_help.aliases", table.concat(command.aliases, ", ") } or ""
|
||||||
pages[current_page][page_length] = { "exp-commands_help.format", command.name, description, aliases }
|
current_page[page_length] = { "exp-commands_help.format", command.name, description, aliases }
|
||||||
end
|
end
|
||||||
|
|
||||||
return pages, total
|
return pages, total
|
||||||
@@ -71,7 +71,7 @@ Commands.new("commands", { "exp-commands_help.description" })
|
|||||||
page = as_number
|
page = as_number
|
||||||
end
|
end
|
||||||
|
|
||||||
keyword = keyword:lower()
|
keyword = tostring(keyword):lower()
|
||||||
local pages, found
|
local pages, found
|
||||||
if cache and cache.keyword == keyword then
|
if cache and cache.keyword == keyword then
|
||||||
-- Cached value found, no search is needed
|
-- Cached value found, no search is needed
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ local ExpUtil = require("modules/exp_util")
|
|||||||
local Async = require("modules/exp_util/async")
|
local Async = require("modules/exp_util/async")
|
||||||
local Storage = require("modules/exp_util/storage")
|
local Storage = require("modules/exp_util/storage")
|
||||||
|
|
||||||
local Commands = require("modules/exp_commands") --- @class Commands
|
local Commands = require("modules/exp_commands") --- @class (partial) Commands
|
||||||
|
|
||||||
local rcon_env = {} --- @type table<string, any>
|
local rcon_env = {} --- @type table<string, any>
|
||||||
local rcon_static = {} --- @type table<string, any>
|
local rcon_static = {} --- @type table<string, any>
|
||||||
@@ -21,12 +21,10 @@ setmetatable(rcon_static, { __index = _G })
|
|||||||
setmetatable(rcon_env, { __index = rcon_static })
|
setmetatable(rcon_env, { __index = rcon_static })
|
||||||
|
|
||||||
--- Some common static values which can be added now
|
--- Some common static values which can be added now
|
||||||
--- @diagnostic disable: name-style-check
|
|
||||||
rcon_static.Async = Async
|
rcon_static.Async = Async
|
||||||
rcon_static.ExpUtil = ExpUtil
|
rcon_static.ExpUtil = ExpUtil
|
||||||
rcon_static.Commands = Commands
|
rcon_static.Commands = Commands
|
||||||
rcon_static.print = Commands.print
|
rcon_static.print = Commands.print
|
||||||
--- @diagnostic enable: name-style-check
|
|
||||||
|
|
||||||
--- Some common callback values which are useful when a player uses the command
|
--- Some common callback values which are useful when a player uses the command
|
||||||
--- @alias ExpCommand.RconDynamic fun(player: LuaPlayer?): any
|
--- @alias ExpCommand.RconDynamic fun(player: LuaPlayer?): any
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Commands.new("_sudo", { "exp-commands_sudo.description" })
|
|||||||
--- @cast command ExpCommand
|
--- @cast command ExpCommand
|
||||||
--- @cast parameter string
|
--- @cast parameter string
|
||||||
|
|
||||||
--- @diagnostic disable-next-line: invisible
|
--- @diagnostic disable-next-line: access-invisible
|
||||||
return Commands._event_handler{
|
return Commands._event_handler{
|
||||||
name = command.name,
|
name = command.name,
|
||||||
tick = game.tick,
|
tick = game.tick,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ local Commands = require("modules/exp_commands")
|
|||||||
local add, parse = Commands.add_data_type, Commands.parse_input
|
local add, parse = Commands.add_data_type, Commands.parse_input
|
||||||
local valid, invalid = Commands.status.success, Commands.status.invalid_input
|
local valid, invalid = Commands.status.success, Commands.status.invalid_input
|
||||||
|
|
||||||
local types = {} --- @class Commands.types
|
local types = {} --- @class (partial) Commands.types
|
||||||
|
|
||||||
--- A boolean value where true is one of: yes, y, true, 1
|
--- A boolean value where true is one of: yes, y, true, 1
|
||||||
types.boolean =
|
types.boolean =
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ local Search = require("modules/exp_commands/search")
|
|||||||
--- @type LuaPlayer?
|
--- @type LuaPlayer?
|
||||||
local _print_player
|
local _print_player
|
||||||
|
|
||||||
--- @class Commands
|
--- @class (partial) Commands
|
||||||
local Commands = {
|
local Commands = {
|
||||||
color = ExpUtil.color,
|
color = ExpUtil.color,
|
||||||
format_rich_text_color = ExpUtil.format_rich_text_color,
|
format_rich_text_color = ExpUtil.format_rich_text_color,
|
||||||
@@ -84,7 +84,7 @@ local Commands = {
|
|||||||
--- Contains the different status values a command can return
|
--- Contains the different status values a command can return
|
||||||
Commands.status = {}
|
Commands.status = {}
|
||||||
|
|
||||||
--- @class Commands.types: table<string, Commands.InputParser | Commands.InputParserFactory>
|
--- @class (partial) Commands.types: table<string, Commands.InputParser<any> | Commands.InputParserFactory<any>>
|
||||||
--- Stores all input parsers and validators for different data types
|
--- Stores all input parsers and validators for different data types
|
||||||
Commands.types = {}
|
Commands.types = {}
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ end
|
|||||||
--- @class Commands.Argument
|
--- @class Commands.Argument
|
||||||
--- @field name string The name of the argument
|
--- @field name string The name of the argument
|
||||||
--- @field description LocalisedString? The description of the argument
|
--- @field description LocalisedString? The description of the argument
|
||||||
--- @field input_parser Commands.InputParser The input parser for the argument
|
--- @field input_parser Commands.InputParser<any> The input parser for the argument
|
||||||
--- @field optional boolean True when the argument is optional
|
--- @field optional boolean True when the argument is optional
|
||||||
--- @field default any? The default value of the argument
|
--- @field default any? The default value of the argument
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ Commands.server = setmetatable({
|
|||||||
Commands.error("Command does not support rcon usage, requires LuaPlayer." .. key)
|
Commands.error("Command does not support rcon usage, requires LuaPlayer." .. key)
|
||||||
error("Command does not support rcon usage, requires LuaPlayer." .. key)
|
error("Command does not support rcon usage, requires LuaPlayer." .. key)
|
||||||
end,
|
end,
|
||||||
}) --[[ @as LuaPlayer ]]
|
}) --[[@as LuaPlayer]]
|
||||||
|
|
||||||
--- Status Returns.
|
--- Status Returns.
|
||||||
-- Return values used by command callbacks
|
-- Return values used by command callbacks
|
||||||
@@ -170,7 +170,8 @@ Commands.server = setmetatable({
|
|||||||
--- @param msg LocalisedString? An optional message to be included when a command completes (only has an effect in command callbacks)
|
--- @param msg LocalisedString? An optional message to be included when a command completes (only has an effect in command callbacks)
|
||||||
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
|
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
|
||||||
function Commands.status.success(msg)
|
function Commands.status.success(msg)
|
||||||
return Commands.status.success, msg == nil and { "exp-commands.success" } or msg
|
if msg == nil then return Commands.status.success, { "exp-commands.success" } end
|
||||||
|
return Commands.status.success, msg
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Used to signal an error has occurred in a command, data type parser, or permission authority
|
--- Used to signal an error has occurred in a command, data type parser, or permission authority
|
||||||
@@ -178,7 +179,8 @@ end
|
|||||||
--- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided
|
--- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided
|
||||||
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
|
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
|
||||||
function Commands.status.error(msg)
|
function Commands.status.error(msg)
|
||||||
return Commands.status.error, { "exp-commands.error", msg == nil and { "exp-commands.error-default" } or msg }
|
if msg == nil then msg = { "exp-commands.error-default" } end
|
||||||
|
return Commands.status.error, { "exp-commands.error", msg }
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Used to signal the player is unauthorised to use a command, primarily used by permission authorities but can be used in a command callback
|
--- Used to signal the player is unauthorised to use a command, primarily used by permission authorities but can be used in a command callback
|
||||||
@@ -186,7 +188,8 @@ end
|
|||||||
--- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided
|
--- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided
|
||||||
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
|
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
|
||||||
function Commands.status.unauthorised(msg)
|
function Commands.status.unauthorised(msg)
|
||||||
return Commands.status.unauthorised, { "exp-commands.unauthorized", msg == nil and { "exp-commands.unauthorized-default" } or msg }
|
if msg == nil then msg = { "exp-commands.unauthorized-default" } end
|
||||||
|
return Commands.status.unauthorised, { "exp-commands.unauthorized", msg }
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Used to signal the player provided invalid input to an command, primarily used by data type parsers but can be used in a command callback
|
--- Used to signal the player provided invalid input to an command, primarily used by data type parsers but can be used in a command callback
|
||||||
@@ -194,15 +197,16 @@ end
|
|||||||
--- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided
|
--- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided
|
||||||
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
|
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
|
||||||
function Commands.status.invalid_input(msg)
|
function Commands.status.invalid_input(msg)
|
||||||
return Commands.status.invalid_input, msg == nil and { "exp-commands.invalid-input" } or msg
|
if msg == nil then return Commands.status.invalid_input, { "exp-commands.invalid-input" } end
|
||||||
|
return Commands.status.invalid_input, msg
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Used to signal an internal error has occurred, this is reserved for internal use only
|
--- Used to signal an internal error has occurred, this is reserved for internal use only
|
||||||
--- @param msg LocalisedString A message detailing the error which has occurred, will be logged and outputted
|
--- @param msg LocalisedString? A message detailing the error which has occurred, will be logged and outputted
|
||||||
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
|
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
|
||||||
--- @package
|
--- @package
|
||||||
function Commands.status.internal_error(msg)
|
function Commands.status.internal_error(msg)
|
||||||
return Commands.status.internal_error, { "exp-commands.internal-error", msg }
|
return Commands.status.internal_error, { "exp-commands.internal-error", msg or "" }
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @type table<Commands.Status, string>
|
--- @type table<Commands.Status, string>
|
||||||
@@ -281,7 +285,7 @@ end
|
|||||||
--- @alias Commands.InputParserFactory<T> fun(...: any): Commands.InputParser<T>
|
--- @alias Commands.InputParserFactory<T> fun(...: any): Commands.InputParser<T>
|
||||||
|
|
||||||
--- Add a new input parser to the command library, this method validates that it does not already exist
|
--- Add a new input parser to the command library, this method validates that it does not already exist
|
||||||
--- @generic T : Commands.InputParser | Commands.InputParserFactory
|
--- @generic T : Commands.InputParser<any> | Commands.InputParserFactory<any>
|
||||||
--- @param data_type string The name of the data type the input parser reads in and validates, becomes a key of Commands.types
|
--- @param data_type string The name of the data type the input parser reads in and validates, becomes a key of Commands.types
|
||||||
--- @param input_parser T The function used to parse and validate the data type
|
--- @param input_parser T The function used to parse and validate the data type
|
||||||
--- @return T # The function which was provided as the second argument
|
--- @return T # The function which was provided as the second argument
|
||||||
@@ -295,7 +299,7 @@ function Commands.add_data_type(data_type, input_parser)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Remove an input parser for a data type, must be the same string that was passed to add_input_parser
|
--- Remove an input parser for a data type, must be the same string that was passed to add_input_parser
|
||||||
--- @param data_type string | Commands.InputParser | Commands.InputParserFactory The data type or input parser you want to remove the input parser for
|
--- @param data_type string | Commands.InputParser<any> | Commands.InputParserFactory<any> The data type or input parser you want to remove the input parser for
|
||||||
function Commands.remove_data_type(data_type)
|
function Commands.remove_data_type(data_type)
|
||||||
Commands.types[data_type] = nil
|
Commands.types[data_type] = nil
|
||||||
for k, v in pairs(Commands.types) do
|
for k, v in pairs(Commands.types) do
|
||||||
@@ -433,7 +437,7 @@ end
|
|||||||
--- Add a new required argument to the command of the given data type
|
--- Add a new required argument to the command of the given data type
|
||||||
--- @param name string The name of the argument being added
|
--- @param name string The name of the argument being added
|
||||||
--- @param description LocalisedString? The description of the argument being added
|
--- @param description LocalisedString? The description of the argument being added
|
||||||
--- @param input_parser Commands.InputParser The input parser to be used for the argument
|
--- @param input_parser Commands.InputParser<any> The input parser to be used for the argument
|
||||||
--- @return ExpCommand
|
--- @return ExpCommand
|
||||||
function Commands._prototype:argument(name, description, input_parser)
|
function Commands._prototype:argument(name, description, input_parser)
|
||||||
assert_command_mutable(self)
|
assert_command_mutable(self)
|
||||||
@@ -454,7 +458,7 @@ end
|
|||||||
--- Add a new optional argument to the command of the given data type
|
--- Add a new optional argument to the command of the given data type
|
||||||
--- @param name string The name of the argument being added
|
--- @param name string The name of the argument being added
|
||||||
--- @param description LocalisedString? The description of the argument being added
|
--- @param description LocalisedString? The description of the argument being added
|
||||||
--- @param input_parser Commands.InputParser The input parser to be used for the argument
|
--- @param input_parser Commands.InputParser<any> The input parser to be used for the argument
|
||||||
--- @return ExpCommand
|
--- @return ExpCommand
|
||||||
function Commands._prototype:optional(name, description, input_parser)
|
function Commands._prototype:optional(name, description, input_parser)
|
||||||
assert_command_mutable(self)
|
assert_command_mutable(self)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ local Search = {}
|
|||||||
local Storage = require("modules/exp_util/storage")
|
local Storage = require("modules/exp_util/storage")
|
||||||
|
|
||||||
--- Setup the storage to contain the pending translations and the completed ones
|
--- Setup the storage to contain the pending translations and the completed ones
|
||||||
local pending = {} --- @type { [1]: string, [2]: string }[]
|
local pending = {} --- @type table<uint, [string, string]>
|
||||||
local translations = {} --- @type table<string, table<string, string>>
|
local translations = {} --- @type table<string, table<string, string>>
|
||||||
Storage.register({
|
Storage.register({
|
||||||
pending,
|
pending,
|
||||||
@@ -50,6 +50,7 @@ function Search.prepare(custom_commands)
|
|||||||
name = name,
|
name = name,
|
||||||
description = locale_desc,
|
description = locale_desc,
|
||||||
help_text = locale_desc,
|
help_text = locale_desc,
|
||||||
|
usage = locale_desc,
|
||||||
aliases = {},
|
aliases = {},
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@@ -65,7 +66,7 @@ function Search.on_player_locale_changed(event)
|
|||||||
local ids = player.request_translations(required_translations)
|
local ids = player.request_translations(required_translations)
|
||||||
assert(ids, "Translation ids was nil")
|
assert(ids, "Translation ids was nil")
|
||||||
for i, command_name in ipairs(command_names) do
|
for i, command_name in ipairs(command_names) do
|
||||||
pending[ids[i]] = { locale, command_name }
|
pending[assert(ids[i])] = { locale, command_name }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@expcluster/lib_commands",
|
"name": "@expcluster/lib_commands",
|
||||||
"version": "0.1.0",
|
"version": "7.0.1",
|
||||||
"description": "Clusterio plugin providing a Lua command processing library.",
|
"description": "Clusterio plugin providing a Lua command processing library.",
|
||||||
"author": "Cooldude2606 <https://github.com/Cooldude2606>",
|
"author": "Cooldude2606 <https://github.com/Cooldude2606>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "explosivegaming/ExpCluster",
|
"repository": "explosivegaming/ExpCluster",
|
||||||
|
"type": "module",
|
||||||
"main": "dist/node/index.js",
|
"main": "dist/node/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "tsc --build && webpack-cli --env production"
|
"prepare": "tsc --build && webpack-cli --env production"
|
||||||
@@ -13,10 +14,10 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@clusterio/lib": "catalog:"
|
"@clusterio/lib": "workspace:^"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@clusterio/lib": "catalog:",
|
"@clusterio/lib": "workspace:^",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"webpack": "catalog:",
|
"webpack": "catalog:",
|
||||||
|
|||||||
+410
-258
@@ -1,282 +1,434 @@
|
|||||||
|
import { BaseControllerPlugin } from "@clusterio/controller";
|
||||||
import * as lib from "@clusterio/lib";
|
import * as lib from "@clusterio/lib";
|
||||||
import { BaseControllerPlugin, InstanceInfo } from "@clusterio/controller";
|
import * as messages from "./messages.js";
|
||||||
|
import * as path from "node:path";
|
||||||
import {
|
|
||||||
PermissionStrings, PermissionStringsUpdate,
|
|
||||||
PermissionGroup, PermissionGroupUpdate,
|
|
||||||
InstancePermissionGroups,
|
|
||||||
PermissionInstanceId,
|
|
||||||
PermissionGroupEditEvent,
|
|
||||||
} from "./messages";
|
|
||||||
|
|
||||||
import path from "path";
|
|
||||||
import fs from "fs-extra";
|
|
||||||
|
|
||||||
export class ControllerPlugin extends BaseControllerPlugin {
|
export class ControllerPlugin extends BaseControllerPlugin {
|
||||||
static permissionGroupsPath = "exp_groups.json";
|
groups!: lib.SubscribableDatastore<messages.GroupRecord>;
|
||||||
static userGroupsPath = "exp_user_groups.json";
|
roleMappings!: lib.SubscribableDatastore<messages.RoleMappingRecord>;
|
||||||
|
manualAssignments!: lib.SubscribableDatastore<messages.AssignmentRecord>;
|
||||||
|
resolvedAssignments!: lib.SubscribableDatastore<messages.AssignmentRecord>;
|
||||||
|
|
||||||
userToGroup: Map<lib.User["id"], PermissionGroup> = new Map(); // TODO this needs to be per instance
|
async init() {
|
||||||
permissionStrings!: Map<PermissionStrings["id"], PermissionStrings>;
|
const databaseDirectory = this.controller.config.get("controller.database_directory");
|
||||||
permissionGroups!: Map<InstancePermissionGroups["id"], InstancePermissionGroups>;
|
|
||||||
|
|
||||||
async init() {
|
this.groups = new lib.SubscribableDatastore(
|
||||||
this.controller.handle(PermissionStringsUpdate, this.handlePermissionStringsUpdate.bind(this));
|
...await new lib.JsonIdDatastoreProvider(
|
||||||
this.controller.handle(PermissionGroupUpdate, this.handlePermissionGroupUpdate.bind(this));
|
path.join(databaseDirectory, "exp_groups", "groups.json"),
|
||||||
this.controller.handle(PermissionGroupEditEvent, this.handlePermissionGroupEditEvent.bind(this));
|
messages.GroupRecord.fromJSON.bind(messages.GroupRecord),
|
||||||
this.controller.subscriptions.handle(PermissionStringsUpdate, this.handlePermissionStringsSubscription.bind(this));
|
).bootstrap()
|
||||||
this.controller.subscriptions.handle(PermissionGroupUpdate, this.handlePermissionGroupSubscription.bind(this));
|
);
|
||||||
this.controller.subscriptions.handle(PermissionGroupEditEvent);
|
|
||||||
this.permissionStrings = new Map([["Global", new PermissionStrings("Global", new Set())]]);
|
|
||||||
this.permissionGroups = new Map([["Global", new InstancePermissionGroups("Global")]]);
|
|
||||||
await this.loadData();
|
|
||||||
|
|
||||||
// Add the default group if missing and add any missing cluster roles
|
this.roleMappings = new lib.SubscribableDatastore(
|
||||||
const clusterRoles = [...this.controller.userManager.roles.values()]
|
...await new lib.JsonIdDatastoreProvider(
|
||||||
for (const instanceGroups of this.permissionGroups.values()) {
|
path.join(databaseDirectory, "exp_groups", "role_mappings.json"),
|
||||||
const groups = instanceGroups.groups;
|
messages.RoleMappingRecord.fromJSON.bind(messages.RoleMappingRecord),
|
||||||
const instanceRoles = [...groups.values()].flatMap(group => [...group.roleIds.values()]);
|
).bootstrap()
|
||||||
const missingRoles = clusterRoles.filter(role => instanceRoles.includes(role.id));
|
);
|
||||||
const defaultGroup = groups.get("Default");
|
|
||||||
if (defaultGroup) {
|
|
||||||
for (const role of missingRoles) {
|
|
||||||
defaultGroup.roleIds.add(role.id)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
groups.set("Default", new PermissionGroup(
|
|
||||||
instanceGroups.instanceId,
|
|
||||||
"Default",
|
|
||||||
groups.size,
|
|
||||||
new Set(missingRoles.map(role => role.id))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async onControllerConfigFieldChanged(field: string, curr: unknown, prev: unknown) {
|
this.manualAssignments = new lib.SubscribableDatastore(
|
||||||
if (field === "exp_groups.allow_role_inconsistency") {
|
...await new lib.JsonIdDatastoreProvider(
|
||||||
// Do something with this.userToGroup
|
path.join(databaseDirectory, "exp_groups", "assignments.json"),
|
||||||
}
|
messages.AssignmentRecord.fromJSON.bind(messages.AssignmentRecord),
|
||||||
}
|
).bootstrap()
|
||||||
|
);
|
||||||
|
|
||||||
async onInstanceConfigFieldChanged(instance: InstanceInfo, field: string, curr: unknown, prev: unknown) {
|
this.resolvedAssignments = new lib.SubscribableDatastore();
|
||||||
this.logger.info(`controller::onInstanceConfigFieldChanged ${instance.id} ${field}`);
|
|
||||||
if (field === "exp_groups.sync_permission_groups") {
|
|
||||||
const updates = []
|
|
||||||
const now = Date.now();
|
|
||||||
if (curr) {
|
|
||||||
// Global sync enabled, we dont need the instance config
|
|
||||||
const instanceGroups = this.permissionGroups.get(instance.id);
|
|
||||||
if (instanceGroups) {
|
|
||||||
this.permissionGroups.delete(instance.id);
|
|
||||||
for (const group of instanceGroups.groups.values()) {
|
|
||||||
group.updatedAtMs = now;
|
|
||||||
group.isDeleted = true;
|
|
||||||
updates.push(group);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Global sync disabled, make a copy of the global config as a base
|
|
||||||
const global = this.permissionGroups.get("Global")!;
|
|
||||||
const oldInstanceGroups = this.permissionGroups.get(instance.id);
|
|
||||||
const instanceGroups = new InstancePermissionGroups(
|
|
||||||
instance.id, new Map([...global.groups.values()].map(group => [group.name, group.copy(instance.id)]))
|
|
||||||
)
|
|
||||||
this.permissionGroups.set(instance.id, instanceGroups);
|
|
||||||
for (const group of instanceGroups.groups.values()) {
|
|
||||||
group.updatedAtMs = now;
|
|
||||||
updates.push(group);
|
|
||||||
}
|
|
||||||
// If it has an old config (unexpected) then deal with it
|
|
||||||
if (oldInstanceGroups) {
|
|
||||||
for (const group of oldInstanceGroups.groups.values()) {
|
|
||||||
if (!instanceGroups.groups.has(group.name)) {
|
|
||||||
group.updatedAtMs = now;
|
|
||||||
group.isDeleted = true;
|
|
||||||
updates.push(group);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Send the updates to all instances and controls
|
|
||||||
if (updates.length) {
|
|
||||||
this.controller.subscriptions.broadcast(new PermissionGroupUpdate(updates));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadPermissionGroups() {
|
this.controller.subscriptions.handle(messages.GroupUpdatedEvent, this.handleGroupSubscription.bind(this));
|
||||||
const file = path.resolve(this.controller.config.get("controller.database_directory"), ControllerPlugin.permissionGroupsPath);
|
this.controller.subscriptions.handle(messages.RoleMappingUpdatedEvent, this.handleRoleMappingSubscription.bind(this));
|
||||||
this.logger.verbose(`Loading ${file}`);
|
this.controller.subscriptions.handle(messages.ManualAssignmentUpdatedEvent, this.handleManualAssignmentSubscription.bind(this));
|
||||||
try {
|
this.controller.subscriptions.handle(messages.ResolvedAssignmentUpdatedEvent, this.handleResolvedAssignmentSubscription.bind(this));
|
||||||
const content = await fs.readFile(file, { encoding: "utf8" });
|
|
||||||
for (const groupRaw of JSON.parse(content)) {
|
|
||||||
const group = PermissionGroup.fromJSON(groupRaw);
|
|
||||||
const instanceGroups = this.permissionGroups.get(group.instanceId);
|
|
||||||
if (instanceGroups) {
|
|
||||||
instanceGroups.groups.set(group.name, group);
|
|
||||||
} else {
|
|
||||||
this.permissionGroups.set(group.instanceId,
|
|
||||||
new InstancePermissionGroups(group.instanceId, new Map([[group.name, group]]))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (err: any) {
|
this.groups.on("update", this.groupsUpdated.bind(this));
|
||||||
if (err.code === "ENOENT") {
|
this.roleMappings.on("update", this.roleMappingsUpdated.bind(this));
|
||||||
this.logger.verbose("Creating new permission group database");
|
this.manualAssignments.on("update", this.manualAssignmentsUpdated.bind(this));
|
||||||
return;
|
this.resolvedAssignments.on("update", this.resolvedAssignmentsUpdated.bind(this));
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async savePermissionGroups() {
|
this.controller.handle(messages.GroupCreateRequest, this.handleGroupCreateRequest.bind(this));
|
||||||
const file = path.resolve(this.controller.config.get("controller.database_directory"), ControllerPlugin.permissionGroupsPath);
|
this.controller.handle(messages.GroupUpdateRequest, this.handleGroupUpdateRequest.bind(this));
|
||||||
this.logger.verbose(`Writing ${file}`);
|
this.controller.handle(messages.GroupDeleteRequest, this.handleGroupDeleteRequest.bind(this));
|
||||||
await lib.safeOutputFile(file, JSON.stringify(
|
this.controller.handle(messages.GroupGetRequest, this.handleGroupGetRequest.bind(this));
|
||||||
[...this.permissionGroups.values()].flatMap(instanceGroups => [...instanceGroups.groups.values()])
|
this.controller.handle(messages.GroupListRequest, this.handleGroupListRequest.bind(this));
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadUserGroups() {
|
this.controller.handle(messages.AssignmentCreateRequest, this.handleAssignmentCreateRequest.bind(this));
|
||||||
if (!this.controller.config.get("exp_groups.allow_role_inconsistency")) return;
|
this.controller.handle(messages.AssignmentUpdateRequest, this.handleAssignmentUpdateRequest.bind(this));
|
||||||
const file = path.resolve(this.controller.config.get("controller.database_directory"), ControllerPlugin.userGroupsPath);
|
this.controller.handle(messages.AssignmentDeleteRequest, this.handleAssignmentDeleteRequest.bind(this));
|
||||||
this.logger.verbose(`Loading ${file}`);
|
this.controller.handle(messages.AssignmentGetRequest, this.handleAssignmentGetRequest.bind(this));
|
||||||
try {
|
this.controller.handle(messages.AssignmentListRequest, this.handleAssignmentListRequest.bind(this));
|
||||||
const content = await fs.readFile(file, { encoding: "utf8" });
|
|
||||||
this.userToGroup = new Map(JSON.parse(content));
|
|
||||||
|
|
||||||
} catch (err: any) {
|
this.controller.handle(messages.RoleMappingCreateRequest, this.handleRoleMappingCreateRequest.bind(this));
|
||||||
if (err.code === "ENOENT") {
|
this.controller.handle(messages.RoleMappingUpdateRequest, this.handleRoleMappingUpdateRequest.bind(this));
|
||||||
this.logger.verbose("Creating new user group database");
|
this.controller.handle(messages.RoleMappingDeleteRequest, this.handleRoleMappingDeleteRequest.bind(this));
|
||||||
return;
|
this.controller.handle(messages.RoleMappingGetRequest, this.handleRoleMappingGetRequest.bind(this));
|
||||||
}
|
this.controller.handle(messages.RoleMappingListRequest, this.handleRoleMappingListRequest.bind(this));
|
||||||
throw err;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveUserGroups() {
|
async onShutdown() {
|
||||||
if (!this.controller.config.get("exp_groups.allow_role_inconsistency")) return;
|
await Promise.all([
|
||||||
const file = path.resolve(this.controller.config.get("controller.database_directory"), ControllerPlugin.userGroupsPath);
|
this.groups.save(),
|
||||||
this.logger.verbose(`Writing ${file}`);
|
this.manualAssignments.save(),
|
||||||
await lib.safeOutputFile(file, JSON.stringify([...this.permissionGroups.entries()]));
|
this.roleMappings.save(),
|
||||||
}
|
])
|
||||||
|
}
|
||||||
|
|
||||||
async loadData() {
|
/*
|
||||||
await Promise.all([
|
Subscriptions
|
||||||
this.loadPermissionGroups(),
|
*/
|
||||||
this.loadUserGroups(),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
async onSaveData() {
|
async groupsUpdated(groups: messages.GroupRecord[]) {
|
||||||
await Promise.all([
|
this.controller.subscriptions.broadcast(new messages.GroupUpdatedEvent(groups));
|
||||||
this.savePermissionGroups(),
|
|
||||||
this.saveUserGroups(),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
addPermisisonGroup(instanceId: PermissionInstanceId, name: string, permissions = new Set<string>(), silent = false) {
|
// We need to do extra work if the group was deleted
|
||||||
const instanceGroups = this.permissionGroups.get(instanceId);
|
const deletedGroupIds = groups.filter(g => g.isDeleted).map(g => g.id);
|
||||||
if (!instanceGroups) {
|
if (!deletedGroupIds.length) {
|
||||||
throw new Error("Instance ID does not exist");
|
return;
|
||||||
}
|
}
|
||||||
if (instanceGroups.groups.has(name)) {
|
|
||||||
return instanceGroups.groups.get(name)!;
|
|
||||||
}
|
|
||||||
for (const group of instanceGroups.groups.values()) {
|
|
||||||
group.order += 1;
|
|
||||||
}
|
|
||||||
const group = new PermissionGroup(instanceId, name, 0, new Set(), permissions, Date.now(), false);
|
|
||||||
instanceGroups.groups.set(group.id, group);
|
|
||||||
if (!silent) {
|
|
||||||
this.controller.subscriptions.broadcast(new PermissionGroupUpdate([group]));
|
|
||||||
}
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
removePermissionGroup(instanceId: PermissionInstanceId, name: string, silent = false) {
|
// Cascade the delete down to affected role mappings
|
||||||
const instanceGroups = this.permissionGroups.get(instanceId);
|
const mappingsToDelete = [];
|
||||||
if (!instanceGroups) {
|
for (const mapping of this.roleMappings.values()) {
|
||||||
throw new Error("Instance ID does not exist");
|
if (deletedGroupIds.includes(mapping.groupId)) {
|
||||||
}
|
mappingsToDelete.push(mapping);
|
||||||
const group = instanceGroups.groups.get(name)
|
}
|
||||||
if (!group) {
|
}
|
||||||
return null;
|
if (mappingsToDelete.length) {
|
||||||
}
|
this.roleMappings.deleteMany(mappingsToDelete);
|
||||||
for (const nextGroup of instanceGroups.groups.values()) {
|
}
|
||||||
if (nextGroup.order > group.order) {
|
|
||||||
nextGroup.order -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
instanceGroups.groups.delete(group.id);
|
|
||||||
group.updatedAtMs = Date.now();
|
|
||||||
group.isDeleted = true;
|
|
||||||
if (!silent) {
|
|
||||||
this.controller.subscriptions.broadcast(new PermissionGroupUpdate([group]));
|
|
||||||
}
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
async handlePermissionGroupEditEvent(event: PermissionGroupEditEvent) {
|
// Cascade the delete down to affected manual assignments
|
||||||
// TODO
|
const affectedPlayers = new Set<string>();
|
||||||
}
|
const assignmentsToDelete = [];
|
||||||
|
for (const assignment of this.manualAssignments.values()) {
|
||||||
|
if (deletedGroupIds.includes(assignment.groupId)) {
|
||||||
|
assignmentsToDelete.push(assignment);
|
||||||
|
affectedPlayers.add(assignment.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (assignmentsToDelete.length) {
|
||||||
|
this.manualAssignments.deleteMany(assignmentsToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
async handlePermissionStringsUpdate(event: PermissionStringsUpdate) {
|
// Find all the affected players who were assigned to this group
|
||||||
for (const update of event.updates) {
|
for (const resolved of this.resolvedAssignments.values()) {
|
||||||
const global = this.permissionStrings.get("Global")!
|
if (deletedGroupIds.includes(resolved.groupId)) {
|
||||||
this.permissionStrings.set(update.instanceId as number, update)
|
affectedPlayers.add(resolved.name);
|
||||||
global.updatedAtMs = Math.max(global.updatedAtMs, update.updatedAtMs)
|
}
|
||||||
for (const permission of update.permissions) {
|
}
|
||||||
global.permissions.add(permission)
|
if (affectedPlayers.size) {
|
||||||
}
|
this.resolvedAssignments.setMany(await this.computeResolvedAssignments([...affectedPlayers]));
|
||||||
// TODO maybe check if changes have happened rather than always pushing updates
|
}
|
||||||
this.controller.subscriptions.broadcast(new PermissionStringsUpdate([global, update]))
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handlePermissionGroupUpdate(event: PermissionGroupUpdate) {
|
async handleGroupSubscription(request: lib.SubscriptionRequest) {
|
||||||
const updates = [];
|
const groups = [...this.groups.values()]
|
||||||
for (const group of event.updates) {
|
.filter(group => group.updatedAtMs > request.lastRequestTimeMs);
|
||||||
const groups = this.permissionGroups.get(group.instanceId);
|
return groups.length ? new messages.GroupUpdatedEvent(groups) : null;
|
||||||
if (!groups) continue;
|
}
|
||||||
const existingGroup = groups.groups.get(group.id);
|
|
||||||
let update
|
|
||||||
if (!existingGroup) {
|
|
||||||
update = this.addPermisisonGroup(group.instanceId, group.name, group.permissions, true);
|
|
||||||
} else if (group.isDeleted) {
|
|
||||||
update = this.removePermissionGroup(group.instanceId, group.name, true);
|
|
||||||
} else {
|
|
||||||
existingGroup.permissions = group.permissions;
|
|
||||||
existingGroup.updatedAtMs = Date.now();
|
|
||||||
update = existingGroup;
|
|
||||||
}
|
|
||||||
if (update) updates.push(update);
|
|
||||||
}
|
|
||||||
this.controller.subscriptions.broadcast(new PermissionGroupUpdate(updates));
|
|
||||||
}
|
|
||||||
|
|
||||||
async handlePermissionStringsSubscription(request: lib.SubscriptionRequest, src: lib.Address) {
|
async roleMappingsUpdated(roleMappings: messages.RoleMappingRecord[]) {
|
||||||
const updates = [ ...this.permissionStrings.values() ]
|
this.controller.subscriptions.broadcast(new messages.RoleMappingUpdatedEvent(roleMappings));
|
||||||
.filter(
|
|
||||||
value => value.updatedAtMs > request.lastRequestTimeMs,
|
|
||||||
)
|
|
||||||
return updates.length ? new PermissionStringsUpdate(updates) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async handlePermissionGroupSubscription(request: lib.SubscriptionRequest, src: lib.Address) {
|
// Mappings pointing to a deleted group have already been handled
|
||||||
const updates = [ ...this.permissionGroups.values() ]
|
// But if any are active, then we still must recompute all assignments
|
||||||
.flatMap(instanceGroups => [...instanceGroups.groups.values()])
|
let hasActiveGroup = false;
|
||||||
.filter(
|
for (const roleMapping of roleMappings) {
|
||||||
value => value.updatedAtMs > request.lastRequestTimeMs,
|
const group = this.groups.get(roleMapping.groupId);
|
||||||
)
|
if (group && !group.isDeleted) {
|
||||||
if (src.type === lib.Address.instance) {
|
hasActiveGroup = true;
|
||||||
const instanceUpdates = updates.filter(group => group.instanceId === src.id || group.instanceId === "Global");
|
break;
|
||||||
this.logger.info(JSON.stringify(updates))
|
}
|
||||||
this.logger.info(JSON.stringify(instanceUpdates))
|
}
|
||||||
return instanceUpdates.length ? new PermissionGroupUpdate(instanceUpdates) : null;
|
if (!hasActiveGroup) {
|
||||||
}
|
return;
|
||||||
return updates.length ? new PermissionGroupUpdate(updates) : null;
|
}
|
||||||
}
|
|
||||||
|
// Affected players are those without manual assignments
|
||||||
|
const affectedPlayers = [];
|
||||||
|
for (const resolved of this.resolvedAssignments.values()) {
|
||||||
|
if (!this.manualAssignments.has(resolved.name)) {
|
||||||
|
affectedPlayers.push(resolved.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (affectedPlayers.length) {
|
||||||
|
this.resolvedAssignments.setMany(await this.computeResolvedAssignments(affectedPlayers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRoleMappingSubscription(request: lib.SubscriptionRequest) {
|
||||||
|
const mappings = [...this.roleMappings.values()]
|
||||||
|
.filter(mapping => mapping.updatedAtMs > request.lastRequestTimeMs);
|
||||||
|
return mappings.length ? new messages.RoleMappingUpdatedEvent(mappings) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async manualAssignmentsUpdated(assignments: messages.AssignmentRecord[]) {
|
||||||
|
this.controller.subscriptions.broadcast(new messages.ManualAssignmentUpdatedEvent(assignments));
|
||||||
|
|
||||||
|
// Assignments pointing to a deleted group have already been handled
|
||||||
|
const affectedPlayers: string[] = [];
|
||||||
|
for (const assignment of assignments) {
|
||||||
|
const group = this.groups.get(assignment.groupId);
|
||||||
|
if (!group || group.isDeleted) continue;
|
||||||
|
affectedPlayers.push(assignment.name);
|
||||||
|
}
|
||||||
|
if (affectedPlayers.length) {
|
||||||
|
this.resolvedAssignments.setMany(await this.computeResolvedAssignments(affectedPlayers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleManualAssignmentSubscription(request: lib.SubscriptionRequest) {
|
||||||
|
const assignments = [...this.resolvedAssignments.values()]
|
||||||
|
.filter(a => a.updatedAtMs > request.lastRequestTimeMs);
|
||||||
|
return assignments.length ? new messages.ManualAssignmentUpdatedEvent(assignments) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedAssignmentsUpdated(assignments: messages.AssignmentRecord[]) {
|
||||||
|
this.controller.subscriptions.broadcast(
|
||||||
|
new messages.ResolvedAssignmentUpdatedEvent(assignments),
|
||||||
|
assignments.map(assignment => assignment.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleResolvedAssignmentSubscription(request: lib.SubscriptionRequest) {
|
||||||
|
// Check for any missing assignments to be computed on demand
|
||||||
|
const filters = request.filters.toJSON();
|
||||||
|
if (filters.length) {
|
||||||
|
const missing = filters.filter(name => !this.resolvedAssignments.has(name));
|
||||||
|
if (missing.length) {
|
||||||
|
this.resolvedAssignments.setMany(await this.computeResolvedAssignments(missing));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter the assignments
|
||||||
|
const assignments = (filters.length
|
||||||
|
? filters.map(name => this.resolvedAssignments.get(name)).filter(Boolean)
|
||||||
|
: [...this.resolvedAssignments.values()]
|
||||||
|
).filter(a => a.updatedAtMs > request.lastRequestTimeMs);
|
||||||
|
|
||||||
|
return assignments.length ? new messages.ResolvedAssignmentUpdatedEvent(assignments) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Groups
|
||||||
|
*/
|
||||||
|
|
||||||
|
async handleGroupListRequest() {
|
||||||
|
return [...this.groups.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleGroupCreateRequest(request: messages.GroupCreateRequest) {
|
||||||
|
if ([...this.groups.values()].some(g => g.name === request.name)) {
|
||||||
|
throw new lib.RequestError(`Group with name '${request.name}' already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = Math.random() * 2**31 | 0;
|
||||||
|
while (this.groups.has(id)) {
|
||||||
|
id = Math.random() * 2**31 | 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const group = new messages.GroupRecord(id, request.name, request.permissions);
|
||||||
|
this.groups.set(group);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleGroupUpdateRequest(request: messages.GroupUpdateRequest) {
|
||||||
|
const group = request.group;
|
||||||
|
if (group.id === undefined || !this.groups.has(group.id)) {
|
||||||
|
throw new lib.RequestError(`Group with ID ${group.id} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.groups.set(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleGroupDeleteRequest(request: messages.GroupDeleteRequest) {
|
||||||
|
const { groupId } = request;
|
||||||
|
|
||||||
|
const group = this.groups.getMutable(groupId);
|
||||||
|
if (!group) {
|
||||||
|
throw new lib.RequestError(`Group with ID ${groupId} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.groups.delete(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleGroupGetRequest(request: messages.GroupGetRequest) {
|
||||||
|
const group = this.groups.get(request.groupId);
|
||||||
|
if (!group) {
|
||||||
|
throw new lib.RequestError(`Group with ID ${request.groupId} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Groups
|
||||||
|
*/
|
||||||
|
|
||||||
|
async handleAssignmentListRequest() {
|
||||||
|
return [...this.manualAssignments.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAssignmentCreateRequest(request: messages.AssignmentCreateRequest) {
|
||||||
|
const { name, groupId } = request;
|
||||||
|
if (this.manualAssignments.has(name)) {
|
||||||
|
throw new lib.RequestError(`Assignment for '${name}' already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const assignment = new messages.AssignmentRecord(name, groupId);
|
||||||
|
this.manualAssignments.set(assignment);
|
||||||
|
return assignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAssignmentUpdateRequest(request: messages.AssignmentUpdateRequest) {
|
||||||
|
const assignment = request.assignment;
|
||||||
|
if (!this.manualAssignments.has(assignment.name)) {
|
||||||
|
throw new lib.RequestError(`Assignment for '${assignment.name}' does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.manualAssignments.set(assignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAssignmentDeleteRequest(request: messages.AssignmentDeleteRequest) {
|
||||||
|
const { name } = request;
|
||||||
|
|
||||||
|
const assignment = this.manualAssignments.getMutable(name);
|
||||||
|
if (!assignment) {
|
||||||
|
throw new lib.RequestError(`Assignment for '${name}' does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.manualAssignments.delete(assignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAssignmentGetRequest(request: messages.AssignmentGetRequest) {
|
||||||
|
if (request.resolve) {
|
||||||
|
let assignment = this.resolvedAssignments.get(request.name);
|
||||||
|
if (!assignment) {
|
||||||
|
assignment = await this.computeResolvedAssignment(request.name);
|
||||||
|
this.resolvedAssignments.set(assignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return assignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assignment = this.manualAssignments.get(request.name);
|
||||||
|
if (!assignment) {
|
||||||
|
throw new lib.RequestError(`Assignment for '${request.name}' does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return assignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Role mappings
|
||||||
|
*/
|
||||||
|
|
||||||
|
async handleRoleMappingListRequest() {
|
||||||
|
return [...this.roleMappings.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRoleMappingCreateRequest(request: messages.RoleMappingCreateRequest) {
|
||||||
|
let id = Math.random() * 2**31 | 0;
|
||||||
|
while (this.roleMappings.has(id)) {
|
||||||
|
id = Math.random() * 2**31 | 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let priority = request.priority;
|
||||||
|
const existing = new Set([...this.roleMappings.values()].map(m => m.priority));
|
||||||
|
while (existing.has(priority)) {
|
||||||
|
priority++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleMapping = new messages.RoleMappingRecord(
|
||||||
|
id, new Set(request.roleIds), request.groupId, priority, request.enabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.roleMappings.set(roleMapping);
|
||||||
|
return roleMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRoleMappingUpdateRequest(request: messages.RoleMappingUpdateRequest) {
|
||||||
|
const roleMapping = request.roleMapping;
|
||||||
|
if (roleMapping.id === undefined || !this.roleMappings.has(roleMapping.id)) {
|
||||||
|
throw new lib.RequestError(`Role mapping with ID ${roleMapping.id} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = new Set(
|
||||||
|
[...this.roleMappings.values()]
|
||||||
|
.filter(m => m.id !== roleMapping.id)
|
||||||
|
.map(m => m.priority)
|
||||||
|
);
|
||||||
|
|
||||||
|
let priority = roleMapping.priority;
|
||||||
|
while (existing.has(priority)) {
|
||||||
|
priority++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.roleMappings.set(roleMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRoleMappingDeleteRequest(request: messages.RoleMappingDeleteRequest) {
|
||||||
|
const { id } = request;
|
||||||
|
|
||||||
|
const mapping = this.roleMappings.getMutable(id);
|
||||||
|
if (!mapping) {
|
||||||
|
throw new lib.RequestError(`Role mapping with ID ${id} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.roleMappings.delete(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRoleMappingGetRequest(request: messages.RoleMappingGetRequest) {
|
||||||
|
const mapping = this.roleMappings.get(request.id);
|
||||||
|
if (!mapping) {
|
||||||
|
throw new lib.RequestError(`Role mapping with ID ${request.id} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculating assignments
|
||||||
|
*/
|
||||||
|
|
||||||
|
async computeResolvedAssignment(playerName: string): Promise<messages.AssignmentRecord> {
|
||||||
|
// 1) Manual override
|
||||||
|
const manual = this.manualAssignments.get(playerName);
|
||||||
|
if (manual) {
|
||||||
|
return manual;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = this.controller.users.getByName(playerName);
|
||||||
|
const userRoles = user?.roleIds ?? new Set<number>();
|
||||||
|
|
||||||
|
// 2) Role mappings
|
||||||
|
let best: messages.RoleMappingRecord | null = null;
|
||||||
|
for (const mapping of this.roleMappings.values()) {
|
||||||
|
if (!mapping.enabled) continue;
|
||||||
|
|
||||||
|
let matches = true;
|
||||||
|
for (const roleId of mapping.roleIds) {
|
||||||
|
if (!userRoles.has(roleId)) {
|
||||||
|
matches = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matches) continue;
|
||||||
|
|
||||||
|
if (!best || mapping.priority > best.priority) {
|
||||||
|
best = mapping;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best) {
|
||||||
|
return new messages.AssignmentRecord(playerName, best.groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Default (deleted assignment, assigns to 'Default' in game)
|
||||||
|
return new messages.AssignmentRecord(playerName, 0, 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async computeResolvedAssignments(playerNames: string[]): Promise<messages.AssignmentRecord[]> {
|
||||||
|
return Promise.all(playerNames.map(name => this.computeResolvedAssignment(name)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+170
-66
@@ -1,84 +1,188 @@
|
|||||||
import * as lib from "@clusterio/lib";
|
import * as lib from "@clusterio/lib";
|
||||||
import * as Messages from "./messages";
|
import * as messages from "./messages.js";
|
||||||
|
|
||||||
lib.definePermission({
|
export * from "./messages.js";
|
||||||
name: "exp_groups.create_delete_groups",
|
export type { ControllerPlugin } from "./controller.js";
|
||||||
title: "Create and delete permission groups",
|
|
||||||
description: "Create and delete permission groups.",
|
|
||||||
});
|
|
||||||
|
|
||||||
lib.definePermission({
|
|
||||||
name: "exp_groups.reorder_groups",
|
|
||||||
title: "Reorder permission groups",
|
|
||||||
description: "Reorder groups and link them to user roles.",
|
|
||||||
});
|
|
||||||
|
|
||||||
lib.definePermission({
|
|
||||||
name: "exp_groups.modify_permissions",
|
|
||||||
title: "Modify permission groups",
|
|
||||||
description: "Modify game permissions for groups.",
|
|
||||||
});
|
|
||||||
|
|
||||||
lib.definePermission({
|
|
||||||
name: "exp_groups.assign_players",
|
|
||||||
title: "Change player group",
|
|
||||||
description: "Change the permission group of a player",
|
|
||||||
});
|
|
||||||
|
|
||||||
lib.definePermission({
|
|
||||||
name: "exp_groups.list",
|
|
||||||
title: "View permission groups",
|
|
||||||
description: "View permission groups.",
|
|
||||||
});
|
|
||||||
|
|
||||||
lib.definePermission({
|
|
||||||
name: "exp_groups.list.subscribe",
|
|
||||||
title: "Subscribe to permission group updates",
|
|
||||||
description: "Subscribe to permission group updates.",
|
|
||||||
});
|
|
||||||
|
|
||||||
declare module "@clusterio/lib" {
|
declare module "@clusterio/lib" {
|
||||||
export interface ControllerConfigFields {
|
|
||||||
"exp_groups.allow_role_inconsistency": boolean;
|
|
||||||
}
|
|
||||||
export interface InstanceConfigFields {
|
export interface InstanceConfigFields {
|
||||||
"exp_groups.sync_permission_groups": boolean;
|
"exp_groups.sync_mode": "enabled" | "disabled" | "bidirectional"
|
||||||
|
}
|
||||||
|
export interface ControllerConfigFields {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const plugin: lib.PluginDeclaration = {
|
export const plugin: lib.PluginDeclaration = {
|
||||||
name: "exp_groups",
|
name: "exp_groups",
|
||||||
title: "exp_groups",
|
title: "ExpGaming - Permission Groups",
|
||||||
description: "Create, modify, and link factorio permission groups to clusterio user roles.",
|
description: "Clusterio plugin providing syncing of permission groups",
|
||||||
|
|
||||||
controllerEntrypoint: "./dist/node/controller",
|
features: [
|
||||||
controllerConfigFields: {
|
"SavePatching",
|
||||||
"exp_groups.allow_role_inconsistency": {
|
"ScriptCommands",
|
||||||
title: "Allow User Role Inconsistency",
|
],
|
||||||
description: "When true, users can be assgined to any group regardless of their roles",
|
|
||||||
type: "boolean",
|
|
||||||
initialValue: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
instanceEntrypoint: "./dist/node/instance",
|
|
||||||
instanceConfigFields: {
|
|
||||||
"exp_groups.sync_permission_groups": {
|
|
||||||
title: "Sync Permission Groups",
|
|
||||||
description: "When true, the instance cannot deviate from the global group settings and will be hidden from the sellection dropdown.",
|
|
||||||
type: "boolean",
|
|
||||||
initialValue: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
messages: [
|
messages: [
|
||||||
Messages.PermissionGroupEditEvent,
|
messages.GroupUpdatedEvent,
|
||||||
Messages.PermissionStringsUpdate,
|
messages.ManualAssignmentUpdatedEvent,
|
||||||
Messages.PermissionGroupUpdate,
|
messages.ResolvedAssignmentUpdatedEvent,
|
||||||
|
messages.RoleMappingUpdatedEvent,
|
||||||
|
|
||||||
|
messages.GroupCreateRequest,
|
||||||
|
messages.GroupUpdateRequest,
|
||||||
|
messages.GroupDeleteRequest,
|
||||||
|
messages.GroupGetRequest,
|
||||||
|
messages.GroupListRequest,
|
||||||
|
|
||||||
|
messages.AssignmentCreateRequest,
|
||||||
|
messages.AssignmentUpdateRequest,
|
||||||
|
messages.AssignmentDeleteRequest,
|
||||||
|
messages.AssignmentGetRequest,
|
||||||
|
messages.AssignmentListRequest,
|
||||||
|
|
||||||
|
messages.RoleMappingCreateRequest,
|
||||||
|
messages.RoleMappingUpdateRequest,
|
||||||
|
messages.RoleMappingDeleteRequest,
|
||||||
|
messages.RoleMappingGetRequest,
|
||||||
|
messages.RoleMappingListRequest,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
permissions: [
|
||||||
|
// Group permissions
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "exp_groups.group.get",
|
||||||
|
title: "Get Groups",
|
||||||
|
description: "Retrieve a specific Factorio permission group by ID.",
|
||||||
|
grantByDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.group.list",
|
||||||
|
title: "List Groups",
|
||||||
|
description: "List all Factorio permission groups.",
|
||||||
|
grantByDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.group.subscribe",
|
||||||
|
title: "Subscribe to Group Updates",
|
||||||
|
description: "Receive updates when Factorio permission groups change.",
|
||||||
|
grantByDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.group.create",
|
||||||
|
title: "Create Groups",
|
||||||
|
description: "Create new Factorio permission groups.",
|
||||||
|
grantByDefault: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.group.update",
|
||||||
|
title: "Update Groups",
|
||||||
|
description: "Modify existing Factorio permission groups.",
|
||||||
|
grantByDefault: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.group.delete",
|
||||||
|
title: "Delete Groups",
|
||||||
|
description: "Delete Factorio permission groups.",
|
||||||
|
grantByDefault: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Assignment permissions
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "exp_groups.assignment.get",
|
||||||
|
title: "Get Assignments",
|
||||||
|
description: "Retrieve a specific manual group assignment for a player.",
|
||||||
|
grantByDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.assignment.list",
|
||||||
|
title: "List Assignments",
|
||||||
|
description: "List all manual group assignments.",
|
||||||
|
grantByDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.assignment.subscribe",
|
||||||
|
title: "Subscribe to Assignment Updates",
|
||||||
|
description: "Receive updates when manual group assignments change.",
|
||||||
|
grantByDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.assignment.create",
|
||||||
|
title: "Create Assignments",
|
||||||
|
description: "Manually assign players to groups, overriding role mappings.",
|
||||||
|
grantByDefault: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.assignment.update",
|
||||||
|
title: "Update Assignments",
|
||||||
|
description: "Modify existing manual group assignments.",
|
||||||
|
grantByDefault: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.assignment.delete",
|
||||||
|
title: "Delete Assignments",
|
||||||
|
description: "Remove manual group assignments.",
|
||||||
|
grantByDefault: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Role mapping permissions
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "exp_groups.role_mapping.get",
|
||||||
|
title: "Get Role Mappings",
|
||||||
|
description: "Retrieve a specific role mapping rule.",
|
||||||
|
grantByDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.role_mapping.list",
|
||||||
|
title: "List Role Mappings",
|
||||||
|
description: "List all role mapping rules.",
|
||||||
|
grantByDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.role_mapping.subscribe",
|
||||||
|
title: "Subscribe to Role Mapping Updates",
|
||||||
|
description: "Receive updates when role mapping rules change.",
|
||||||
|
grantByDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.role_mapping.create",
|
||||||
|
title: "Create Role Mappings",
|
||||||
|
description: "Create rules that map user roles to Factorio permission groups.",
|
||||||
|
grantByDefault: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.role_mapping.update",
|
||||||
|
title: "Update Role Mappings",
|
||||||
|
description: "Modify existing role mapping rules.",
|
||||||
|
grantByDefault: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exp_groups.role_mapping.delete",
|
||||||
|
title: "Delete Role Mappings",
|
||||||
|
description: "Delete role mapping rules.",
|
||||||
|
grantByDefault: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
instanceEntrypoint: "./dist/node/instance.js",
|
||||||
|
instanceConfigFields: {
|
||||||
|
"exp_groups.sync_mode": {
|
||||||
|
description: "Synchronize permission groups with the controller",
|
||||||
|
type: "string",
|
||||||
|
enum: ["disabled", "enabled", "bidirectional"],
|
||||||
|
initialValue: "bidirectional",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
controllerEntrypoint: "./dist/node/controller.js",
|
||||||
|
controllerConfigFields: {
|
||||||
|
},
|
||||||
|
|
||||||
webEntrypoint: "./web",
|
webEntrypoint: "./web",
|
||||||
routes: [
|
routes: [
|
||||||
"/exp_groups",
|
"/permission_groups",
|
||||||
],
|
"/permission_groups/:id/view",
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
+152
-110
@@ -1,129 +1,171 @@
|
|||||||
import * as lib from "@clusterio/lib";
|
|
||||||
import { BaseInstancePlugin } from "@clusterio/host";
|
import { BaseInstancePlugin } from "@clusterio/host";
|
||||||
import {
|
import * as lib from "@clusterio/lib";
|
||||||
PermissionGroup, PermissionGroupEditEvent, PermissionGroupEditType,
|
import * as messages from "./messages.js";
|
||||||
PermissionGroupUpdate, PermissionInstanceId, PermissionStrings, PermissionStringsUpdate
|
|
||||||
} from "./messages";
|
|
||||||
|
|
||||||
const rconBase = "/sc local Groups = package.loaded['modules/exp_groups/module_exports'];"
|
export type IpcGroupUpdated = {
|
||||||
|
group_name: string,
|
||||||
type EditIPC = {
|
group_id: number | undefined,
|
||||||
type: PermissionGroupEditType,
|
permissions: { is_blacklist: boolean, permissions: string[] | undefined },
|
||||||
changes: string[],
|
|
||||||
group: string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type CreateIPC = {
|
export type IpcGroupDeleted = {
|
||||||
group: string,
|
group_name: string,
|
||||||
defiantion: [boolean, string[] | {}]
|
group_id: number | undefined,
|
||||||
}
|
};
|
||||||
|
|
||||||
type DeleteIPC = {
|
export type IpcPlayerAssignments = {
|
||||||
group: string,
|
assignments: Record<string, number>,
|
||||||
}
|
};
|
||||||
|
|
||||||
export class InstancePlugin extends BaseInstancePlugin {
|
export class InstancePlugin extends BaseInstancePlugin {
|
||||||
permissions: Set<string> = new Set();
|
// Once only, don't send permissions for these groups
|
||||||
permissionGroups = new lib.EventSubscriber(PermissionGroupUpdate, this.instance);
|
// This is used for groups created on this instance that only need the controller generated id
|
||||||
permissionGroupUpdates = new lib.EventSubscriber(PermissionGroupEditEvent, this.instance);
|
skipSendingPermissions = new Set<string>();
|
||||||
syncId: PermissionInstanceId = this.instance.config.get("exp_groups.sync_permission_groups") ? "Global" : this.instance.id;
|
// This is used for groups updated / deleted on this instance to stop cycles
|
||||||
|
skipSendingUpdate = new Set<number>();
|
||||||
|
// Track known online players so that we only apply assignment updates for them
|
||||||
|
onlinePlayers = new Set<string>();
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
this.instance.server.handle("exp_groups-permission_group_edit", this.handleEditIPC.bind(this));
|
this.instance.handle(messages.GroupUpdatedEvent, this.handleGroupUpdatedEvent.bind(this));
|
||||||
this.instance.server.handle("exp_groups-permission_group_create", this.handleCreateIPC.bind(this));
|
this.instance.handle(messages.ResolvedAssignmentUpdatedEvent, this.handleResolvedAssignmentUpdatedEvent.bind(this));
|
||||||
this.instance.server.handle("exp_groups-permission_group_delete", this.handleDeleteIPC.bind(this));
|
this.instance.server.handle(`exp_group:group_updated`, this.handleGroupUpdatedIPC.bind(this))
|
||||||
}
|
this.instance.server.handle(`exp_group:group_deleted`, this.handleGroupDeletedIPC.bind(this))
|
||||||
|
this.instance.server.handle(`exp_group:player_assignments`, this.handlePlayerAssignmentsIPC.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
async onStart() {
|
async onInstanceConfigFieldChanged(field: string, curr: unknown, prev: unknown) {
|
||||||
// Send the most recent version of the permission string
|
switch(field) {
|
||||||
const permissionsString = await this.sendRcon(rconBase + "rcon.print(Groups.get_actions_json())");
|
case "exp_groups.sync_mode":
|
||||||
this.permissions = new Set(JSON.parse(permissionsString));
|
await this.luaSetEmitEvents(curr == "bidirectional")
|
||||||
this.instance.sendTo("controller", new PermissionStringsUpdate([
|
break;
|
||||||
new PermissionStrings(this.instance.id, this.permissions, Date.now())
|
}
|
||||||
]));
|
}
|
||||||
|
|
||||||
// Subscribe to get updates for permission groups
|
async onStart() {
|
||||||
this.permissionGroups.subscribe(this.onPermissionGroupsUpdate.bind(this));
|
// We use Date.now() because we need to manually initialise the groups on the lua side
|
||||||
this.permissionGroupUpdates.subscribe(this.onPermissionGroupUpdate.bind(this));
|
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
|
||||||
}
|
`exp_groups:${messages.GroupUpdatedEvent.name}`, "subscribe", Date.now()
|
||||||
|
));
|
||||||
|
const groups = await this.instance.sendTo("controller", new messages.GroupListRequest())
|
||||||
|
await this.luaSendInitialGroups(groups);
|
||||||
|
await this.luaSetEmitEvents(this.instance.config.get("exp_groups.sync_mode") == "bidirectional")
|
||||||
|
}
|
||||||
|
|
||||||
async onControllerConnectionEvent(event: any) {
|
async onPlayerEvent(event: lib.PlayerEvent) {
|
||||||
this.permissionGroups.handleConnectionEvent(event);
|
switch(event.type) {
|
||||||
}
|
case "join":
|
||||||
|
this.onlinePlayers.add(event.name);
|
||||||
|
await this.subscribePlayerAssignment(event.name);
|
||||||
|
break;
|
||||||
|
case "leave":
|
||||||
|
this.onlinePlayers.delete(event.name);
|
||||||
|
await this.unsubscribePlayerAssignment(event.name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async onInstanceConfigFieldChanged(field: string, curr: unknown, prev: unknown) {
|
async handleGroupUpdatedEvent(event: messages.GroupUpdatedEvent) {
|
||||||
if (field === "exp_groups.sync_permission_groups") {
|
for (const group of event.updates) {
|
||||||
this.syncId = curr ? "Global" : this.instance.id;
|
await this.luaSendGroupUpdate(group);
|
||||||
const [snapshot, synced] = this.permissionGroups.getSnapshot();
|
}
|
||||||
if (synced && this.instance.status !== "running") await this.syncPermissionGroups(snapshot.values());
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async onPermissionGroupsUpdate(event: PermissionGroupUpdate | null, synced: boolean) {
|
async handleResolvedAssignmentUpdatedEvent(event: messages.ManualAssignmentUpdatedEvent) {
|
||||||
if (!synced || this.instance.status !== "running" || !event?.updates.length) return;
|
for (const assignment of event.updates) {
|
||||||
await this.syncPermissionGroups(event.updates);
|
if (this.onlinePlayers.has(assignment.name)) {
|
||||||
}
|
await this.luaSendAssignmentUpdate(assignment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async syncPermissionGroups(groups: Iterable<PermissionGroup>) {
|
async handleGroupUpdatedIPC(event: IpcGroupUpdated) {
|
||||||
const updateCommands = [rconBase];
|
const permissions = new messages.GroupPermissions(
|
||||||
for (const group of groups) {
|
event.permissions.is_blacklist,
|
||||||
if (group.instanceId === this.syncId && group.updatedAtMs > (this.permissionGroups.values.get(group.id)?.updatedAtMs ?? 0)) {
|
event.permissions.permissions ?? [],
|
||||||
if (group.isDeleted) {
|
)
|
||||||
updateCommands.push(`Groups.destroy_group('${group.name}')`);
|
|
||||||
} else if (group.permissions.size < this.permissions.size / 2) {
|
|
||||||
updateCommands.push(`Groups.get_or_create('${group.name}'):from_json('${JSON.stringify([false, [...this.permissions.values()]])}')`);
|
|
||||||
} else {
|
|
||||||
const inverted = [...this.permissions.values()].filter(permission => !group.permissions.has(permission));
|
|
||||||
updateCommands.push(`Groups.get_or_create('${group.name}'):from_json('${JSON.stringify([true, inverted])}')`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await this.sendRcon(updateCommands.join(";"), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
async onPermissionGroupUpdate(event: PermissionGroupEditEvent | null, synced: boolean) {
|
if (event.group_id === undefined) {
|
||||||
if (!synced || this.instance.status !== "running" || !event) return;
|
this.skipSendingPermissions.add(event.group_name);
|
||||||
if (event.src.equals(lib.Address.fromShorthand({ instanceId: this.instance.id }))) return;
|
await this.instance.sendTo("controller",
|
||||||
const getCmd = `Groups.get_or_create('${event.group}')`;
|
new messages.GroupCreateRequest(event.group_name, permissions),
|
||||||
if (event.type === "add_permissions") {
|
);
|
||||||
await this.sendRcon(rconBase + getCmd + `:allow_actions(Groups.json_to_actions('${JSON.stringify(event.changes)}'))`);
|
} else {
|
||||||
} else if (event.type === "remove_permissions") {
|
this.skipSendingUpdate.add(event.group_id);
|
||||||
await this.sendRcon(rconBase + getCmd + `:disallow_actions(Groups.json_to_actions('${JSON.stringify(event.changes)}'))`);
|
await this.instance.sendTo("controller", new messages.GroupUpdateRequest(
|
||||||
} else if (event.type === "assign_players") {
|
new messages.GroupRecord(event.group_id, event.group_name, permissions),
|
||||||
await this.sendRcon(rconBase + getCmd + `:add_players(game.json_to_table('${JSON.stringify(event.changes)}'))`);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleEditIPC(event: EditIPC) {
|
async handleGroupDeletedIPC(event: IpcGroupDeleted) {
|
||||||
this.logger.info(JSON.stringify(event))
|
if (event.group_id === undefined) {
|
||||||
this.instance.sendTo("controller", new PermissionGroupEditEvent(
|
return;
|
||||||
lib.Address.fromShorthand({ instanceId: this.instance.id }),
|
}
|
||||||
event.type, event.group, event.changes
|
this.skipSendingUpdate.add(event.group_id);
|
||||||
))
|
await this.instance.sendTo("controller", new messages.GroupDeleteRequest(event.group_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCreateIPC(event: CreateIPC) {
|
async handlePlayerAssignmentsIPC(event: IpcPlayerAssignments) {
|
||||||
this.logger.info(JSON.stringify(event))
|
await Promise.all(
|
||||||
if (!this.permissionGroups.synced) return;
|
Object.entries(event.assignments).map(([playerName, groupId]) =>
|
||||||
let [defaultAllow, permissionsRaw] = event.defiantion;
|
this.instance.sendTo("controller",
|
||||||
if (!Array.isArray(permissionsRaw)) {
|
new messages.AssignmentUpdateRequest(new messages.AssignmentRecord(playerName, groupId)),
|
||||||
permissionsRaw = [] // lua outputs {} for empty arrays
|
)
|
||||||
}
|
)
|
||||||
const permissions = [...this.permissions.values()]
|
);
|
||||||
.filter(permission => defaultAllow !== (permissionsRaw as String[]).includes(permission));
|
}
|
||||||
this.instance.sendTo("controller", new PermissionGroupUpdate([ new PermissionGroup(
|
|
||||||
this.syncId, event.group, 0, new Set(), new Set(permissions)
|
|
||||||
) ]));
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleDeleteIPC(event: DeleteIPC) {
|
async subscribePlayerAssignment(playerName: string) {
|
||||||
if (!this.permissionGroups.synced) return;
|
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
|
||||||
const group = [...this.permissionGroups.values.values()]
|
`exp_groups:${messages.ResolvedAssignmentUpdatedEvent.name}`, "subscribe", 0, lib.SubscriptionFilters.fromShorthand(playerName)
|
||||||
.find(group => group.instanceId === this.syncId && group.name === event.group);
|
));
|
||||||
if (group) {
|
}
|
||||||
group.updatedAtMs = Date.now();
|
|
||||||
group.isDeleted = true;
|
async unsubscribePlayerAssignment(playerName: string) {
|
||||||
this.instance.sendTo("controller", new PermissionGroupUpdate([ group ]));
|
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
|
||||||
}
|
`exp_groups:${messages.ResolvedAssignmentUpdatedEvent.name}`, "unsubscribe", 0, lib.SubscriptionFilters.fromShorthand(playerName)
|
||||||
}
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
async luaSendInitialGroups(groups: messages.GroupRecord[]) {
|
||||||
|
if (this.instance.config.get("exp_groups.sync_mode") === "disabled") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.luaSend("initialise_groups", groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
async luaSendGroupUpdate(group: messages.GroupRecord) {
|
||||||
|
if (this.instance.config.get("exp_groups.sync_mode") === "disabled") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.skipSendingUpdate.has(group.id)) {
|
||||||
|
this.skipSendingUpdate.delete(group.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = group.toJSON();
|
||||||
|
if (this.skipSendingPermissions.has(group.name)) {
|
||||||
|
this.skipSendingPermissions.delete(group.name);
|
||||||
|
delete (json as any).permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.luaSend("receive_group_update", json);
|
||||||
|
}
|
||||||
|
|
||||||
|
async luaSendAssignmentUpdate(assignment: messages.AssignmentRecord) {
|
||||||
|
if (this.instance.config.get("exp_groups.sync_mode") === "disabled") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.luaSend("receive_assignment_update", assignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
async luaSetEmitEvents(emitEvents: boolean) {
|
||||||
|
await this.luaSend("set_emit_events", emitEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
async luaSend(receiver: string, json: any) {
|
||||||
|
await this.instance.sendRcon(`/sc exp_groups.${receiver}(helpers.json_to_table[=[${JSON.stringify(json)}]=])`, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+683
-206
@@ -1,239 +1,716 @@
|
|||||||
import { User, InstanceDetails, IControllerUser, Link, MessageRequest, StringEnum, PermissionError, Address } from "@clusterio/lib";
|
import * as lib from "@clusterio/lib";
|
||||||
import { Type, Static } from "@sinclair/typebox";
|
import { Type, Static } from "@sinclair/typebox";
|
||||||
|
|
||||||
export const PermissionInstanceIdSchema = Type.Union([InstanceDetails.jsonSchema.properties.id, Type.Literal("Global")])
|
// Declared here rather than in index.ts because the browser project cannot include index.ts, which re-exports controller.ts
|
||||||
export type PermissionInstanceId = InstanceDetails["id"] | "Global"
|
declare module "@clusterio/lib" {
|
||||||
export type GamePermission = string; // todo: maybe enum this?
|
export interface Permissions {
|
||||||
|
"exp_groups.group.get": never;
|
||||||
/**
|
"exp_groups.group.list": never;
|
||||||
* Data class for permission groups
|
"exp_groups.group.subscribe": never;
|
||||||
*/
|
"exp_groups.group.create": never;
|
||||||
export class PermissionGroup {
|
"exp_groups.group.update": never;
|
||||||
constructor(
|
"exp_groups.group.delete": never;
|
||||||
public instanceId: PermissionInstanceId,
|
"exp_groups.assignment.get": never;
|
||||||
public name: string,
|
"exp_groups.assignment.list": never;
|
||||||
/** A lower order assumes a lower permission group */
|
"exp_groups.assignment.subscribe": never;
|
||||||
public order: number = 0,
|
"exp_groups.assignment.create": never;
|
||||||
/** A role will use the highest order group it is apart of */
|
"exp_groups.assignment.update": never;
|
||||||
public roleIds: User["roleIds"] = new Set(),
|
"exp_groups.assignment.delete": never;
|
||||||
public permissions: Set<GamePermission> = new Set(),
|
"exp_groups.role_mapping.get": never;
|
||||||
public updatedAtMs: number = 0,
|
"exp_groups.role_mapping.list": never;
|
||||||
public isDeleted: boolean = false,
|
"exp_groups.role_mapping.subscribe": never;
|
||||||
) {
|
"exp_groups.role_mapping.create": never;
|
||||||
}
|
"exp_groups.role_mapping.update": never;
|
||||||
|
"exp_groups.role_mapping.delete": never;
|
||||||
static jsonSchema = Type.Object({
|
|
||||||
instanceId: PermissionInstanceIdSchema,
|
|
||||||
name: Type.String(),
|
|
||||||
order: Type.Number(),
|
|
||||||
roleIds: Type.Array(Type.Number()),
|
|
||||||
permissions: Type.Array(Type.String()),
|
|
||||||
updatedAtMs: Type.Optional(Type.Number()),
|
|
||||||
isDeleted: Type.Optional(Type.Boolean()),
|
|
||||||
});
|
|
||||||
|
|
||||||
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
||||||
return new this(
|
|
||||||
json.instanceId,
|
|
||||||
json.name,
|
|
||||||
json.order,
|
|
||||||
new Set(json.roleIds),
|
|
||||||
new Set(json.permissions),
|
|
||||||
json.updatedAtMs,
|
|
||||||
json.isDeleted
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): Static<typeof PermissionGroup.jsonSchema> {
|
|
||||||
return {
|
|
||||||
instanceId: this.instanceId,
|
|
||||||
name: this.name,
|
|
||||||
order: this.order,
|
|
||||||
roleIds: [...this.roleIds.values()],
|
|
||||||
permissions: [...this.permissions.values()],
|
|
||||||
updatedAtMs: this.updatedAtMs > 0 ? this.updatedAtMs : undefined,
|
|
||||||
isDeleted: this.isDeleted ? this.isDeleted : undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get id() {
|
|
||||||
return `${this.instanceId}:${this.name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(newInstanceId: PermissionInstanceId) {
|
|
||||||
return new PermissionGroup(
|
|
||||||
newInstanceId,
|
|
||||||
this.name,
|
|
||||||
this.order,
|
|
||||||
new Set(this.roleIds),
|
|
||||||
new Set(this.permissions),
|
|
||||||
Date.now(),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InstancePermissionGroups {
|
/*
|
||||||
constructor(
|
Data records
|
||||||
public instanceId: PermissionInstanceId,
|
*/
|
||||||
public groups: Map<PermissionGroup["name"], PermissionGroup> = new Map(),
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
static jsonSchema = Type.Object({
|
export class GroupPermissions {
|
||||||
instanceId: PermissionInstanceIdSchema,
|
constructor(
|
||||||
permissionsGroups: Type.Array(PermissionGroup.jsonSchema),
|
public isBlacklist: boolean,
|
||||||
});
|
public permissions: string[],
|
||||||
|
) {}
|
||||||
|
|
||||||
static fromJSON(json: Static<typeof InstancePermissionGroups.jsonSchema>) {
|
static jsonSchema = Type.Object({
|
||||||
return new InstancePermissionGroups(
|
is_blacklist: Type.Boolean(),
|
||||||
json.instanceId,
|
permissions: Type.Array(Type.String()),
|
||||||
new Map(json.permissionsGroups.map(group => [group.name, PermissionGroup.fromJSON(group)])),
|
});
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
toJSON(): Static<typeof GroupPermissions.jsonSchema> {
|
||||||
return {
|
return {
|
||||||
instanceId: this.instanceId,
|
is_blacklist: this.isBlacklist,
|
||||||
permissionsGroups: [...this.groups.values()],
|
permissions: this.permissions,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserGroup(user: User) {
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
const groups = [...user.roleIds.values()].map(roleId =>
|
return new this(
|
||||||
// There will always be one and only one group for each role
|
json.is_blacklist,
|
||||||
[...this.groups.values()].find(group => group.roleIds.has(roleId))!
|
json.permissions,
|
||||||
);
|
);
|
||||||
return groups.reduce((highest, group) => highest.order > group.order ? highest : group);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
get id() {
|
|
||||||
return this.instanceId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PermissionGroupUpdate {
|
export class GroupRecord {
|
||||||
declare ["constructor"]: typeof PermissionGroupUpdate;
|
constructor(
|
||||||
static type = "event" as const;
|
public id: number,
|
||||||
static src = ["controller", "instance"] as const;
|
public name: string,
|
||||||
static dst = ["control", "instance", "controller"] as const;
|
public permissions: GroupPermissions,
|
||||||
static plugin = "exp_groups" as const;
|
public updatedAtMs: number = 0,
|
||||||
static permission = "exp_groups.list.subscribe";
|
public isDeleted: boolean = false,
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor(
|
static jsonSchema = Type.Object({
|
||||||
public updates: PermissionGroup[],
|
id: Type.Integer(),
|
||||||
) { }
|
name: Type.String(),
|
||||||
|
permissions: GroupPermissions.jsonSchema,
|
||||||
|
updated_at_ms: Type.Optional(Type.Number()),
|
||||||
|
is_deleted: Type.Optional(Type.Boolean()),
|
||||||
|
});
|
||||||
|
|
||||||
static jsonSchema = Type.Object({
|
toJSON() {
|
||||||
"updates": Type.Array(PermissionGroup.jsonSchema),
|
let json: Static<typeof GroupRecord.jsonSchema> = {
|
||||||
});
|
id: this.id,
|
||||||
|
name: this.name,
|
||||||
|
permissions: this.permissions.toJSON(),
|
||||||
|
};
|
||||||
|
|
||||||
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
if (this.updatedAtMs) {
|
||||||
return new this(
|
json.updated_at_ms = this.updatedAtMs;
|
||||||
json.updates.map(update => PermissionGroup.fromJSON(update))
|
}
|
||||||
);
|
|
||||||
}
|
if (this.isDeleted) {
|
||||||
|
json.is_deleted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(
|
||||||
|
json.id,
|
||||||
|
json.name,
|
||||||
|
GroupPermissions.fromJSON(json.permissions),
|
||||||
|
json.updated_at_ms ?? 0,
|
||||||
|
json.is_deleted ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PermissionGroupEditType = "assign_players" | "add_permissions" | "remove_permissions";
|
export class AssignmentRecord {
|
||||||
|
constructor(
|
||||||
|
public name: string,
|
||||||
|
public groupId: number,
|
||||||
|
public updatedAtMs: number = 0,
|
||||||
|
public isDeleted: boolean = false,
|
||||||
|
) {}
|
||||||
|
|
||||||
export class PermissionGroupEditEvent {
|
get id() {
|
||||||
declare ["constructor"]: typeof PermissionGroupEditEvent;
|
return this.name;
|
||||||
static type = "event" as const;
|
}
|
||||||
static src = ["instance", "controller"] as const;
|
|
||||||
static dst = ["control", "instance", "controller"] as const;
|
|
||||||
static plugin = "exp_groups" as const;
|
|
||||||
|
|
||||||
static permission(user: IControllerUser, message: MessageRequest) {
|
static jsonSchema = Type.Object({
|
||||||
if (typeof message.data === "object" && message.data !== null) {
|
name: Type.String(),
|
||||||
const data = message.data as Static<typeof PermissionGroupEditEvent.jsonSchema>;
|
group_id: Type.Integer(),
|
||||||
if (data.type === "add_permissions" || data.type === "remove_permissions") {
|
updated_at_ms: Type.Optional(Type.Number()),
|
||||||
user.checkPermission("exp_groups.modify_permissions")
|
is_deleted: Type.Optional(Type.Boolean()),
|
||||||
} else if (data.type === "assign_players") {
|
});
|
||||||
user.checkPermission("exp_groups.assign_players")
|
|
||||||
} else {
|
|
||||||
throw new PermissionError("Permission denied");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
toJSON(): Static<typeof AssignmentRecord.jsonSchema> {
|
||||||
public src: Address,
|
let json: Static<typeof AssignmentRecord.jsonSchema> = {
|
||||||
public type: PermissionGroupEditType,
|
name: this.name,
|
||||||
public group: string,
|
group_id: this.groupId,
|
||||||
public changes: String[],
|
};
|
||||||
) { }
|
|
||||||
|
|
||||||
static jsonSchema = Type.Object({
|
if (this.updatedAtMs) {
|
||||||
"src": Address.jsonSchema,
|
json.updated_at_ms = this.updatedAtMs;
|
||||||
"type": StringEnum(["assign_players", "add_permissions", "remove_permissions"]),
|
}
|
||||||
"group": Type.String(),
|
|
||||||
"changes": Type.Array(Type.String()),
|
|
||||||
});
|
|
||||||
|
|
||||||
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
if (this.isDeleted) {
|
||||||
return new this(Address.fromJSON(json.src), json.type, json.group, json.changes);
|
json.is_deleted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(
|
||||||
|
json.name,
|
||||||
|
json.group_id,
|
||||||
|
json.updated_at_ms ?? 0,
|
||||||
|
json.is_deleted ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PermissionStrings {
|
export class RoleMappingRecord {
|
||||||
constructor(
|
constructor(
|
||||||
public instanceId: PermissionInstanceId,
|
public id: number,
|
||||||
public permissions: Set<GamePermission>,
|
public roleIds: Set<number>,
|
||||||
public updatedAtMs: number = 0,
|
public groupId: number,
|
||||||
public isDeleted: boolean = false,
|
public priority: number,
|
||||||
) {
|
public enabled: boolean,
|
||||||
}
|
public updatedAtMs: number = 0,
|
||||||
|
public isDeleted: boolean = false,
|
||||||
|
) {}
|
||||||
|
|
||||||
static jsonSchema = Type.Object({
|
static jsonSchema = Type.Object({
|
||||||
instanceId: PermissionInstanceIdSchema,
|
id: Type.Integer(),
|
||||||
permissions: Type.Array(Type.String()),
|
role_ids: Type.Array(Type.Integer()),
|
||||||
updatedAtMs: Type.Optional(Type.Number()),
|
group_id: Type.Integer(),
|
||||||
isDeleted: Type.Optional(Type.Boolean()),
|
priority: Type.Number(),
|
||||||
});
|
enabled: Type.Boolean(),
|
||||||
|
updated_at_ms: Type.Optional(Type.Number()),
|
||||||
|
is_deleted: Type.Optional(Type.Boolean()),
|
||||||
|
});
|
||||||
|
|
||||||
static fromJSON(json: Static<typeof PermissionStrings.jsonSchema>) {
|
toJSON(): Static<typeof RoleMappingRecord.jsonSchema> {
|
||||||
return new PermissionStrings(
|
let json: Static<typeof RoleMappingRecord.jsonSchema> = {
|
||||||
json.instanceId,
|
id: this.id,
|
||||||
new Set(json.permissions),
|
role_ids: [...this.roleIds],
|
||||||
json.updatedAtMs,
|
group_id: this.groupId,
|
||||||
json.isDeleted
|
priority: this.priority,
|
||||||
);
|
enabled: this.enabled,
|
||||||
}
|
};
|
||||||
|
|
||||||
toJSON() {
|
if (this.updatedAtMs) {
|
||||||
return {
|
json.updated_at_ms = this.updatedAtMs;
|
||||||
instanceId: this.instanceId,
|
}
|
||||||
permissions: [...this.permissions.values()],
|
|
||||||
updatedAtMs: this.updatedAtMs > 0 ? this.updatedAtMs : undefined,
|
|
||||||
isDeleted: this.isDeleted ? this.isDeleted : undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get id() {
|
if (this.isDeleted) {
|
||||||
return this.instanceId
|
json.is_deleted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(
|
||||||
|
json.id,
|
||||||
|
new Set(json.role_ids),
|
||||||
|
json.group_id,
|
||||||
|
json.priority,
|
||||||
|
json.enabled,
|
||||||
|
json.updated_at_ms ?? 0,
|
||||||
|
json.is_deleted ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PermissionStringsUpdate {
|
/*
|
||||||
declare ["constructor"]: typeof PermissionStringsUpdate;
|
Update events
|
||||||
static type = "event" as const;
|
*/
|
||||||
static src = ["instance", "controller"] as const;
|
|
||||||
static dst = ["controller", "control"] as const;
|
|
||||||
static plugin = "exp_groups" as const;
|
|
||||||
static permission = "exp_groups.list.subscribe";
|
|
||||||
|
|
||||||
constructor(
|
export class GroupUpdatedEvent {
|
||||||
public updates: PermissionStrings[],
|
declare ["constructor"]: typeof GroupUpdatedEvent;
|
||||||
) { }
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "event" as const;
|
||||||
|
static src = "controller" as const;
|
||||||
|
static dst = ["control", "instance"] as const;
|
||||||
|
static permission = "exp_groups.group.subscribe" as const;
|
||||||
|
|
||||||
static jsonSchema = Type.Object({
|
constructor(
|
||||||
"updates": Type.Array(PermissionStrings.jsonSchema),
|
public updates: GroupRecord[],
|
||||||
});
|
) {}
|
||||||
|
|
||||||
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
static jsonSchema = Type.Object({
|
||||||
return new this(
|
updates: Type.Array(GroupRecord.jsonSchema),
|
||||||
json.updates.map(update => PermissionStrings.fromJSON(update))
|
});
|
||||||
);
|
|
||||||
}
|
toJSON() {
|
||||||
|
return { updates: this.updates.map(group => group.toJSON()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(json.updates.map(group => GroupRecord.fromJSON(group)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ManualAssignmentUpdatedEvent {
|
||||||
|
declare ["constructor"]: typeof ManualAssignmentUpdatedEvent;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "event" as const;
|
||||||
|
static src = "controller" as const;
|
||||||
|
static dst = ["control", "instance"] as const;
|
||||||
|
static permission = "exp_groups.assignment.subscribe" as const;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public updates: AssignmentRecord[],
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
updates: Type.Array(AssignmentRecord.jsonSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return { updates: this.updates.map(assignment => assignment.toJSON()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(json.updates.map(assignment => AssignmentRecord.fromJSON(assignment)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ResolvedAssignmentUpdatedEvent {
|
||||||
|
declare ["constructor"]: typeof ResolvedAssignmentUpdatedEvent;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "event" as const;
|
||||||
|
static src = "controller" as const;
|
||||||
|
static dst = ["control", "instance"] as const;
|
||||||
|
static permission = "exp_groups.assignment.subscribe" as const;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public updates: AssignmentRecord[],
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
updates: Type.Array(AssignmentRecord.jsonSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return { updates: this.updates.map(assignment => assignment.toJSON()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(json.updates.map(assignment => AssignmentRecord.fromJSON(assignment)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RoleMappingUpdatedEvent {
|
||||||
|
declare ["constructor"]: typeof RoleMappingUpdatedEvent;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "event" as const;
|
||||||
|
static src = "controller" as const;
|
||||||
|
static dst = "control" as const;
|
||||||
|
static permission = "exp_groups.role_mapping.subscribe" as const;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public updates: RoleMappingRecord[],
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
updates: Type.Array(RoleMappingRecord.jsonSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return { updates: this.updates.map(roleMapping => roleMapping.toJSON()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(json.updates.map(roleMapping => RoleMappingRecord.fromJSON(roleMapping)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Group requests
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class GroupCreateRequest {
|
||||||
|
declare ["constructor"]: typeof GroupCreateRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.group.create" as const;
|
||||||
|
static Response = GroupRecord;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public name: string,
|
||||||
|
public permissions: GroupPermissions,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
name: Type.String(),
|
||||||
|
permissions: GroupPermissions.jsonSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
name: this.name,
|
||||||
|
permissions: this.permissions.toJSON(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(
|
||||||
|
json.name,
|
||||||
|
GroupPermissions.fromJSON(json.permissions),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GroupUpdateRequest {
|
||||||
|
declare ["constructor"]: typeof GroupUpdateRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.group.update" as const;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public group: GroupRecord,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
group: GroupRecord.jsonSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
group: this.group.toJSON(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(
|
||||||
|
GroupRecord.fromJSON(json.group),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GroupDeleteRequest {
|
||||||
|
declare ["constructor"]: typeof GroupDeleteRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.group.delete" as const;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public groupId: number,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
group_id: Type.Integer(),
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
group_id: this.groupId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(json.group_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GroupGetRequest {
|
||||||
|
declare ["constructor"]: typeof GroupGetRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.group.get" as const;
|
||||||
|
static Response = GroupRecord;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public groupId: number,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
group_id: Type.Integer(),
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
group_id: this.groupId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(json.group_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GroupListRequest {
|
||||||
|
declare ["constructor"]: typeof GroupListRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.group.list" as const;
|
||||||
|
static Response = lib.jsonArray(GroupRecord);
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Assignment requests
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class AssignmentCreateRequest {
|
||||||
|
declare ["constructor"]: typeof AssignmentCreateRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.assignment.create" as const;
|
||||||
|
static Response = AssignmentRecord;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public name: string,
|
||||||
|
public groupId: number,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
name: Type.String(),
|
||||||
|
group_id: Type.Integer(),
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
name: this.name,
|
||||||
|
group_id: this.groupId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(json.name, json.group_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AssignmentUpdateRequest {
|
||||||
|
declare ["constructor"]: typeof AssignmentUpdateRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.assignment.update" as const;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public assignment: AssignmentRecord,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
assignment: AssignmentRecord.jsonSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
assignment: this.assignment.toJSON(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(
|
||||||
|
AssignmentRecord.fromJSON(json.assignment),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AssignmentDeleteRequest {
|
||||||
|
declare ["constructor"]: typeof AssignmentDeleteRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.assignment.delete" as const;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public name: string,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
name: Type.String(),
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
name: this.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(json.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AssignmentGetRequest {
|
||||||
|
declare ["constructor"]: typeof AssignmentGetRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.assignment.get" as const;
|
||||||
|
static Response = AssignmentRecord;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public name: string,
|
||||||
|
public resolve: boolean = false,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
name: Type.String(),
|
||||||
|
resolve: Type.Boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
name: this.name,
|
||||||
|
resolve: this.resolve,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(json.name, json.resolve);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AssignmentListRequest {
|
||||||
|
declare ["constructor"]: typeof AssignmentListRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.assignment.list" as const;
|
||||||
|
static Response = lib.jsonArray(AssignmentRecord);
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Role mapping requests
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class RoleMappingCreateRequest {
|
||||||
|
declare ["constructor"]: typeof RoleMappingCreateRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.role_mapping.create" as const;
|
||||||
|
static Response = RoleMappingRecord;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public roleIds: number[],
|
||||||
|
public groupId: number,
|
||||||
|
public priority: number,
|
||||||
|
public enabled: boolean,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
role_ids: Type.Array(Type.Integer()),
|
||||||
|
group_id: Type.Integer(),
|
||||||
|
priority: Type.Number(),
|
||||||
|
enabled: Type.Boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
role_ids: this.roleIds,
|
||||||
|
group_id: this.groupId,
|
||||||
|
priority: this.priority,
|
||||||
|
enabled: this.enabled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(
|
||||||
|
json.role_ids,
|
||||||
|
json.group_id,
|
||||||
|
json.priority,
|
||||||
|
json.enabled,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RoleMappingUpdateRequest {
|
||||||
|
declare ["constructor"]: typeof RoleMappingUpdateRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.role_mapping.update" as const;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public roleMapping: RoleMappingRecord,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
role_mapping: RoleMappingRecord.jsonSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
role_mapping: this.roleMapping.toJSON(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(
|
||||||
|
RoleMappingRecord.fromJSON(json.role_mapping),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RoleMappingDeleteRequest {
|
||||||
|
declare ["constructor"]: typeof RoleMappingDeleteRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.role_mapping.delete" as const;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public id: number,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
id: Type.Integer(),
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(json.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RoleMappingGetRequest {
|
||||||
|
declare ["constructor"]: typeof RoleMappingGetRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.role_mapping.get" as const;
|
||||||
|
static Response = RoleMappingRecord;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public id: number,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
id: Type.Integer(),
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(json.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RoleMappingListRequest {
|
||||||
|
declare ["constructor"]: typeof RoleMappingListRequest;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = ["control", "instance"] as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "exp_groups.role_mapping.list" as const;
|
||||||
|
static Response = lib.jsonArray(RoleMappingRecord);
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
}
|
}
|
||||||
|
|||||||
+419
-108
@@ -1,127 +1,438 @@
|
|||||||
|
--[[-- ExpGroups
|
||||||
|
Adds permission group syncing to clusterio
|
||||||
|
]]
|
||||||
|
|
||||||
local clusterio_api = require("modules/clusterio/api")
|
local clusterio_api = require("modules/clusterio/api")
|
||||||
local Global = require("modules/exp_util/global")
|
local compat = require("modules/clusterio/compat") --[[@as LibCompat]]
|
||||||
local Groups = require("modules/exp_groups")
|
|
||||||
|
|
||||||
local pending_updates = {}
|
--- Top level module table, contains event handlers and public methods
|
||||||
Global.register(pending_updates, function(tbl)
|
--- @class ExpGroups
|
||||||
pending_updates = tbl
|
local ExpGroups = {}
|
||||||
end)
|
|
||||||
|
|
||||||
|
--- @class ExpPermissionGroups.GroupPermissions
|
||||||
|
--- @field is_blacklist boolean
|
||||||
|
--- @field permissions string[]?
|
||||||
|
|
||||||
|
--- @class ExpPermissionGroups.GroupRecord
|
||||||
|
--- @field id number
|
||||||
|
--- @field name string
|
||||||
|
--- @field permissions ExpPermissionGroups.GroupPermissions?
|
||||||
|
--- @field is_deleted boolean
|
||||||
|
|
||||||
|
--- @class ExpPermissionGroups.AssignmentRecord
|
||||||
|
--- @field name string
|
||||||
|
--- @field group_id number
|
||||||
|
--- @field is_deleted boolean
|
||||||
|
|
||||||
|
--- @class ExpPermissionGroups.ScriptData
|
||||||
|
--- @field factorio_to_clusterio_id table<number, number?>
|
||||||
|
--- @field clusterio_id_to_group table<number, LuaPermissionGroup?>
|
||||||
|
--- @field dirty_groups table<number, LuaPermissionGroup>
|
||||||
|
--- @field dirty_players table<string, LuaPlayer>
|
||||||
|
--- @field emit_updates boolean
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
local function setup_script_data()
|
||||||
|
if compat.script_data["exp_groups"] == nil then
|
||||||
|
--- @type ExpPermissionGroups.ScriptData
|
||||||
|
compat.script_data["exp_groups"] = {
|
||||||
|
factorio_to_clusterio_id = {},
|
||||||
|
clusterio_id_to_group = {},
|
||||||
|
dirty_groups = {},
|
||||||
|
dirty_players = {},
|
||||||
|
emit_updates = false,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
ExpGroups.on_load()
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Helper methods
|
||||||
|
]]
|
||||||
|
|
||||||
|
--- Get the default factorio permission group
|
||||||
|
--- @return LuaPermissionGroup
|
||||||
|
local function get_default_group()
|
||||||
|
return assert(game.permissions.get_group("Default"))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Move all players from one group to another
|
||||||
|
--- @param group_src LuaPermissionGroup
|
||||||
|
--- @param group_dst LuaPermissionGroup
|
||||||
|
local function move_players(group_src, group_dst)
|
||||||
|
local add_player = group_dst.add_player
|
||||||
|
for _, player in pairs(group_src.players) do
|
||||||
|
add_player(player)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Apply an encoded permission definition to a group
|
||||||
|
--- @param group LuaPermissionGroup
|
||||||
|
--- @param permissions ExpPermissionGroups.GroupPermissions
|
||||||
|
local function decode_group_permissions(group, permissions)
|
||||||
|
-- Construct a hash map for faster lookup
|
||||||
|
local action_map = {}
|
||||||
|
for _, input_action_name in pairs(assert(permissions.permissions)) do
|
||||||
|
action_map[input_action_name] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Apply the whitelist / backlist to the group
|
||||||
|
local is_blacklist = permissions.is_blacklist
|
||||||
|
local action_allowed = not is_blacklist
|
||||||
|
for input_action_name, input_action in pairs(defines.input_action) do
|
||||||
|
if action_map[input_action_name] then
|
||||||
|
group.set_allows_action(input_action, action_allowed)
|
||||||
|
else
|
||||||
|
group.set_allows_action(input_action, is_blacklist)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Encode the permissions of a group in a shorthand format
|
||||||
|
--- @param group LuaPermissionGroup
|
||||||
|
--- @return ExpPermissionGroups.GroupPermissions
|
||||||
|
local function encode_group_permissions(group)
|
||||||
|
local whitelist = {} --- @type string[]
|
||||||
|
local blacklist = {} --- @type string[]
|
||||||
|
local whitelist_index = 0
|
||||||
|
local blacklist_index = 0
|
||||||
|
|
||||||
|
-- Construct the whitelist and blacklist
|
||||||
|
local allows_action = group.allows_action
|
||||||
|
for input_action_name, input_action in pairs(defines.input_action) do
|
||||||
|
if allows_action(input_action) then
|
||||||
|
whitelist_index = whitelist_index + 1
|
||||||
|
whitelist[whitelist_index] = input_action_name
|
||||||
|
else
|
||||||
|
blacklist_index = blacklist_index + 1
|
||||||
|
blacklist[blacklist_index] = input_action_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Return the whitelist if it is smaller
|
||||||
|
if blacklist_index > whitelist_index then
|
||||||
|
return whitelist_index > 0
|
||||||
|
and { is_blacklist = false, permissions = whitelist }
|
||||||
|
or { is_blacklist = false }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Otherwise return the blacklist as it is smaller
|
||||||
|
return blacklist_index > 0
|
||||||
|
and { is_blacklist = true, permissions = blacklist }
|
||||||
|
or { is_blacklist = true }
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
State handlers
|
||||||
|
]]
|
||||||
|
|
||||||
|
--- Update the factorio permission group
|
||||||
|
--- @param group_record ExpPermissionGroups.GroupRecord
|
||||||
|
local function update_group(group_record)
|
||||||
|
assert(not group_record.is_deleted)
|
||||||
|
|
||||||
|
-- Try find the group by id and then then name
|
||||||
|
local group = script_data.clusterio_id_to_group[group_record.id]
|
||||||
|
if not group then
|
||||||
|
group = game.permissions.get_group(group_record.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create a new group or update the found group
|
||||||
|
if not group or not group.valid then
|
||||||
|
group = assert(game.permissions.create_group(group_record.name))
|
||||||
|
else
|
||||||
|
group.name = group_record.name
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update the permissions for the group
|
||||||
|
if group_record.permissions then
|
||||||
|
decode_group_permissions(group, group_record.permissions)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update the script data
|
||||||
|
script_data.factorio_to_clusterio_id[group.group_id] = group_record.id
|
||||||
|
script_data.clusterio_id_to_group[group_record.id] = group
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Delete the factorio permission group
|
||||||
|
--- @param group_record ExpPermissionGroups.GroupRecord
|
||||||
|
local function delete_group(group_record)
|
||||||
|
assert(group_record.is_deleted)
|
||||||
|
|
||||||
|
local default_group = get_default_group()
|
||||||
|
local group = script_data.clusterio_id_to_group[group_record.id]
|
||||||
|
if group then
|
||||||
|
move_players(group, default_group)
|
||||||
|
group.destroy()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Update an assignment by moving the player to their new group
|
||||||
|
--- @param assignment_record ExpPermissionGroups.AssignmentRecord
|
||||||
|
local function update_assignment(assignment_record)
|
||||||
|
assert(not assignment_record.is_deleted)
|
||||||
|
|
||||||
|
local group = script_data.clusterio_id_to_group[assignment_record.group_id]
|
||||||
|
local player = assert(game.get_player(assignment_record.name))
|
||||||
|
if group then
|
||||||
|
group.add_player(player)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clear an assignment by moving the player to the default group
|
||||||
|
--- @param assignment_record ExpPermissionGroups.AssignmentRecord
|
||||||
|
local function delete_assignment(assignment_record)
|
||||||
|
assert(assignment_record.is_deleted)
|
||||||
|
|
||||||
|
local default_group = get_default_group()
|
||||||
|
local player = assert(game.get_player(assignment_record.name))
|
||||||
|
default_group.add_player(player)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Public methods
|
||||||
|
]]
|
||||||
|
|
||||||
|
--- Restore local references to persistent script data after load
|
||||||
|
function ExpGroups.on_load()
|
||||||
|
script_data = compat.script_data["exp_groups"]
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Enable or disable emitting lua changes back to the instance plugin
|
||||||
|
--- @param enabled boolean?
|
||||||
|
function ExpGroups.set_emit_events(enabled)
|
||||||
|
script_data.emit_updates = enabled ~= false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Replace local state with expected controller state on startup
|
||||||
|
--- @param group_records ExpPermissionGroups.GroupRecord[]
|
||||||
|
function ExpGroups.initialise_groups(group_records)
|
||||||
|
local _emit_events = script_data.emit_updates
|
||||||
|
script_data.emit_updates = false
|
||||||
|
|
||||||
|
-- Update all the received groups
|
||||||
|
local seen_clusterio_ids = {} --- @type table<number, boolean>
|
||||||
|
for _, group_record in pairs(group_records) do
|
||||||
|
update_group(group_record)
|
||||||
|
seen_clusterio_ids[group_record.id] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Cleanup the script data, removing stale group ids
|
||||||
|
local factorio_to_clusterio_id = script_data.factorio_to_clusterio_id
|
||||||
|
local clusterio_id_to_group = script_data.clusterio_id_to_group
|
||||||
|
for factorio_id, clusterio_id in pairs(factorio_to_clusterio_id) do
|
||||||
|
if not seen_clusterio_ids[clusterio_id] then
|
||||||
|
factorio_to_clusterio_id[factorio_id] = nil
|
||||||
|
clusterio_id_to_group[clusterio_id] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Remove all other groups
|
||||||
|
local default_group = get_default_group()
|
||||||
|
local default_group_id = default_group.group_id
|
||||||
|
for _, group in pairs(game.permissions.groups) do
|
||||||
|
if not factorio_to_clusterio_id[group.group_id] and group.group_id ~= default_group_id then
|
||||||
|
move_players(group, default_group)
|
||||||
|
group.destroy()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.emit_updates = _emit_events
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Receive an updated version of a group record
|
||||||
|
--- @param group_record ExpPermissionGroups.GroupRecord
|
||||||
|
function ExpGroups.receive_group_update(group_record)
|
||||||
|
local _emit_events = script_data.emit_updates
|
||||||
|
script_data.emit_updates = false
|
||||||
|
|
||||||
|
if group_record.is_deleted then
|
||||||
|
delete_group(group_record)
|
||||||
|
else
|
||||||
|
update_group(group_record)
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.emit_updates = _emit_events
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Receive an updated version of a assignment record
|
||||||
|
--- @param assignment_record ExpPermissionGroups.AssignmentRecord
|
||||||
|
function ExpGroups.receive_assignment_update(assignment_record)
|
||||||
|
local _emit_events = script_data.emit_updates
|
||||||
|
script_data.emit_updates = false
|
||||||
|
|
||||||
|
if assignment_record.is_deleted then
|
||||||
|
delete_assignment(assignment_record)
|
||||||
|
else
|
||||||
|
update_assignment(assignment_record)
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.emit_updates = _emit_events
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the current script data for debugging purposes
|
||||||
|
--- @package
|
||||||
|
function ExpGroups._script_data()
|
||||||
|
return script_data
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
IPC events
|
||||||
|
]]
|
||||||
|
|
||||||
|
--- Emit a group update to the instance plugin
|
||||||
|
--- @param group LuaPermissionGroup
|
||||||
|
local function emit_group_update(group)
|
||||||
|
clusterio_api.send_json("exp_group:group_updated", {
|
||||||
|
group_name = group.name,
|
||||||
|
group_id = script_data.factorio_to_clusterio_id[group.group_id],
|
||||||
|
permissions = encode_group_permissions(group),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Emit a group deletion to the instance plugin
|
||||||
|
--- @param group_id number
|
||||||
|
--- @param group_name string
|
||||||
|
local function emit_group_delete(group_id, group_name)
|
||||||
|
clusterio_api.send_json("exp_group:group_deleted", {
|
||||||
|
group_name = group_name,
|
||||||
|
group_id = script_data.factorio_to_clusterio_id[group_id],
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Emit a player assignment to the instance plugin
|
||||||
|
--- @param assignments table<string, number>
|
||||||
|
local function emit_player_assignments(assignments)
|
||||||
|
clusterio_api.send_json("exp_group:player_assignments", {
|
||||||
|
assignments = assignments
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
IPC event queuing
|
||||||
|
]]
|
||||||
|
|
||||||
|
--- Mark a group as changed
|
||||||
|
--- @param group LuaPermissionGroup
|
||||||
|
local function mark_group_dirty(group)
|
||||||
|
script_data.dirty_groups[group.group_id] = group
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Mark a player as changed
|
||||||
|
--- @param player LuaPlayer
|
||||||
|
local function mark_player_dirty(player)
|
||||||
|
script_data.dirty_players[player.name] = player
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Flush queued group updates to the instance plugin
|
||||||
|
local function flush_group_updates()
|
||||||
|
-- Check if updates should be updated
|
||||||
|
if not script_data.emit_updates then
|
||||||
|
script_data.dirty_groups = {}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get all the groups and emit their updates
|
||||||
|
for _, group in pairs(script_data.dirty_groups) do
|
||||||
|
if group.valid then
|
||||||
|
emit_group_update(group)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.dirty_groups = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Flush queued player updates to the instance plugin
|
||||||
|
local function flush_player_updates()
|
||||||
|
-- Check if updates should be updated
|
||||||
|
if not script_data.emit_updates then
|
||||||
|
script_data.dirty_players = {}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Construct the update payload
|
||||||
|
local assignments = {} --- @type table<string, number>
|
||||||
|
for player_name, player in pairs(script_data.dirty_players) do
|
||||||
|
local group = player.valid and player.permission_group
|
||||||
|
if group then
|
||||||
|
assignments[player_name] = script_data.factorio_to_clusterio_id[group.group_id]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
emit_player_assignments(assignments)
|
||||||
|
|
||||||
|
script_data.dirty_players = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Factorio events
|
||||||
|
]]
|
||||||
|
|
||||||
|
--- Handle clusterio server startup
|
||||||
|
local function on_server_startup()
|
||||||
|
setup_script_data()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Handle creation of permission groups
|
||||||
|
--- @param event EventData.on_permission_group_added
|
||||||
local function on_permission_group_added(event)
|
local function on_permission_group_added(event)
|
||||||
if not event.player_index then return end
|
-- Check if updates should be updated
|
||||||
pending_updates[event.group.name] = {
|
if not script_data.emit_updates then
|
||||||
created = true,
|
return
|
||||||
sync_all = true,
|
end
|
||||||
tick = event.tick,
|
|
||||||
permissions = {},
|
mark_group_dirty(event.group)
|
||||||
players = {},
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Handle deletion of permission groups
|
||||||
|
--- @param event EventData.on_permission_group_deleted
|
||||||
local function on_permission_group_deleted(event)
|
local function on_permission_group_deleted(event)
|
||||||
if not event.player_index then return end
|
-- Check if updates should be updated
|
||||||
local existing = pending_updates[event.group_name]
|
if not script_data.emit_updates then
|
||||||
pending_updates[event.group_name] = nil
|
return
|
||||||
if not existing or not existing.created then
|
|
||||||
clusterio_api.send_json("exp_groups-permission_group_delete", {
|
|
||||||
group = event.group_name,
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
emit_group_delete(event.id, event.group_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Handle edits make to permission groups
|
||||||
|
--- @param event EventData.on_permission_group_edited
|
||||||
local function on_permission_group_edited(event)
|
local function on_permission_group_edited(event)
|
||||||
if not event.player_index then return end
|
-- Check if updates should be updated
|
||||||
local pending = pending_updates[event.group.name]
|
if not script_data.emit_updates then
|
||||||
if not pending then
|
return
|
||||||
pending = {
|
|
||||||
tick = event.tick,
|
|
||||||
permissions = {},
|
|
||||||
players = {},
|
|
||||||
}
|
|
||||||
pending_updates[event.group.name] = pending
|
|
||||||
end
|
end
|
||||||
pending.tick = event.tick
|
|
||||||
|
|
||||||
if event.type == "add-permission" then
|
-- Check if this is a player or group event
|
||||||
if not pending.sync_all then
|
if event.type == "add-player" or event.type == "remove-player" then
|
||||||
pending.permissions[event.action] = true
|
local player = assert(game.get_player(event.other_player_index))
|
||||||
end
|
mark_player_dirty(player)
|
||||||
elseif event.type == "remove-permission" then
|
else
|
||||||
if not pending.sync_all then
|
mark_group_dirty(event.group)
|
||||||
pending.permissions[event.action] = false
|
|
||||||
end
|
|
||||||
elseif event.type == "enable-all" then
|
|
||||||
pending.sync_all = true
|
|
||||||
elseif event.type == "disable-all" then
|
|
||||||
pending.sync_all = true
|
|
||||||
elseif event.type == "add-player" then
|
|
||||||
local player = game.get_player(event.other_player_index) --- @cast player -nil
|
|
||||||
pending.players[player.name] = true
|
|
||||||
elseif event.type == "remove-player" then
|
|
||||||
local player = game.get_player(event.other_player_index) --- @cast player -nil
|
|
||||||
pending.players[player.name] = nil
|
|
||||||
elseif event.type == "rename" then
|
|
||||||
pending.created = true
|
|
||||||
pending.sync_all = true
|
|
||||||
local old = pending_updates[event.old_name]
|
|
||||||
if old then pending.players = old.players end
|
|
||||||
on_permission_group_deleted{
|
|
||||||
tick = event.tick, player_index = event.player_index, group_name = event.old_name,
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function send_updates()
|
--- Periodically flush queued changes
|
||||||
local tick = game.tick - 600 -- 10 Seconds
|
local function on_nth_tick_flush()
|
||||||
local done = {}
|
flush_group_updates()
|
||||||
for group_name, pending in pairs(pending_updates) do
|
flush_player_updates()
|
||||||
if pending.tick < tick then
|
|
||||||
done[group_name] = true
|
|
||||||
if pending.sync_all then
|
|
||||||
clusterio_api.send_json("exp_groups-permission_group_create", {
|
|
||||||
group = group_name, defiantion = Groups.get_group(group_name):to_json(true),
|
|
||||||
})
|
|
||||||
else
|
|
||||||
if next(pending.players) then
|
|
||||||
clusterio_api.send_json("exp_groups-permission_group_edit", {
|
|
||||||
type = "assign_players", group = group_name, changes = table.get_keys(pending.players),
|
|
||||||
})
|
|
||||||
end
|
|
||||||
local add, remove = {}, {}
|
|
||||||
for permission, state in pairs(pending.permissions) do
|
|
||||||
if state then
|
|
||||||
add[#add + 1] = permission
|
|
||||||
else
|
|
||||||
remove[#remove + 1] = permission
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if next(add) then
|
|
||||||
clusterio_api.send_json("exp_groups-permission_group_edit", {
|
|
||||||
type = "add_permissions", group = group_name, changes = Groups.actions_to_names(add),
|
|
||||||
})
|
|
||||||
end
|
|
||||||
if next(remove) then
|
|
||||||
clusterio_api.send_json("exp_groups-permission_group_edit", {
|
|
||||||
type = "remove_permissions", group = group_name, changes = Groups.actions_to_names(remove),
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for group_name in pairs(done) do
|
|
||||||
pending_updates[group_name] = nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
local e = defines.events
|
||||||
events = {
|
|
||||||
[defines.events.on_permission_group_added] = on_permission_group_added,
|
local events = {
|
||||||
[defines.events.on_permission_group_deleted] = on_permission_group_deleted,
|
[clusterio_api.events.on_server_startup] = on_server_startup,
|
||||||
[defines.events.on_permission_group_edited] = on_permission_group_edited,
|
[e.on_multiplayer_init] = on_server_startup,
|
||||||
},
|
[e.on_permission_group_added] = on_permission_group_added,
|
||||||
on_nth_tick = {
|
[e.on_permission_group_deleted] = on_permission_group_deleted,
|
||||||
[300] = send_updates,
|
[e.on_permission_group_edited] = on_permission_group_edited,
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local on_nth_tick = {
|
||||||
|
[300] = on_nth_tick_flush,
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpGroups.events = events --- @package
|
||||||
|
ExpGroups.on_nth_tick = on_nth_tick --- @package
|
||||||
|
return ExpGroups
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
--[[
|
||||||
|
It is best practice to not expose any globals because all modules share a global environment
|
||||||
|
However, sometimes you need globals, for example to access functions within rcon commands
|
||||||
|
Therefore, we advise that this should be the only file in your module to expose globals
|
||||||
|
Typically this would be your control file as shown in the example below
|
||||||
|
]]
|
||||||
|
|
||||||
|
--- @diagnostic disable: global-in-non-module
|
||||||
|
|
||||||
|
-- Access using `/sc exp_groups.foo()`
|
||||||
|
exp_groups = require("modules/exp_groups/control")
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
"control.lua"
|
"control.lua"
|
||||||
],
|
],
|
||||||
"require": [
|
"require": [
|
||||||
|
"globals.lua"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clusterio": "*",
|
"clusterio": "*"
|
||||||
"exp_util": "*"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,306 +1,3 @@
|
|||||||
local Async = require("modules/exp_util/async")
|
|
||||||
|
|
||||||
local table_to_json = helpers.table_to_json
|
-- Access the exports from other modules using require("modules/exp_groups")
|
||||||
local json_to_table = helpers.json_to_table
|
return require("modules/exp_groups/control")
|
||||||
|
|
||||||
--- Top level module table, contains event handlers and public methods
|
|
||||||
local Groups = {}
|
|
||||||
|
|
||||||
--- @class ExpGroup : LuaPermissionGroup
|
|
||||||
--- @field group LuaPermissionGroup The permission group for this group proxy
|
|
||||||
Groups._prototype = {}
|
|
||||||
|
|
||||||
Groups._metatable = {
|
|
||||||
__index = setmetatable(Groups._prototype, {
|
|
||||||
--- @type any Annotation required because otherwise it is typed as 'table'
|
|
||||||
__index = function(self, key)
|
|
||||||
return self.group[key]
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
__class = "ExpGroup",
|
|
||||||
}
|
|
||||||
|
|
||||||
local action_to_name = {}
|
|
||||||
for name, action in pairs(defines.input_action) do
|
|
||||||
action_to_name[action] = name
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Async Functions
|
|
||||||
-- These are required to allow bypassing edit_permission_group
|
|
||||||
|
|
||||||
--- Add a player to a permission group, requires edit_permission_group
|
|
||||||
--- @param player LuaPlayer Player to add to the group
|
|
||||||
--- @param group LuaPermissionGroup Group to add the player to
|
|
||||||
--- @return boolean # True if successful
|
|
||||||
local function add_player_to_group(player, group)
|
|
||||||
return group.add_player(player)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Add a players to a permission group, requires edit_permission_group
|
|
||||||
--- @param players LuaPlayer[] Players to add to the group
|
|
||||||
--- @param group LuaPermissionGroup Group to add the players to
|
|
||||||
--- @return boolean # True if successful
|
|
||||||
local function add_players_to_group(players, group)
|
|
||||||
local add_player = group.add_player
|
|
||||||
if not add_player(players[1]) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
for i = 2, #players do
|
|
||||||
add_player(players[i])
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Async will bypass edit_permission_group but takes at least one tick
|
|
||||||
local add_player_to_group_async = Async.register(add_player_to_group)
|
|
||||||
local add_players_to_group_async = Async.register(add_players_to_group)
|
|
||||||
|
|
||||||
--- Static methods for gettings, creating and removing permission groups
|
|
||||||
|
|
||||||
--- Gets the permission group proxy with the given name or group ID.
|
|
||||||
--- @param group_name string|uint32 The name or id of the permission group
|
|
||||||
--- @return ExpGroup?
|
|
||||||
function Groups.get_group(group_name)
|
|
||||||
local group = game.permissions.get_group(group_name)
|
|
||||||
if group == nil then return nil end
|
|
||||||
return setmetatable({
|
|
||||||
group = group,
|
|
||||||
}, Groups._metatable)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets the permission group proxy for a players group
|
|
||||||
--- @param player LuaPlayer The player to get the group of
|
|
||||||
--- @return ExpGroup?
|
|
||||||
function Groups.get_player_group(player)
|
|
||||||
local group = player.permission_group
|
|
||||||
if group == nil then return nil end
|
|
||||||
return setmetatable({
|
|
||||||
group = group,
|
|
||||||
}, Groups._metatable)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Creates a new permission group, requires add_permission_group
|
|
||||||
--- @param group_name string Name of the group to create
|
|
||||||
--- @return ExpGroup
|
|
||||||
function Groups.new_group(group_name)
|
|
||||||
local group = game.permissions.get_group(group_name)
|
|
||||||
assert(group == nil, "Group already exists with name: " .. group_name)
|
|
||||||
group = game.permissions.create_group(group_name)
|
|
||||||
assert(group ~= nil, "Requires permission add_permission_group")
|
|
||||||
return setmetatable({
|
|
||||||
group = group,
|
|
||||||
}, Groups._metatable)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Get or create a permisison group, must use the group name not the group id
|
|
||||||
--- @param group_name string Name of the group to create
|
|
||||||
--- @return ExpGroup
|
|
||||||
function Groups.get_or_create(group_name)
|
|
||||||
local group = game.permissions.get_group(group_name)
|
|
||||||
if group then
|
|
||||||
return setmetatable({
|
|
||||||
group = group,
|
|
||||||
}, Groups._metatable)
|
|
||||||
else
|
|
||||||
group = game.permissions.create_group(group_name)
|
|
||||||
assert(group ~= nil, "Requires permission add_permission_group")
|
|
||||||
return setmetatable({
|
|
||||||
group = group,
|
|
||||||
}, Groups._metatable)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Destory a permission group, moves all players to default group
|
|
||||||
--- @param group_name string|uint32 The name or id of the permission group to destroy
|
|
||||||
--- @param move_to_name string|uint32? The name or id of the permission group to move players to
|
|
||||||
function Groups.destroy_group(group_name, move_to_name)
|
|
||||||
local group = game.permissions.get_group(group_name)
|
|
||||||
if group == nil then return end
|
|
||||||
|
|
||||||
local players = group.players
|
|
||||||
if #players > 0 then
|
|
||||||
local move_to = game.permissions.get_group(move_to_name or "Default")
|
|
||||||
for _, player in ipairs(players) do
|
|
||||||
player.permission_group = move_to
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local success = group.destroy()
|
|
||||||
assert(success, "Requires permission delete_permission_group")
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Prototype methods for modifying and working with permission groups
|
|
||||||
|
|
||||||
--- Add a player to the permission group
|
|
||||||
--- @param player LuaPlayer The player to add to the group
|
|
||||||
function Groups._prototype:add_player(player)
|
|
||||||
if not add_player_to_group(player, self.group) then
|
|
||||||
add_player_to_group_async(player, self.group)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Add players to the permission group
|
|
||||||
--- @param players LuaPlayer[] The player to add to the group
|
|
||||||
function Groups._prototype:add_players(players)
|
|
||||||
if not add_players_to_group(players, self.group) then
|
|
||||||
add_players_to_group_async(players, self.group)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Move all players to another group
|
|
||||||
--- @param other_group ExpGroup The group to move players to, default is the Default group
|
|
||||||
function Groups._prototype:move_players(other_group)
|
|
||||||
if not add_players_to_group(self.group.players, other_group.group) then
|
|
||||||
add_players_to_group_async(self.group.players, other_group.group)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Allow a set of actions for this group
|
|
||||||
--- @param actions defines.input_action[] Actions to allow
|
|
||||||
--- @return ExpGroup
|
|
||||||
function Groups._prototype:allow_actions(actions)
|
|
||||||
local set_allow = self.group.set_allows_action
|
|
||||||
for _, action in ipairs(actions) do
|
|
||||||
set_allow(action, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Disallow a set of actions for this group
|
|
||||||
--- @param actions defines.input_action[] Actions to disallow
|
|
||||||
--- @return ExpGroup
|
|
||||||
function Groups._prototype:disallow_actions(actions)
|
|
||||||
local set_allow = self.group.set_allows_action
|
|
||||||
for _, action in ipairs(actions) do
|
|
||||||
set_allow(action, false)
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Reset the allowed state of all actions
|
|
||||||
--- @param allowed boolean? default true for allow all actions, false to disallow all actions
|
|
||||||
--- @return ExpGroup
|
|
||||||
function Groups._prototype:reset(allowed)
|
|
||||||
local set_allow = self.group.set_allows_action
|
|
||||||
if allowed == nil then allowed = true end
|
|
||||||
for _, action in pairs(defines.input_action) do
|
|
||||||
set_allow(action, allowed)
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns if the group is allowed a given action
|
|
||||||
--- @param action string|defines.input_action Actions to test
|
|
||||||
--- @return boolean # True if successful
|
|
||||||
function Groups._prototype:allows(action)
|
|
||||||
if type(action) == "string" then
|
|
||||||
return self.group.allows_action(defines.input_action[action])
|
|
||||||
end
|
|
||||||
return self.group.allows_action(action)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Print a message to all players in the group
|
|
||||||
function Groups._prototype:print(...)
|
|
||||||
for _, player in ipairs(self.group.players) do
|
|
||||||
player.print(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Static and Prototype methods for use with IPC
|
|
||||||
|
|
||||||
--- Convert an array of strings into an array of action names
|
|
||||||
--- @param actions_names string[] An array of action names
|
|
||||||
--- @return defines.input_action[]
|
|
||||||
local function names_to_actions(actions_names)
|
|
||||||
--- @type defines.input_action[], number[], number
|
|
||||||
local actions, invalid, invalid_i = {}, {}, 1
|
|
||||||
for i, action_name in ipairs(actions_names) do
|
|
||||||
local action = defines.input_action[action_name] --[[ @as defines.input_action? ]]
|
|
||||||
if action then
|
|
||||||
actions[i] = action
|
|
||||||
else
|
|
||||||
invalid[invalid_i] = i
|
|
||||||
invalid_i = invalid_i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local last = #actions
|
|
||||||
for _, i in ipairs(invalid) do
|
|
||||||
actions[i] = actions[last]
|
|
||||||
last = last - 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return actions
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Get the action names from the action numbers
|
|
||||||
function Groups.actions_to_names(actions)
|
|
||||||
local names = {}
|
|
||||||
for i, action in ipairs(actions) do
|
|
||||||
names[i] = action_to_name[action]
|
|
||||||
end
|
|
||||||
|
|
||||||
return names
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Get all input actions that are defined
|
|
||||||
function Groups.get_actions_json()
|
|
||||||
local rtn, rtn_i = {}, 1
|
|
||||||
for name in pairs(defines.input_action) do
|
|
||||||
rtn[rtn_i] = name
|
|
||||||
rtn_i = rtn_i + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return table_to_json(rtn)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Convert a json string array into an array of input actions
|
|
||||||
--- @param json string A json string representing a string array of actions
|
|
||||||
--- @return defines.input_action[]
|
|
||||||
function Groups.json_to_actions(json)
|
|
||||||
local tbl = json_to_table(json)
|
|
||||||
assert(tbl, "Invalid Json String")
|
|
||||||
--- @cast tbl string[]
|
|
||||||
return names_to_actions(tbl)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns the shortest defination of the allowed actions
|
|
||||||
-- The first value of the return can be passed to :reset
|
|
||||||
function Groups._prototype:to_json(raw)
|
|
||||||
local allow, disallow = {}, {}
|
|
||||||
local allow_i, disallow_i = 1, 1
|
|
||||||
local allows = self.group.allows_action
|
|
||||||
for name, action in pairs(defines.input_action) do
|
|
||||||
if allows(action) then
|
|
||||||
allow[allow_i] = name
|
|
||||||
allow_i = allow_i + 1
|
|
||||||
else
|
|
||||||
disallow[disallow_i] = name
|
|
||||||
disallow_i = disallow_i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if allow_i >= disallow_i then
|
|
||||||
return raw and { true, disallow } or table_to_json{ true, disallow }
|
|
||||||
end
|
|
||||||
return raw and { false, allow } or table_to_json{ false, allow }
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Restores this group to the state given in a json string
|
|
||||||
--- @param json string The json string to restore from
|
|
||||||
function Groups._prototype:from_json(json)
|
|
||||||
local tbl = json_to_table(json)
|
|
||||||
assert(tbl and type(tbl[1]) == "boolean" and type(tbl[2]) == "table", "Invalid Json String")
|
|
||||||
|
|
||||||
if tbl[1] then
|
|
||||||
self:reset(true):disallow_actions(names_to_actions(tbl[2]))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
self:reset(false):allow_actions(names_to_actions(tbl[2]))
|
|
||||||
end
|
|
||||||
|
|
||||||
return Groups
|
|
||||||
|
|||||||
+20
-11
@@ -1,41 +1,50 @@
|
|||||||
{
|
{
|
||||||
"name": "@expcluster/permission_groups",
|
"name": "@expcluster/permission-groups",
|
||||||
"private": true,
|
"version": "7.0.1",
|
||||||
"version": "0.0.0",
|
"description": "Clusterio plugin providing syncing of permission groups",
|
||||||
"description": "Example Description. Package. Change me in package.json",
|
"author": "Cooldude2606 <https://github.com/Cooldude2606>",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": "explosivegaming/ExpCluster",
|
||||||
|
"type": "module",
|
||||||
"main": "dist/node/index.js",
|
"main": "dist/node/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"$prepare": "tsc --build && webpack-cli --env production"
|
"prepare": "tsc --build && webpack-cli --env production"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@clusterio/lib": "catalog:"
|
"@clusterio/controller": "workspace:^",
|
||||||
|
"@clusterio/host": "workspace:^",
|
||||||
|
"@clusterio/lib": "workspace:^",
|
||||||
|
"@clusterio/web_ui": "workspace:^"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@clusterio/lib": "catalog:",
|
"@ant-design/icons": "^6.2.3",
|
||||||
"@clusterio/web_ui": "catalog:",
|
"@clusterio/controller": "workspace:^",
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@clusterio/host": "workspace:^",
|
||||||
|
"@clusterio/lib": "workspace:^",
|
||||||
|
"@clusterio/web_ui": "workspace:^",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
"@types/react": "catalog:",
|
"@types/react": "catalog:",
|
||||||
"antd": "catalog:",
|
"antd": "catalog:",
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
"react-dom": "catalog:",
|
"react-dom": "catalog:",
|
||||||
|
"react-router-dom": "catalog:",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"webpack": "catalog:",
|
"webpack": "catalog:",
|
||||||
"webpack-cli": "catalog:",
|
"webpack-cli": "catalog:",
|
||||||
"webpack-merge": "catalog:"
|
"webpack-merge": "catalog:"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sinclair/typebox": "catalog:",
|
"@sinclair/typebox": "catalog:"
|
||||||
"fs-extra": "^11.3.3"
|
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"clusterio",
|
"clusterio",
|
||||||
|
"clusterio-plugin",
|
||||||
"factorio"
|
"factorio"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"files": [],
|
"files": [],
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "./tsconfig.browser.json" },
|
{ "path": "./tsconfig.node.json" },
|
||||||
{ "path": "./tsconfig.node.json" }
|
{ "path": "./tsconfig.browser.json" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import React, { useContext, useEffect } from "react";
|
||||||
|
import { Modal, Form, Select } from "antd";
|
||||||
|
|
||||||
|
import { ControlContext, useUsers } from "@clusterio/web_ui";
|
||||||
|
import * as messages from "../../messages.js";
|
||||||
|
import type { WebPlugin } from "..";
|
||||||
|
|
||||||
|
export default function AssignmentForm({ open, setOpen, initial }: {
|
||||||
|
open: boolean,
|
||||||
|
setOpen: (open: boolean) => void,
|
||||||
|
initial?: messages.AssignmentRecord,
|
||||||
|
}) {
|
||||||
|
const control = useContext(ControlContext);
|
||||||
|
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||||
|
|
||||||
|
const [groups] = plugin.useGroups();
|
||||||
|
const [users] = useUsers();
|
||||||
|
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
function submit(values: any) {
|
||||||
|
if (initial) {
|
||||||
|
control.send(new messages.AssignmentUpdateRequest(
|
||||||
|
new messages.AssignmentRecord(
|
||||||
|
values.name,
|
||||||
|
values.groupId,
|
||||||
|
)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
control.send(new messages.AssignmentCreateRequest(
|
||||||
|
values.name,
|
||||||
|
values.groupId,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
form.resetFields();
|
||||||
|
form.setFieldsValue(initial);
|
||||||
|
}
|
||||||
|
}, [open, initial]);
|
||||||
|
|
||||||
|
return <Modal
|
||||||
|
title={initial ? "Edit Assignment" : "Create Assignment"}
|
||||||
|
open={open}
|
||||||
|
onCancel={() => setOpen(false)}
|
||||||
|
onOk={() => form.submit()}
|
||||||
|
>
|
||||||
|
<Form form={form} onFinish={submit}>
|
||||||
|
<Form.Item name="name" label="Player" rules={[{ required: true }]}>
|
||||||
|
<Select disabled={Boolean(initial)} showSearch>
|
||||||
|
{[...users.values()].map(u => (
|
||||||
|
<Select.Option key={u.name} value={u.name}>
|
||||||
|
{u.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="groupId" label="Group" rules={[{ required: true }]}>
|
||||||
|
<Select>
|
||||||
|
{[...groups.values()].map(g => (
|
||||||
|
<Select.Option key={g.id} value={g.id}>
|
||||||
|
{g.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import React, { useContext, useState, useRef } from "react";
|
||||||
|
import { Table, Button, Space, Input, InputRef } from "antd";
|
||||||
|
import { EditOutlined, SearchOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
|
import { ControlContext, useAccount } from "@clusterio/web_ui";
|
||||||
|
import { AssignmentDeleteRequest, AssignmentRecord } from "../../messages.js";
|
||||||
|
import type { WebPlugin } from "..";
|
||||||
|
|
||||||
|
import AssignmentForm from "./AssignmentForm";
|
||||||
|
import DeletedConfirm from "./DeleteConfirm";
|
||||||
|
|
||||||
|
const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare;
|
||||||
|
|
||||||
|
export default function AssignmentsTable() {
|
||||||
|
const control = useContext(ControlContext);
|
||||||
|
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||||
|
const account = useAccount();
|
||||||
|
|
||||||
|
const searchInput = useRef<InputRef>(null);
|
||||||
|
|
||||||
|
const [assignments, assignmentsSynced] = plugin.useAssignments();
|
||||||
|
const [groups, groupsSynced] = plugin.useGroups();
|
||||||
|
|
||||||
|
const [editing, setEditing] = useState<AssignmentRecord | undefined>();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const assignmentsArray = [...assignments.values()];
|
||||||
|
|
||||||
|
const groupFilters = [...groups.values()]
|
||||||
|
.filter(group => assignmentsArray.some(assignment => assignment.groupId === group.id))
|
||||||
|
.map(group => ({ text: group.name, value: group.id }))
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Table
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: "Player",
|
||||||
|
dataIndex: "name",
|
||||||
|
sorter: (a: AssignmentRecord, b: AssignmentRecord) => strcmp(a.name, b.name),
|
||||||
|
filterIcon: (filtered: boolean) => (
|
||||||
|
<SearchOutlined style={{ color: filtered ? "#1677ff" : undefined }} />
|
||||||
|
),
|
||||||
|
onFilter: (value, record: AssignmentRecord) => (
|
||||||
|
record.name.toLowerCase().includes(String(value).toLowerCase())
|
||||||
|
),
|
||||||
|
filterDropdownProps: {
|
||||||
|
onOpenChange: (open: boolean) => open && setTimeout(() => searchInput.current?.select(), 100),
|
||||||
|
},
|
||||||
|
filterDropdown: ({ selectedKeys, setSelectedKeys, confirm, clearFilters }) => (
|
||||||
|
<div style={{ padding: 4 }} onKeyDown={(e) => e.stopPropagation()}>
|
||||||
|
<Input.Search
|
||||||
|
allowClear
|
||||||
|
ref={searchInput}
|
||||||
|
placeholder={"Search username"}
|
||||||
|
value={selectedKeys[0]}
|
||||||
|
onChange={(e) => setSelectedKeys([e.target.value])}
|
||||||
|
onSearch={() => confirm({ closeDropdown: false })}
|
||||||
|
onClear={() => {
|
||||||
|
clearFilters?.({ closeDropdown: false });
|
||||||
|
confirm({ closeDropdown: true });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Group",
|
||||||
|
width: "40%",
|
||||||
|
filters: groupFilters,
|
||||||
|
onFilter: (value, record: AssignmentRecord) => (
|
||||||
|
record.groupId === value
|
||||||
|
),
|
||||||
|
render: (_: any, a: any) => (
|
||||||
|
groups.get(a.groupId)?.name ?? a.groupId
|
||||||
|
),
|
||||||
|
},
|
||||||
|
...(account.hasAnyPermission(
|
||||||
|
"exp_groups.assignment.update",
|
||||||
|
"exp_groups.assignment.delete",
|
||||||
|
) ? [{
|
||||||
|
title: "Actions",
|
||||||
|
width: "10%",
|
||||||
|
render: (_: any, record: AssignmentRecord) => (
|
||||||
|
<Space>
|
||||||
|
{account.hasPermission("exp_groups.assignment.update") &&
|
||||||
|
<Button icon={<EditOutlined />} onClick={() => {
|
||||||
|
setEditing(record);
|
||||||
|
setOpen(true);
|
||||||
|
}} />
|
||||||
|
}
|
||||||
|
{account.hasPermission("exp_groups.assignment.delete") && <DeletedConfirm
|
||||||
|
onConfirm={() => control.send(new AssignmentDeleteRequest(record.name))}
|
||||||
|
/>}
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
}] : []),
|
||||||
|
]}
|
||||||
|
dataSource={assignmentsArray}
|
||||||
|
loading={!assignmentsSynced || !groupsSynced}
|
||||||
|
rowKey={(a) => a.name}
|
||||||
|
pagination={{
|
||||||
|
defaultPageSize: 50,
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: ["10", "20", "50", "100", "200"],
|
||||||
|
showTotal: (total: number) => `${total} Assignments`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AssignmentForm open={open} setOpen={setOpen} initial={editing} />
|
||||||
|
</>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Button, Popconfirm } from "antd";
|
||||||
|
import { DeleteOutlined } from "@ant-design/icons";
|
||||||
|
import { TooltipPlacement } from "antd/es/tooltip";
|
||||||
|
|
||||||
|
export default function DeletedConfirm(props: { onConfirm: () => void, placement?: TooltipPlacement }) {
|
||||||
|
return <Popconfirm
|
||||||
|
title="Delete?"
|
||||||
|
okText="Delete"
|
||||||
|
okButtonProps={{ danger: true }}
|
||||||
|
onConfirm={props.onConfirm}
|
||||||
|
placement={props.placement}
|
||||||
|
>
|
||||||
|
<Button danger icon={<DeleteOutlined />} />
|
||||||
|
</Popconfirm>
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import React, { useContext, useEffect } from "react";
|
||||||
|
import { Modal, Form, Input, Switch } from "antd";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import { ControlContext } from "@clusterio/web_ui";
|
||||||
|
import * as messages from "../../messages.js";
|
||||||
|
|
||||||
|
export default function GroupForm({ open, setOpen }: {
|
||||||
|
open: boolean,
|
||||||
|
setOpen: (open: boolean) => void,
|
||||||
|
}) {
|
||||||
|
const control = useContext(ControlContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
async function submit(values: any) {
|
||||||
|
const group = await control.send(new messages.GroupCreateRequest(
|
||||||
|
values.name, new messages.GroupPermissions(Boolean(values.isBlacklist), [])
|
||||||
|
));
|
||||||
|
navigate(`/permission_groups/${group.id}/view`);
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) form.resetFields();
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
return <Modal
|
||||||
|
title="Create Group"
|
||||||
|
open={open}
|
||||||
|
onCancel={() => setOpen(false)}
|
||||||
|
onOk={() => form.submit()}
|
||||||
|
>
|
||||||
|
<Form form={form} onFinish={submit}>
|
||||||
|
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="isBlacklist" label="Grant all by default" valuePropName="checked">
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
import React, { useContext, useEffect, useMemo, useState } from "react";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { Button, Checkbox, Input, Space, Spin, Alert } from "antd";
|
||||||
|
|
||||||
|
import { ControlContext, useAccount, useDefaultModPack, PageLayout, PageHeader, notifyErrorHandler } from "@clusterio/web_ui";
|
||||||
|
import DeletedConfirm from "./DeleteConfirm";
|
||||||
|
|
||||||
|
import * as messages from "../../messages.js";
|
||||||
|
import type { WebPlugin } from "..";
|
||||||
|
|
||||||
|
const DOMAIN_MAPPING = {
|
||||||
|
"Admin": ["admin", "cheat", "permission", "infinity", "editor", "spawn"],
|
||||||
|
"Building & Crafting": ["mining", "build", "craft", "deconstruct", "rotate", "entity"],
|
||||||
|
"Inventory": ["inventory", "stack", "cursor", "slot", "quick_bar", "item", "equipment"],
|
||||||
|
"Blueprints": ["blueprint", "copy", "paste", "undo", "redo", "upgrade"],
|
||||||
|
"Trains": ["train", "station", "schedule", "interrupt", "rolling_stock"],
|
||||||
|
"Circuit Network": ["combinator", "circuit", "signal", "wire", "cable", "speaker", "display_panel"],
|
||||||
|
"GUI": ["gui", "open"],
|
||||||
|
"Pins & Alerts": ["pin", "alert", "tag"],
|
||||||
|
"Logistics": ["logistic", "filter", "request"],
|
||||||
|
"Space": ["space", "surface", "planet", "rocket"],
|
||||||
|
"Toggles": ["toggle", "switch", "swap", "set"],
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDomain(name: string) {
|
||||||
|
const n = name.toLowerCase();
|
||||||
|
|
||||||
|
for (const [domain, keywords] of Object.entries(DOMAIN_MAPPING)) {
|
||||||
|
if (keywords.some(k => n.includes(k))) return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Other";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GroupViewPage() {
|
||||||
|
const control = useContext(ControlContext);
|
||||||
|
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const account = useAccount();
|
||||||
|
|
||||||
|
const { id } = useParams();
|
||||||
|
const groupId = Number(id);
|
||||||
|
|
||||||
|
const [groups, synced] = plugin.useGroups();
|
||||||
|
const group = groups.get(groupId);
|
||||||
|
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
|
||||||
|
const defaultModPack = useDefaultModPack();
|
||||||
|
const [definesJson, setDefinesJson] = useState<Record<string, number> | null>(null);
|
||||||
|
|
||||||
|
const [permissionState, setPermissionState] = useState<Record<string, boolean>>({});
|
||||||
|
const [baselineState, setBaselineState] = useState<Record<string, boolean>>({});
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
const canUpdate = Boolean(account.hasPermission("exp_groups.group.update"));
|
||||||
|
const canDelete = Boolean(account.hasPermission("exp_groups.group.delete"));
|
||||||
|
|
||||||
|
// fetch defines
|
||||||
|
useEffect(() => {
|
||||||
|
if (!defaultModPack?.exportManifest?.assets?.defines) return;
|
||||||
|
if (definesJson) return;
|
||||||
|
|
||||||
|
const assetName = defaultModPack.exportManifest.assets.defines;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const response = await fetch(`${staticRoot}${assetName}`);
|
||||||
|
const json = await response.json();
|
||||||
|
setDefinesJson(json.input_action ?? {});
|
||||||
|
})().catch(notifyErrorHandler("Failed to load permissions"));
|
||||||
|
}, [defaultModPack]);
|
||||||
|
|
||||||
|
// initialise permissions
|
||||||
|
useEffect(() => {
|
||||||
|
if (!group || !definesJson) return;
|
||||||
|
if (Object.keys(permissionState).length) return;
|
||||||
|
|
||||||
|
const { isBlacklist, permissions } = group.permissions;
|
||||||
|
|
||||||
|
const base = Object.fromEntries(
|
||||||
|
Object.keys(definesJson).map(p => [
|
||||||
|
p, isBlacklist !== permissions.includes(p)
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
setPermissionState(base);
|
||||||
|
setBaselineState(base);
|
||||||
|
}, [group, definesJson]);
|
||||||
|
|
||||||
|
// sync name
|
||||||
|
useEffect(() => {
|
||||||
|
if (group) setName(group.name);
|
||||||
|
}, [group]);
|
||||||
|
|
||||||
|
// derived data
|
||||||
|
const allPermissions = useMemo(() => (
|
||||||
|
Object.keys(definesJson ?? {})
|
||||||
|
), [definesJson]);
|
||||||
|
|
||||||
|
const filtered = useMemo(() => allPermissions.filter(p =>
|
||||||
|
p.toLowerCase().includes(search.toLowerCase())
|
||||||
|
), [allPermissions, search]);
|
||||||
|
|
||||||
|
const grouped = useMemo(() => {
|
||||||
|
const map = new Map<string, string[]>();
|
||||||
|
|
||||||
|
for (const p of filtered) {
|
||||||
|
const domain = getDomain(p);
|
||||||
|
if (!map.has(domain)) map.set(domain, []);
|
||||||
|
map.get(domain)!.push(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = [...map.entries()].sort(([a], [b]) => a.localeCompare(b));
|
||||||
|
|
||||||
|
for (const [, perms] of sorted) {
|
||||||
|
perms.sort((a, b) => a.localeCompare(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorted;
|
||||||
|
}, [filtered]);
|
||||||
|
|
||||||
|
const permissionChanged = Object.keys(permissionState).some(
|
||||||
|
k => permissionState[k] !== baselineState[k]
|
||||||
|
);
|
||||||
|
const metaChanged = group ? name !== group.name : false;
|
||||||
|
const edited = permissionChanged || metaChanged;
|
||||||
|
|
||||||
|
function applyChanges() {
|
||||||
|
if (!group) return;
|
||||||
|
|
||||||
|
const whitelist: string[] = [];
|
||||||
|
const blacklist: string[] = [];
|
||||||
|
|
||||||
|
for (const [perm, enabled] of Object.entries(permissionState)) {
|
||||||
|
if (enabled) whitelist.push(perm);
|
||||||
|
else blacklist.push(perm);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBlacklist = blacklist.length < whitelist.length;
|
||||||
|
const permissions = isBlacklist ? blacklist : whitelist;
|
||||||
|
|
||||||
|
control.send(new messages.GroupUpdateRequest(
|
||||||
|
new messages.GroupRecord(group.id, name, new messages.GroupPermissions(isBlacklist, permissions))
|
||||||
|
))
|
||||||
|
.then(() => setBaselineState(permissionState))
|
||||||
|
.catch(notifyErrorHandler("Error applying changes"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function revertChanges() {
|
||||||
|
if (!group) return;
|
||||||
|
setPermissionState(baselineState);
|
||||||
|
setName(group.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!synced) {
|
||||||
|
return <PageLayout nav={[{ name: "exp_groups" }, { name: String(groupId) }]}>
|
||||||
|
<Spin />
|
||||||
|
</PageLayout>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
return <PageLayout nav={[{ name: "exp_groups" }, { name: String(groupId) }]}>
|
||||||
|
<PageHeader title="Group not found" />
|
||||||
|
</PageLayout>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <PageLayout nav={[{ name: "Permission Groups", path: "/permission_groups" }, { name: group.name }]}>
|
||||||
|
<PageHeader
|
||||||
|
title={group.name}
|
||||||
|
extra={<Space wrap>
|
||||||
|
{canDelete && <DeletedConfirm placement="bottomRight" onConfirm={async () => {
|
||||||
|
control.send(new messages.GroupDeleteRequest(group.id));
|
||||||
|
navigate(`/permission_groups`);
|
||||||
|
}}/>}
|
||||||
|
</Space>}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{edited && (
|
||||||
|
<div style={{
|
||||||
|
position: "fixed",
|
||||||
|
bottom: 24,
|
||||||
|
left: "50%",
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
background: "#2a1912",
|
||||||
|
borderRadius: 8,
|
||||||
|
padding: "10px 14px",
|
||||||
|
display: "flex",
|
||||||
|
gap: 12,
|
||||||
|
zIndex: 1000,
|
||||||
|
}}>
|
||||||
|
<span>You have unsaved changes</span>
|
||||||
|
<Space>
|
||||||
|
<Button onClick={revertChanges}>Revert</Button>
|
||||||
|
<Button type="primary" onClick={applyChanges}>Apply</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 16, display: "flex", gap: 12 }}>
|
||||||
|
<label style={{ width: 110 }}>Name</label>
|
||||||
|
<Input value={name} disabled={!canUpdate} onChange={e => setName(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Permissions</h3>
|
||||||
|
|
||||||
|
{!defaultModPack?.exportManifest?.assets?.defines && (
|
||||||
|
<Alert
|
||||||
|
type="warning"
|
||||||
|
message="Missing export"
|
||||||
|
description="An export of the default mod pack is required to modify permissions."
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{defaultModPack?.exportManifest?.assets?.defines && !definesJson && <Spin />}
|
||||||
|
|
||||||
|
{definesJson && <>
|
||||||
|
<Input.Search
|
||||||
|
placeholder="Search permissions"
|
||||||
|
allowClear
|
||||||
|
onChange={e => setSearch(e.target.value)}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Space direction="vertical" style={{ width: "100%" }}>
|
||||||
|
{grouped.map(([groupName, permissions]) => (
|
||||||
|
<div key={groupName} style={{ border: "1px solid #424242", padding: 12, borderRadius: 6 }}>
|
||||||
|
|
||||||
|
<Space style={{ width: "100%", justifyContent: "space-between" }}>
|
||||||
|
<strong>{groupName}</strong>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
disabled={!canUpdate}
|
||||||
|
onClick={() => {
|
||||||
|
setPermissionState(prev => {
|
||||||
|
const next = { ...prev };
|
||||||
|
for (const p of permissions) next[p] = true;
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Select all
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
disabled={!canUpdate}
|
||||||
|
onClick={() => {
|
||||||
|
setPermissionState(prev => {
|
||||||
|
const next = { ...prev };
|
||||||
|
for (const p of permissions) next[p] = false;
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
marginTop: 12,
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))",
|
||||||
|
gap: 8,
|
||||||
|
}}>
|
||||||
|
{permissions.map(p => (
|
||||||
|
<Checkbox
|
||||||
|
key={p}
|
||||||
|
checked={permissionState[p]}
|
||||||
|
disabled={!canUpdate}
|
||||||
|
onChange={e =>
|
||||||
|
setPermissionState(prev => ({ ...prev, [p]: e.target.checked }))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{p}
|
||||||
|
</Checkbox>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</>}
|
||||||
|
</PageLayout>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import React, { useContext, useState } from "react";
|
||||||
|
import { Table } from "antd";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import { ControlContext } from "@clusterio/web_ui";
|
||||||
|
import { GroupRecord } from "../../messages.js";
|
||||||
|
import type { WebPlugin } from "..";
|
||||||
|
|
||||||
|
import GroupForm from "./GroupForm";
|
||||||
|
|
||||||
|
const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare;
|
||||||
|
|
||||||
|
export default function GroupsTable() {
|
||||||
|
const control = useContext(ControlContext);
|
||||||
|
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [groups, synced] = plugin.useGroups();
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Table
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: "Name",
|
||||||
|
dataIndex: "name",
|
||||||
|
sorter: (a: any, b: GroupRecord) => strcmp(a.name, b.name),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Permissions",
|
||||||
|
width: "50%",
|
||||||
|
render: (_: any, g: GroupRecord) => (
|
||||||
|
`${g.permissions.permissions.length} ${g.permissions.isBlacklist ? "Disallowed" : "Allowed"}`
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
dataSource={[...groups.values()]}
|
||||||
|
loading={!synced}
|
||||||
|
rowKey={(g) => g.id}
|
||||||
|
pagination={false}
|
||||||
|
onRow={(g) => ({
|
||||||
|
onClick: () => navigate(`/permission_groups/${g.id}/view`),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<GroupForm open={open} setOpen={setOpen} />
|
||||||
|
</>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import React, { useContext, useEffect } from "react";
|
||||||
|
import { Modal, Form, Select, InputNumber, Switch, Alert } from "antd";
|
||||||
|
|
||||||
|
import { ControlContext, useRoles } from "@clusterio/web_ui";
|
||||||
|
import { RoleMappingRecord, RoleMappingCreateRequest, RoleMappingUpdateRequest } from "../../messages.js";
|
||||||
|
import type { WebPlugin } from "..";
|
||||||
|
|
||||||
|
export default function RoleMappingForm({ open, setOpen, initial }: {
|
||||||
|
open: boolean,
|
||||||
|
setOpen: (open: boolean) => void,
|
||||||
|
initial?: RoleMappingRecord,
|
||||||
|
}) {
|
||||||
|
const control = useContext(ControlContext);
|
||||||
|
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||||
|
|
||||||
|
const [groups] = plugin.useGroups();
|
||||||
|
const [roles] = useRoles();
|
||||||
|
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
function submit(values: any) {
|
||||||
|
if (initial) {
|
||||||
|
control.send(new RoleMappingUpdateRequest(
|
||||||
|
new RoleMappingRecord(
|
||||||
|
initial.id,
|
||||||
|
new Set(values.roleIds),
|
||||||
|
values.groupId,
|
||||||
|
values.priority,
|
||||||
|
values.enabled,
|
||||||
|
)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
control.send(new RoleMappingCreateRequest(
|
||||||
|
values.roleIds,
|
||||||
|
values.groupId,
|
||||||
|
values.priority,
|
||||||
|
values.enabled,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
form.resetFields();
|
||||||
|
form.setFieldsValue({
|
||||||
|
...initial,
|
||||||
|
roleIds: initial ? [...initial.roleIds] : [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [open, initial]);
|
||||||
|
|
||||||
|
return <Modal
|
||||||
|
title={initial ? "Edit Role Mapping" : "Create Role Mapping"}
|
||||||
|
open={open}
|
||||||
|
onCancel={() => setOpen(false)}
|
||||||
|
onOk={() => form.submit()}
|
||||||
|
>
|
||||||
|
<Alert
|
||||||
|
type="info"
|
||||||
|
showIcon
|
||||||
|
message="How role mappings work"
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
description={
|
||||||
|
<ul style={{ paddingLeft: 16, margin: 0 }}>
|
||||||
|
<li>Manual assignments always take priority.</li>
|
||||||
|
<li>Mappings are checked in priority order (highest first).</li>
|
||||||
|
<li>A mapping applies only if the user has <b>all listed roles</b>.</li>
|
||||||
|
<li>The first matching mapping assigns the group.</li>
|
||||||
|
<li>If none match, the player is given all permissions.</li>
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form form={form} onFinish={submit}>
|
||||||
|
<Form.Item name="groupId" label="Group" rules={[{ required: true }]}>
|
||||||
|
<Select>
|
||||||
|
{[...groups.values()].map(g => (
|
||||||
|
<Select.Option key={g.id} value={g.id}>
|
||||||
|
{g.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="roleIds" label="Roles" rules={[{ required: true }]}>
|
||||||
|
<Select mode="multiple">
|
||||||
|
{[...roles.values()].map(r => (
|
||||||
|
<Select.Option key={r.id} value={r.id}>
|
||||||
|
{r.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="priority" label="Priority" initialValue={100}>
|
||||||
|
<InputNumber style={{ width: "100%" }} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="enabled" label="Enabled" valuePropName="checked" initialValue={true}>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import React, { useContext, useState } from "react";
|
||||||
|
import { Table, Button, Space, Tag } from "antd";
|
||||||
|
import { EditOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
|
import { ControlContext, useAccount, useRoles } from "@clusterio/web_ui";
|
||||||
|
import { RoleMappingRecord, RoleMappingDeleteRequest } from "../../messages.js";
|
||||||
|
import type { WebPlugin } from "..";
|
||||||
|
|
||||||
|
import RoleMappingForm from "./RoleMappingForm";
|
||||||
|
import DeletedConfirm from "./DeleteConfirm";
|
||||||
|
|
||||||
|
export default function RoleMappingsTable() {
|
||||||
|
const control = useContext(ControlContext);
|
||||||
|
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||||
|
const account = useAccount();
|
||||||
|
|
||||||
|
const [roleMappings, roleMappingsSynced] = plugin.useRoleMappings();
|
||||||
|
const [groups, groupsSynced] = plugin.useGroups();
|
||||||
|
const [roles, rolesSynced] = useRoles();
|
||||||
|
|
||||||
|
const [editing, setEditing] = useState<RoleMappingRecord | undefined>();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const roleMappingArray = [...roleMappings.values()];
|
||||||
|
|
||||||
|
const roleFilters = [...roles.values()]
|
||||||
|
.filter(role => roleMappingArray.some(mapping => mapping.roleIds.has(role.id)))
|
||||||
|
.map(role => ({ text: role.name, value: role.id }));
|
||||||
|
|
||||||
|
const groupFilters = [...groups.values()]
|
||||||
|
.filter(group => roleMappingArray.some(mapping => mapping.groupId === group.id))
|
||||||
|
.map(group => ({ text: group.name, value: group.id }))
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Table
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: "Priority",
|
||||||
|
dataIndex: "priority",
|
||||||
|
width: "10%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Roles",
|
||||||
|
filters: roleFilters,
|
||||||
|
onFilter: (value, record: RoleMappingRecord) => (
|
||||||
|
record.roleIds.has(value as number)
|
||||||
|
),
|
||||||
|
render: (_: any, record: RoleMappingRecord) => (
|
||||||
|
[...record.roleIds].map(id => <Tag key={id}>{roles.get(id)?.name ?? id}</Tag>)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Group",
|
||||||
|
width: "30%",
|
||||||
|
filters: groupFilters,
|
||||||
|
onFilter: (value, record: RoleMappingRecord) => (
|
||||||
|
record.groupId === value
|
||||||
|
),
|
||||||
|
render: (_: any, record: RoleMappingRecord) => (
|
||||||
|
groups.get(record.groupId)?.name ?? record.groupId
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Enabled",
|
||||||
|
width: "10%",
|
||||||
|
filters: [
|
||||||
|
{ text: "Enabled", value: true },
|
||||||
|
{ text: "Disabled", value: false },
|
||||||
|
],
|
||||||
|
onFilter: (value, record: RoleMappingRecord) => (
|
||||||
|
record.enabled === value
|
||||||
|
),
|
||||||
|
render: (_: any, record: RoleMappingRecord) => (
|
||||||
|
record.enabled ? "Yes" : "No"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
...(account.hasAnyPermission(
|
||||||
|
"exp_groups.role_mapping.update",
|
||||||
|
"exp_groups.role_mapping.delete",
|
||||||
|
) ? [{
|
||||||
|
title: "Actions",
|
||||||
|
width: "10%",
|
||||||
|
render: (_: any, record: any) => (
|
||||||
|
<Space>
|
||||||
|
{account.hasPermission("exp_groups.role_mapping.update") && <Button
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
setEditing(record);
|
||||||
|
setOpen(true);
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
{account.hasPermission("exp_groups.role_mapping.delete") && <DeletedConfirm
|
||||||
|
onConfirm={() => control.send(new RoleMappingDeleteRequest(record.id))}
|
||||||
|
/>}
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
}] : []),
|
||||||
|
]}
|
||||||
|
dataSource={roleMappingArray.sort((a, b) => b.priority - a.priority)}
|
||||||
|
loading={!roleMappingsSynced || !groupsSynced || !rolesSynced}
|
||||||
|
rowKey={(m) => m.id}
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RoleMappingForm open={open} setOpen={setOpen} initial={editing} />
|
||||||
|
</>;
|
||||||
|
}
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { Tree } from 'antd';
|
|
||||||
import type { TreeDataNode, TreeProps } from 'antd';
|
|
||||||
|
|
||||||
const defaultData: TreeDataNode[] = [
|
|
||||||
{
|
|
||||||
title: "Group 1",
|
|
||||||
key: "G-1",
|
|
||||||
icon: false,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
title: "Role 1",
|
|
||||||
key: "R-1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Role 2",
|
|
||||||
key: "R-2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Role 3",
|
|
||||||
key: "R-3"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Group 2",
|
|
||||||
key: "G-2",
|
|
||||||
icon: false,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
title: "Role 4",
|
|
||||||
key: "R-4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Role 5",
|
|
||||||
key: "R-5"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Default",
|
|
||||||
key: "G-3",
|
|
||||||
icon: false,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
title: "Role 6",
|
|
||||||
key: "R-6"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export function GroupTree() {
|
|
||||||
const [gData, setGData] = useState(defaultData);
|
|
||||||
|
|
||||||
const onDrop: TreeProps['onDrop'] = (info) => {
|
|
||||||
const dropKey = info.node.key;
|
|
||||||
const dragKey = info.dragNode.key;
|
|
||||||
const dropPos = info.node.pos.split('-');
|
|
||||||
const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); // the drop position relative to the drop node, inside 0, top -1, bottom 1
|
|
||||||
|
|
||||||
const findKey = (
|
|
||||||
data: TreeDataNode[],
|
|
||||||
key: React.Key,
|
|
||||||
callback: (node: TreeDataNode, i: number, data: TreeDataNode[]) => void,
|
|
||||||
) => {
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
if (data[i].key === key) {
|
|
||||||
return callback(data[i], i, data);
|
|
||||||
}
|
|
||||||
if (data[i].children) {
|
|
||||||
findKey(data[i].children!, key, callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const data = [...gData]
|
|
||||||
|
|
||||||
// Find dragObject
|
|
||||||
let dragObj: TreeDataNode;
|
|
||||||
findKey(data, dragKey, (item, index, arr) => {
|
|
||||||
arr.splice(index, 1);
|
|
||||||
dragObj = item;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!info.dropToGap) {
|
|
||||||
// Drop on the content
|
|
||||||
findKey(data, dropKey, (item) => {
|
|
||||||
item.children = item.children || [];
|
|
||||||
// where to insert. New item was inserted to the start of the array in this example, but can be anywhere
|
|
||||||
item.children.unshift(dragObj);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
let ar: TreeDataNode[] = [];
|
|
||||||
let i: number;
|
|
||||||
findKey(data, dropKey, (_item, index, arr) => {
|
|
||||||
ar = arr;
|
|
||||||
i = index;
|
|
||||||
});
|
|
||||||
if (dropPosition === -1) {
|
|
||||||
// Drop on the top of the drop node
|
|
||||||
ar.splice(i!, 0, dragObj!);
|
|
||||||
} else {
|
|
||||||
// Drop on the bottom of the drop node
|
|
||||||
ar.splice(i! + 1, 0, dragObj!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setGData(data)
|
|
||||||
};
|
|
||||||
|
|
||||||
const allowDrop: TreeProps['allowDrop'] = ({dragNode, dropNode, dropPosition}) => {
|
|
||||||
const dragType = (dragNode.key as string).charAt(0);
|
|
||||||
const dropType = (dropNode.key as string).charAt(0);
|
|
||||||
return dropType === dragType && dropPosition != 0 || dragType === "R" && dropType === "G" && dropPosition == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tree
|
|
||||||
className="draggable-tree"
|
|
||||||
defaultExpandAll={true}
|
|
||||||
draggable
|
|
||||||
blockNode
|
|
||||||
onDrop={onDrop}
|
|
||||||
allowDrop={allowDrop}
|
|
||||||
treeData={gData}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
+83
-57
@@ -1,82 +1,108 @@
|
|||||||
import React, {
|
import React, { useState, useCallback, useSyncExternalStore } from "react";
|
||||||
useContext, useEffect, useState,
|
import { BaseWebPlugin, PageLayout, PageHeader, useAccount, SectionHeader } from "@clusterio/web_ui";
|
||||||
useCallback, useSyncExternalStore,
|
import { Button } from "antd";
|
||||||
} from "react";
|
|
||||||
|
|
||||||
// import {
|
|
||||||
//
|
|
||||||
// } from "antd";
|
|
||||||
|
|
||||||
import {
|
|
||||||
BaseWebPlugin, PageLayout, PageHeader, Control, ControlContext, notifyErrorHandler,
|
|
||||||
useInstances,
|
|
||||||
} from "@clusterio/web_ui";
|
|
||||||
|
|
||||||
import { PermissionGroupUpdate, PermissionInstanceId, PermissionStringsUpdate } from "../messages";
|
|
||||||
|
|
||||||
|
import * as messages from "../messages.js";
|
||||||
import * as lib from "@clusterio/lib";
|
import * as lib from "@clusterio/lib";
|
||||||
|
|
||||||
import { GroupTree } from "./components/groupTree";
|
import GroupsTable from "./components/GroupsTable";
|
||||||
|
import AssignmentsTable from "./components/AssignmentsTable";
|
||||||
|
import RoleMappingsTable from "./components/RoleMappingsTable";
|
||||||
|
import GroupViewPage from "./components/GroupViewPage";
|
||||||
|
import GroupForm from "./components/GroupForm";
|
||||||
|
import RoleMappingForm from "./components/RoleMappingForm";
|
||||||
|
import AssignmentForm from "./components/AssignmentForm";
|
||||||
|
|
||||||
function MyTemplatePage() {
|
|
||||||
const control = useContext(ControlContext);
|
|
||||||
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
|
||||||
const [permissionStrings, permissionStringsSynced] = plugin.usePermissionStrings();
|
|
||||||
const [permissionGroups, permissionGroupsSynced] = plugin.usePermissionGroups();
|
|
||||||
const [instances, instancesSync] = useInstances();
|
|
||||||
|
|
||||||
let [roles, setRoles] = useState<lib.Role[]>([]);
|
function ExpGroupsPage() {
|
||||||
|
const account = useAccount();
|
||||||
|
|
||||||
useEffect(() => {
|
const [groupOpen, setGroupOpen] = useState(false);
|
||||||
control.send(new lib.RoleListRequest()).then(newRoles => {
|
const [assignmentOpen, setAssignmentOpen] = useState(false);
|
||||||
setRoles(newRoles);
|
const [roleMappingOpen, setRoleMappingOpen] = useState(false);
|
||||||
}).catch(notifyErrorHandler("Error fetching role list"));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return <PageLayout nav={[{ name: "exp_groups" }]}>
|
return <PageLayout nav={[{ name: "Permission Groups" }]}>
|
||||||
<PageHeader title="exp_groups" />
|
<PageHeader title="Permission Groups" />
|
||||||
Permission Strings: {String(permissionStringsSynced)} {JSON.stringify([...permissionStrings.values()])} <br/>
|
|
||||||
Permission Groups: {String(permissionGroupsSynced)} {JSON.stringify([...permissionGroups.values()])} <br/>
|
{account.hasPermission("exp_groups.group.list") && <>
|
||||||
Instances: {String(instancesSync)} {JSON.stringify([...instances.values()].map(instance => [instance.id, instance.name]))} <br/>
|
<SectionHeader
|
||||||
Roles: {JSON.stringify([...roles.values()].map(role => [role.id, role.name]))} <br/>
|
title="Groups"
|
||||||
<GroupTree/>
|
extra={
|
||||||
|
account.hasPermission("exp_groups.group.create")
|
||||||
|
? <Button type="primary" onClick={() => setGroupOpen(true)}>Create</Button>
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<GroupsTable />
|
||||||
|
<GroupForm open={groupOpen} setOpen={setGroupOpen} />
|
||||||
|
</>}
|
||||||
|
|
||||||
|
{account.hasPermission("exp_groups.role_mapping.list") && <>
|
||||||
|
<SectionHeader
|
||||||
|
title="Role Mappings"
|
||||||
|
extra={
|
||||||
|
account.hasPermission("exp_groups.role_mapping.create")
|
||||||
|
? <Button type="primary" onClick={() => setRoleMappingOpen(true)}>Create</Button>
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<RoleMappingsTable />
|
||||||
|
<RoleMappingForm open={roleMappingOpen} setOpen={setRoleMappingOpen} />
|
||||||
|
</>}
|
||||||
|
|
||||||
|
{account.hasPermission("exp_groups.assignment.list") && <>
|
||||||
|
<SectionHeader
|
||||||
|
title="Assignments"
|
||||||
|
extra={
|
||||||
|
account.hasPermission("exp_groups.assignment.create")
|
||||||
|
? <Button type="primary" onClick={() => setAssignmentOpen(true)}>Create</Button>
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AssignmentsTable />
|
||||||
|
<AssignmentForm open={assignmentOpen} setOpen={setAssignmentOpen} />
|
||||||
|
</>}
|
||||||
</PageLayout>;
|
</PageLayout>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebPlugin extends BaseWebPlugin {
|
export class WebPlugin extends BaseWebPlugin {
|
||||||
permissionStrings = new lib.EventSubscriber(PermissionStringsUpdate, this.control);
|
groups = new lib.MapSubscriber(messages.GroupUpdatedEvent, this.control);
|
||||||
permissionGroups = new lib.EventSubscriber(PermissionGroupUpdate, this.control);
|
assignments = new lib.MapSubscriber(messages.ManualAssignmentUpdatedEvent, this.control);
|
||||||
|
roleMappings = new lib.MapSubscriber(messages.RoleMappingUpdatedEvent, this.control);
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
this.pages = [
|
this.pages = [
|
||||||
{
|
{
|
||||||
path: "/exp_groups",
|
path: "/permission_groups",
|
||||||
sidebarName: "exp_groups",
|
sidebarName: "Permission Groups",
|
||||||
permission: "exp_groups.list",
|
permission: (account => account.hasAnyPermission(
|
||||||
content: <MyTemplatePage/>,
|
"exp_groups.group.list",
|
||||||
|
"exp_groups.assignment.list",
|
||||||
|
"exp_groups.role_mapping.list",
|
||||||
|
)),
|
||||||
|
content: <ExpGroupsPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/permission_groups/:id/view",
|
||||||
|
sidebarPath: "/permission_groups",
|
||||||
|
permission: "exp_groups.group.get",
|
||||||
|
content: <GroupViewPage />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
useInstancePermissionStrings(instanceId?: PermissionInstanceId) {
|
useGroups() {
|
||||||
const [permissionStrings, synced] = this.usePermissionStrings();
|
const subscribe = useCallback((cb: () => void) => this.groups.subscribe(cb), []);
|
||||||
return [instanceId !== undefined ? permissionStrings.get(instanceId) : undefined, synced] as const;
|
return useSyncExternalStore(subscribe, () => this.groups.getSnapshot());
|
||||||
}
|
}
|
||||||
|
|
||||||
usePermissionStrings() {
|
useAssignments() {
|
||||||
const control = useContext(ControlContext);
|
const subscribe = useCallback((cb: () => void) => this.assignments.subscribe(cb), []);
|
||||||
const subscribe = useCallback((callback: () => void) => this.permissionStrings.subscribe(callback), [control]);
|
return useSyncExternalStore(subscribe, () => this.assignments.getSnapshot());
|
||||||
return useSyncExternalStore(subscribe, () => this.permissionStrings.getSnapshot());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useInstancePermissionGroups(instanceId?: PermissionInstanceId) {
|
useRoleMappings() {
|
||||||
const [permissionGroups, synced] = this.usePermissionGroups();
|
const subscribe = useCallback((cb: () => void) => this.roleMappings.subscribe(cb), []);
|
||||||
return [instanceId !== undefined ? [...permissionGroups.values()].filter(group => group.instanceId === instanceId) : undefined, synced] as const;
|
return useSyncExternalStore(subscribe, () => this.roleMappings.getSnapshot());
|
||||||
}
|
|
||||||
|
|
||||||
usePermissionGroups() {
|
|
||||||
const control = useContext(ControlContext);
|
|
||||||
const subscribe = useCallback((callback: () => void) => this.permissionGroups.subscribe(callback), [control]);
|
|
||||||
return useSyncExternalStore(subscribe, () => this.permissionGroups.getSnapshot());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ module.exports = (env = {}) => merge(common(env), {
|
|||||||
"antd": { import: false },
|
"antd": { import: false },
|
||||||
"react": { import: false },
|
"react": { import: false },
|
||||||
"react-dom": { import: false },
|
"react-dom": { import: false },
|
||||||
|
"react-router": { import: false },
|
||||||
|
"react-router-dom": { import: false },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
+1
-1
@@ -4,5 +4,5 @@ export const plugin: lib.PluginDeclaration = {
|
|||||||
name: "exp_gui",
|
name: "exp_gui",
|
||||||
title: "exp_gui",
|
title: "exp_gui",
|
||||||
description: "Example Description. Plugin. Change me in index.ts",
|
description: "Example Description. Plugin. Change me in index.ts",
|
||||||
instanceEntrypoint: "./dist/node/instance",
|
instanceEntrypoint: "./dist/node/instance.js",
|
||||||
};
|
};
|
||||||
|
|||||||
+17
-12
@@ -16,7 +16,7 @@ Storage.register(player_elements, function(tbl)
|
|||||||
player_elements = tbl
|
player_elements = tbl
|
||||||
end)
|
end)
|
||||||
|
|
||||||
--- @class Gui
|
--- @class (partial) Gui
|
||||||
local Gui = {
|
local Gui = {
|
||||||
define = ExpElement.new,
|
define = ExpElement.new,
|
||||||
from_argument = ExpElement.from_argument,
|
from_argument = ExpElement.from_argument,
|
||||||
@@ -46,9 +46,11 @@ end
|
|||||||
--- @return LuaPlayer
|
--- @return LuaPlayer
|
||||||
function Gui.get_player(input)
|
function Gui.get_player(input)
|
||||||
if type(input) == "table" and not input.player_index then
|
if type(input) == "table" and not input.player_index then
|
||||||
return assert(game.get_player(input.element.player_index))
|
--- @cast input { element: LuaGuiElement }
|
||||||
|
return (assert(game.get_player(input.element.player_index)))
|
||||||
end
|
end
|
||||||
return assert(game.get_player(input.player_index))
|
--- @cast input LuaGuiElement | { player_index: uint }
|
||||||
|
return (assert(game.get_player(input.player_index)))
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Toggle the enable state of an element
|
--- Toggle the enable state of an element
|
||||||
@@ -88,7 +90,7 @@ end
|
|||||||
--- @param define ExpElement
|
--- @param define ExpElement
|
||||||
--- @param visible Gui.VisibleCallback | boolean | nil
|
--- @param visible Gui.VisibleCallback | boolean | nil
|
||||||
function Gui.add_top_element(define, visible)
|
function Gui.add_top_element(define, visible)
|
||||||
assert(Gui.top_elements[define.name] == nil, "Element is already added to the top flow")
|
assert(Gui.top_elements[define] == nil, "Element is already added to the top flow")
|
||||||
Gui.top_elements[define] = visible or false
|
Gui.top_elements[define] = visible or false
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -96,7 +98,7 @@ end
|
|||||||
--- @param define ExpElement
|
--- @param define ExpElement
|
||||||
--- @param visible Gui.VisibleCallback | boolean | nil
|
--- @param visible Gui.VisibleCallback | boolean | nil
|
||||||
function Gui.add_left_element(define, visible)
|
function Gui.add_left_element(define, visible)
|
||||||
assert(Gui.left_elements[define.name] == nil, "Element is already added to the left flow")
|
assert(Gui.left_elements[define] == nil, "Element is already added to the left flow")
|
||||||
Gui.left_elements[define] = visible or false
|
Gui.left_elements[define] = visible or false
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -105,7 +107,7 @@ end
|
|||||||
--- @param define ExpElement
|
--- @param define ExpElement
|
||||||
--- @param visible Gui.VisibleCallback | boolean | nil
|
--- @param visible Gui.VisibleCallback | boolean | nil
|
||||||
function Gui.add_relative_element(define, visible)
|
function Gui.add_relative_element(define, visible)
|
||||||
assert(Gui.relative_elements[define.name] == nil, "Element is already added to the relative flow")
|
assert(Gui.relative_elements[define] == nil, "Element is already added to the relative flow")
|
||||||
Gui.relative_elements[define] = visible or false
|
Gui.relative_elements[define] = visible or false
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -114,7 +116,8 @@ end
|
|||||||
--- @param player LuaPlayer
|
--- @param player LuaPlayer
|
||||||
--- @return LuaGuiElement
|
--- @return LuaGuiElement
|
||||||
function Gui.get_top_element(define, player)
|
function Gui.get_top_element(define, player)
|
||||||
return assert(player_elements[player.index].top[define.name], "Element is not on the top flow")
|
local elements = assert(player_elements[player.index])
|
||||||
|
return (assert(elements.top[define.name], "Element is not on the top flow"))
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Register a element define to be drawn to the left flow on join
|
--- Register a element define to be drawn to the left flow on join
|
||||||
@@ -122,7 +125,8 @@ end
|
|||||||
--- @param player LuaPlayer
|
--- @param player LuaPlayer
|
||||||
--- @return LuaGuiElement
|
--- @return LuaGuiElement
|
||||||
function Gui.get_left_element(define, player)
|
function Gui.get_left_element(define, player)
|
||||||
return assert(player_elements[player.index].left[define.name], "Element is not on the left flow")
|
local elements = assert(player_elements[player.index])
|
||||||
|
return (assert(elements.left[define.name], "Element is not on the left flow"))
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Register a element define to be drawn to the relative flow on join
|
--- Register a element define to be drawn to the relative flow on join
|
||||||
@@ -130,7 +134,8 @@ end
|
|||||||
--- @param player LuaPlayer
|
--- @param player LuaPlayer
|
||||||
--- @return LuaGuiElement
|
--- @return LuaGuiElement
|
||||||
function Gui.get_relative_element(define, player)
|
function Gui.get_relative_element(define, player)
|
||||||
return assert(player_elements[player.index].relative[define.name], "Element is not on the relative flow")
|
local elements = assert(player_elements[player.index])
|
||||||
|
return (assert(elements.relative[define.name], "Element is not on the relative flow"))
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Ensure all the correct elements are visible and exist
|
--- Ensure all the correct elements are visible and exist
|
||||||
@@ -188,9 +193,9 @@ function Gui._ensure_consistency(event)
|
|||||||
|
|
||||||
-- This check isn't needed, but allows the toolbar file to be deleted without modifying any lib code
|
-- This check isn't needed, but allows the toolbar file to be deleted without modifying any lib code
|
||||||
if Gui.toolbar then
|
if Gui.toolbar then
|
||||||
--- @diagnostic disable-next-line invisible
|
--- @diagnostic disable-next-line: access-invisible
|
||||||
Gui.toolbar._create_elements(player)
|
Gui.toolbar._create_elements(player)
|
||||||
--- @diagnostic disable-next-line invisible
|
--- @diagnostic disable-next-line: access-invisible
|
||||||
Gui.toolbar._ensure_consistency(player)
|
Gui.toolbar._ensure_consistency(player)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -211,7 +216,7 @@ local function on_gui_opened(event)
|
|||||||
|
|
||||||
if visible then
|
if visible then
|
||||||
event.element = element
|
event.element = element
|
||||||
--- @diagnostic disable-next-line invisible
|
--- @diagnostic disable-next-line: access-invisible
|
||||||
define:raise_event(event)
|
define:raise_event(event)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ local GuiData = {
|
|||||||
--- @field player_data table<uint, any>
|
--- @field player_data table<uint, any>
|
||||||
--- @field force_data table<uint, any>
|
--- @field force_data table<uint, any>
|
||||||
--- @field global_data table
|
--- @field global_data table
|
||||||
|
--- @field [DataKey] any
|
||||||
-- This class has no prototype methods
|
-- This class has no prototype methods
|
||||||
-- Same as raw but __index ensures the values exist
|
-- Same as raw but __index ensures the values exist
|
||||||
|
|
||||||
@@ -89,13 +90,16 @@ function GuiData._metatable.__index(self, key)
|
|||||||
assert(type(key) == "userdata", "Index type '" .. ExpUtil.get_class_name(key) .. "' given to GuiData. Must be of type userdata.")
|
assert(type(key) == "userdata", "Index type '" .. ExpUtil.get_class_name(key) .. "' given to GuiData. Must be of type userdata.")
|
||||||
local object_name = key.object_name --- @diagnostic disable-line assign-type-mismatch
|
local object_name = key.object_name --- @diagnostic disable-line assign-type-mismatch
|
||||||
if object_name == "LuaGuiElement" then
|
if object_name == "LuaGuiElement" then
|
||||||
|
--- @cast key LuaGuiElement
|
||||||
local data = self._raw.element_data
|
local data = self._raw.element_data
|
||||||
local player_elements = data and data[key.player_index]
|
local player_elements = data and data[key.player_index]
|
||||||
return player_elements and player_elements[key.index]
|
return player_elements and player_elements[key.index]
|
||||||
elseif object_name == "LuaPlayer" then
|
elseif object_name == "LuaPlayer" then
|
||||||
|
--- @cast key LuaPlayer
|
||||||
local data = self._raw.player_data
|
local data = self._raw.player_data
|
||||||
return data and data[key.index]
|
return data and data[key.index]
|
||||||
elseif object_name == "LuaForce" then
|
elseif object_name == "LuaForce" then
|
||||||
|
--- @cast key LuaForce
|
||||||
local data = self._raw.force_data
|
local data = self._raw.force_data
|
||||||
return data and data[key.index]
|
return data and data[key.index]
|
||||||
else
|
else
|
||||||
@@ -153,7 +157,7 @@ end
|
|||||||
--- @param scope string
|
--- @param scope string
|
||||||
--- @return GuiData
|
--- @return GuiData
|
||||||
function GuiData.get(scope)
|
function GuiData.get(scope)
|
||||||
return GuiData._scopes[scope] --[[ @as GuiData ]]
|
return GuiData._scopes[scope] --[[@as GuiData]]
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Used to clean up data from destroyed elements
|
--- Used to clean up data from destroyed elements
|
||||||
@@ -168,7 +172,7 @@ local function on_object_destroyed(event)
|
|||||||
for _, scope in pairs(registered_scopes) do
|
for _, scope in pairs(registered_scopes) do
|
||||||
local data = scope._raw.element_data
|
local data = scope._raw.element_data
|
||||||
local player_elements = data and data[player_index]
|
local player_elements = data and data[player_index]
|
||||||
if player_elements then
|
if data and player_elements then
|
||||||
player_elements[element_index] = nil
|
player_elements[element_index] = nil
|
||||||
if not next(player_elements) then
|
if not next(player_elements) then
|
||||||
data[player_index] = nil
|
data[player_index] = nil
|
||||||
|
|||||||
+17
-17
@@ -1,4 +1,4 @@
|
|||||||
--- @class Gui
|
--- @class (partial) Gui
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
|
|
||||||
--- @class Gui.elements
|
--- @class Gui.elements
|
||||||
@@ -28,7 +28,7 @@ Elements.aligned_flow = Gui.define("aligned_flow")
|
|||||||
vertically_stretchable = vertical_align ~= "center",
|
vertically_stretchable = vertical_align ~= "center",
|
||||||
horizontally_stretchable = horizontal_align ~= "center",
|
horizontally_stretchable = horizontal_align ~= "center",
|
||||||
}
|
}
|
||||||
end) --[[ @as any ]]
|
end) --[[@as any]]
|
||||||
|
|
||||||
--- A solid horizontal white bar element
|
--- A solid horizontal white bar element
|
||||||
--- @class Gui.elements.bar: ExpElement
|
--- @class Gui.elements.bar: ExpElement
|
||||||
@@ -48,11 +48,11 @@ Elements.bar = Gui.define("bar")
|
|||||||
else
|
else
|
||||||
style.horizontally_stretchable = true
|
style.horizontally_stretchable = true
|
||||||
end
|
end
|
||||||
end) --[[ @as any ]]
|
end) --[[@as any]]
|
||||||
|
|
||||||
--- A label which is centered
|
--- A label which is centered
|
||||||
--- @class Gui.elements.centered_label: ExpElement
|
--- @class Gui.elements.centered_label: ExpElement
|
||||||
--- @overload fun(parent: LuaGuiElement, width: number, caption: LocalisedString, tooltip: LocalisedString?): LuaGuiElement
|
--- @overload fun(parent: LuaGuiElement, width: number, caption: LocalisedString?, tooltip: LocalisedString?): LuaGuiElement
|
||||||
Elements.centered_label = Gui.define("centered_label")
|
Elements.centered_label = Gui.define("centered_label")
|
||||||
:draw{
|
:draw{
|
||||||
type = "label",
|
type = "label",
|
||||||
@@ -63,7 +63,7 @@ Elements.centered_label = Gui.define("centered_label")
|
|||||||
horizontal_align = "center",
|
horizontal_align = "center",
|
||||||
single_line = false,
|
single_line = false,
|
||||||
width = Gui.from_argument(1),
|
width = Gui.from_argument(1),
|
||||||
} --[[ @as any ]]
|
} --[[@as any]]
|
||||||
|
|
||||||
--- A label which has two white bars on either side of it
|
--- A label which has two white bars on either side of it
|
||||||
--- @class Gui.elements.title_label: ExpElement
|
--- @class Gui.elements.title_label: ExpElement
|
||||||
@@ -91,7 +91,7 @@ Elements.title_label = Gui.define("title_label")
|
|||||||
Elements.bar(flow)
|
Elements.bar(flow)
|
||||||
|
|
||||||
return label
|
return label
|
||||||
end) --[[ @as any ]]
|
end) --[[@as any]]
|
||||||
|
|
||||||
--- A fixed size vertical scroll pane
|
--- A fixed size vertical scroll pane
|
||||||
--- @class Gui.elements.scroll_pane: ExpElement
|
--- @class Gui.elements.scroll_pane: ExpElement
|
||||||
@@ -109,7 +109,7 @@ Elements.scroll_pane = Gui.define("scroll_pane")
|
|||||||
padding = { 1, 3 },
|
padding = { 1, 3 },
|
||||||
maximal_height = Gui.from_argument(1),
|
maximal_height = Gui.from_argument(1),
|
||||||
horizontally_stretchable = true,
|
horizontally_stretchable = true,
|
||||||
} --[[ @as any ]]
|
} --[[@as any]]
|
||||||
|
|
||||||
--- A fixed size vertical scroll pane containing a table
|
--- A fixed size vertical scroll pane containing a table
|
||||||
--- @class Gui.elements.scroll_table: ExpElement
|
--- @class Gui.elements.scroll_table: ExpElement
|
||||||
@@ -132,7 +132,7 @@ Elements.scroll_table = Gui.define("scroll_table")
|
|||||||
cell_padding = 1,
|
cell_padding = 1,
|
||||||
vertical_align = "center",
|
vertical_align = "center",
|
||||||
horizontally_stretchable = true,
|
horizontally_stretchable = true,
|
||||||
} --[[ @as any ]]
|
} --[[@as any]]
|
||||||
|
|
||||||
--- A container frame
|
--- A container frame
|
||||||
--- @class Gui.elements.container: ExpElement
|
--- @class Gui.elements.container: ExpElement
|
||||||
@@ -159,13 +159,13 @@ Elements.container = Gui.define("container")
|
|||||||
vertically_stretchable = false,
|
vertically_stretchable = false,
|
||||||
horizontally_stretchable = false,
|
horizontally_stretchable = false,
|
||||||
minimal_width = Gui.from_argument(1),
|
minimal_width = Gui.from_argument(1),
|
||||||
} --[[ @as any ]]
|
} --[[@as any]]
|
||||||
|
|
||||||
--- Get the root element of a container
|
--- Get the root element of a container
|
||||||
--- @param container LuaGuiElement
|
--- @param container LuaGuiElement
|
||||||
--- @return LuaGuiElement
|
--- @return LuaGuiElement
|
||||||
function Elements.container.get_root_element(container)
|
function Elements.container.get_root_element(container)
|
||||||
return container.parent
|
return assert(container.parent)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- A frame within a container
|
--- A frame within a container
|
||||||
@@ -183,7 +183,7 @@ Elements.subframe_base = Gui.define("container_subframe")
|
|||||||
padding = { 3, 6, 0, 6 },
|
padding = { 3, 6, 0, 6 },
|
||||||
use_header_filler = false,
|
use_header_filler = false,
|
||||||
horizontally_stretchable = true,
|
horizontally_stretchable = true,
|
||||||
} --[[ @as any ]]
|
} --[[@as any]]
|
||||||
|
|
||||||
--- @class Gui.elements.header.opts
|
--- @class Gui.elements.header.opts
|
||||||
--- @field name string?
|
--- @field name string?
|
||||||
@@ -212,7 +212,7 @@ Elements.header = Gui.define("container_header")
|
|||||||
subframe.add{ type = "empty-widget" }.style.horizontally_stretchable = true
|
subframe.add{ type = "empty-widget" }.style.horizontally_stretchable = true
|
||||||
|
|
||||||
return subframe
|
return subframe
|
||||||
end) --[[ @as any ]]
|
end) --[[@as any]]
|
||||||
|
|
||||||
--- @class Gui.elements.footer.opts
|
--- @class Gui.elements.footer.opts
|
||||||
--- @field name string?
|
--- @field name string?
|
||||||
@@ -241,7 +241,7 @@ Elements.footer = Gui.define("container_footer")
|
|||||||
subframe.add{ type = "empty-widget" }.style.horizontally_stretchable = true
|
subframe.add{ type = "empty-widget" }.style.horizontally_stretchable = true
|
||||||
|
|
||||||
return subframe
|
return subframe
|
||||||
end) --[[ @as any ]]
|
end) --[[@as any]]
|
||||||
|
|
||||||
--- A button used to destroy its target when clicked, intended for screen frames
|
--- A button used to destroy its target when clicked, intended for screen frames
|
||||||
--- @class Gui.elements.screen_frame_close: ExpElement
|
--- @class Gui.elements.screen_frame_close: ExpElement
|
||||||
@@ -259,7 +259,7 @@ Elements.screen_frame_close = Gui.define("screen_frame_close")
|
|||||||
:on_click(function(def, player, element, event)
|
:on_click(function(def, player, element, event)
|
||||||
--- @cast def Gui.elements.screen_frame_close
|
--- @cast def Gui.elements.screen_frame_close
|
||||||
Gui.destroy_if_valid(def.data[element])
|
Gui.destroy_if_valid(def.data[element])
|
||||||
end) --[[ @as any ]]
|
end) --[[@as any]]
|
||||||
|
|
||||||
--- A draggable frame with close button and button flow
|
--- A draggable frame with close button and button flow
|
||||||
--- @class Gui.elements.screen_frame: ExpElement
|
--- @class Gui.elements.screen_frame: ExpElement
|
||||||
@@ -313,20 +313,20 @@ Elements.screen_frame = Gui.define("screen_frame")
|
|||||||
direction = "vertical",
|
direction = "vertical",
|
||||||
style = "inside_shallow_frame_packed",
|
style = "inside_shallow_frame_packed",
|
||||||
}
|
}
|
||||||
end) --[[ @as any ]]
|
end) --[[@as any]]
|
||||||
|
|
||||||
--- Get the button flow for a screen frame
|
--- Get the button flow for a screen frame
|
||||||
--- @param screen_frame LuaGuiElement
|
--- @param screen_frame LuaGuiElement
|
||||||
--- @return LuaGuiElement
|
--- @return LuaGuiElement
|
||||||
function Elements.screen_frame.get_button_flow(screen_frame)
|
function Elements.screen_frame.get_button_flow(screen_frame)
|
||||||
return assert(Elements.screen_frame.data[screen_frame.parent], "Screen frame has no button flow")
|
return (assert(Elements.screen_frame.data[assert(screen_frame.parent)], "Screen frame has no button flow"))
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get the root element of a screen frame
|
--- Get the root element of a screen frame
|
||||||
--- @param screen_frame LuaGuiElement
|
--- @param screen_frame LuaGuiElement
|
||||||
--- @return LuaGuiElement
|
--- @return LuaGuiElement
|
||||||
function Elements.screen_frame.get_root_element(screen_frame)
|
function Elements.screen_frame.get_root_element(screen_frame)
|
||||||
return screen_frame.parent
|
return assert(screen_frame.parent)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Elements
|
return Elements
|
||||||
|
|||||||
@@ -166,7 +166,6 @@ end
|
|||||||
function GuiIter.get_tracked_elements(scope, filter)
|
function GuiIter.get_tracked_elements(scope, filter)
|
||||||
local class_name = ExpUtil.get_class_name(filter)
|
local class_name = ExpUtil.get_class_name(filter)
|
||||||
if class_name == "nil" then
|
if class_name == "nil" then
|
||||||
--- @cast filter nil
|
|
||||||
return GuiIter.all_elements(scope)
|
return GuiIter.all_elements(scope)
|
||||||
elseif class_name == "LuaPlayer" then
|
elseif class_name == "LuaPlayer" then
|
||||||
--- @cast filter LuaPlayer
|
--- @cast filter LuaPlayer
|
||||||
@@ -189,7 +188,6 @@ end
|
|||||||
function GuiIter.get_online_elements(scope, filter)
|
function GuiIter.get_online_elements(scope, filter)
|
||||||
local class_name = ExpUtil.get_class_name(filter)
|
local class_name = ExpUtil.get_class_name(filter)
|
||||||
if class_name == "nil" then
|
if class_name == "nil" then
|
||||||
--- @cast filter nil
|
|
||||||
return GuiIter.filtered_elements(scope, game.connected_players)
|
return GuiIter.filtered_elements(scope, game.connected_players)
|
||||||
elseif class_name == "LuaPlayer" then
|
elseif class_name == "LuaPlayer" then
|
||||||
--- @cast filter LuaPlayer
|
--- @cast filter LuaPlayer
|
||||||
@@ -235,7 +233,7 @@ end
|
|||||||
function GuiIter.remove_element(scope, player_index, element_index)
|
function GuiIter.remove_element(scope, player_index, element_index)
|
||||||
local scope_elements = registered_scopes[scope]
|
local scope_elements = registered_scopes[scope]
|
||||||
if not scope_elements then return end
|
if not scope_elements then return end
|
||||||
local player_elements = registered_scopes[player_index]
|
local player_elements = scope_elements[player_index]
|
||||||
if not player_elements then return end
|
if not player_elements then return end
|
||||||
player_elements[element_index] = nil
|
player_elements[element_index] = nil
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ ExpElement.events = {}
|
|||||||
--- @field _force_data ExpElement.PostDrawCallback?
|
--- @field _force_data ExpElement.PostDrawCallback?
|
||||||
--- @field _global_data ExpElement.PostDrawCallback?
|
--- @field _global_data ExpElement.PostDrawCallback?
|
||||||
--- @field _events table<defines.events, ExpElement.EventHandler<EventData>[]>
|
--- @field _events table<defines.events, ExpElement.EventHandler<EventData>[]>
|
||||||
|
--- @field _has_handlers boolean
|
||||||
|
--- @field _track_elements boolean
|
||||||
--- @overload fun(parent: LuaGuiElement, ...: any): LuaGuiElement
|
--- @overload fun(parent: LuaGuiElement, ...: any): LuaGuiElement
|
||||||
ExpElement._prototype = {
|
ExpElement._prototype = {
|
||||||
_track_elements = false,
|
_track_elements = false,
|
||||||
@@ -158,7 +160,7 @@ function ExpElement.new(name)
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ExpElement._elements[element_name] = instance --[[ @as ExpElement ]]
|
ExpElement._elements[element_name] = instance --[[@as ExpElement]]
|
||||||
return setmetatable(instance, ExpElement._metatable)
|
return setmetatable(instance, ExpElement._metatable)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -166,7 +168,7 @@ end
|
|||||||
--- @param name string
|
--- @param name string
|
||||||
--- @return ExpElement
|
--- @return ExpElement
|
||||||
function ExpElement.get(name)
|
function ExpElement.get(name)
|
||||||
return assert(ExpElement._elements[name], "ExpElement is not defined: " .. tostring(name))
|
return (assert(ExpElement._elements[name], "ExpElement is not defined: " .. tostring(name)))
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Create a new instance of this element definition
|
--- Create a new instance of this element definition
|
||||||
@@ -274,6 +276,7 @@ function ExpElement._prototype:draw(definition)
|
|||||||
|
|
||||||
if not next(signals) then
|
if not next(signals) then
|
||||||
-- If no signals then skip var arg
|
-- If no signals then skip var arg
|
||||||
|
--- @cast definition LuaGuiElement.add_param
|
||||||
self._draw = function(_, parent)
|
self._draw = function(_, parent)
|
||||||
return parent.add(definition)
|
return parent.add(definition)
|
||||||
end
|
end
|
||||||
@@ -459,7 +462,7 @@ function ExpElement._prototype:raise_event(event)
|
|||||||
for _, handler in ipairs(handlers) do
|
for _, handler in ipairs(handlers) do
|
||||||
-- All gui elements will contain player and element, other events might have these as nil
|
-- All gui elements will contain player and element, other events might have these as nil
|
||||||
-- Therefore only the signature of on_event has these values as optional
|
-- Therefore only the signature of on_event has these values as optional
|
||||||
handler(self, player --[[ @as LuaPlayer ]], event.element, event --[[ @as EventData ]])
|
handler(self, player --[[@as LuaPlayer]], event.element, event --[[@as EventData]])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
--- @class Gui
|
--- @class (partial) Gui
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
|
|
||||||
--- @class Gui.styles
|
--- @class Gui.styles
|
||||||
|
|||||||
+18
-16
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
--- @class Gui
|
--- @class (partial) Gui
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local ExpElement = require("modules/exp_gui/prototype")
|
local ExpElement = require("modules/exp_gui/prototype")
|
||||||
local mod_gui = require("mod-gui")
|
local mod_gui = require("mod-gui")
|
||||||
@@ -29,7 +29,7 @@ Toolbar.on_gui_button_toggled = script.generate_event_name()
|
|||||||
--- @class _ExpElement._prototype
|
--- @class _ExpElement._prototype
|
||||||
--- @field on_button_toggled ExpElement.OnEventAdder<EventData.on_gui_button_toggled>
|
--- @field on_button_toggled ExpElement.OnEventAdder<EventData.on_gui_button_toggled>
|
||||||
|
|
||||||
--- @diagnostic disable-next-line: invisible, inject-field
|
--- @diagnostic disable-next-line: access-invisible, inject-field
|
||||||
function ExpElement._prototype.on_button_toggled(self, handler)
|
function ExpElement._prototype.on_button_toggled(self, handler)
|
||||||
return self:on_event(Toolbar.on_gui_button_toggled, handler)
|
return self:on_event(Toolbar.on_gui_button_toggled, handler)
|
||||||
end
|
end
|
||||||
@@ -85,7 +85,7 @@ function Toolbar.set_button_toggled_state(define, player, state, _from_left)
|
|||||||
button.style = state and toolbar_button_active_style or toolbar_button_default_style
|
button.style = state and toolbar_button_active_style or toolbar_button_default_style
|
||||||
|
|
||||||
-- Make the extra required adjustments
|
-- Make the extra required adjustments
|
||||||
local style = button.style
|
local style = button.style --[[@as LuaStyle]]
|
||||||
style.minimal_width = original_width
|
style.minimal_width = original_width
|
||||||
style.maximal_height = original_height
|
style.maximal_height = original_height
|
||||||
if button.type == "sprite-button" then
|
if button.type == "sprite-button" then
|
||||||
@@ -335,9 +335,10 @@ Below here is the toolbar settings GUI and its associated functions
|
|||||||
--- @param dst LuaGuiElement
|
--- @param dst LuaGuiElement
|
||||||
local function copy_style(src, dst)
|
local function copy_style(src, dst)
|
||||||
dst.style = src.style.name
|
dst.style = src.style.name
|
||||||
dst.style.height = toolbar_button_small
|
local style = dst.style --[[@as LuaStyle]]
|
||||||
dst.style.width = toolbar_button_small
|
style.height = toolbar_button_small
|
||||||
dst.style.padding = -2
|
style.width = toolbar_button_small
|
||||||
|
style.padding = -2
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Reorder the buttons relative to each other, this will update the datastore
|
--- Reorder the buttons relative to each other, this will update the datastore
|
||||||
@@ -350,7 +351,7 @@ local function move_toolbar_button(player, item, offset)
|
|||||||
|
|
||||||
-- Swap the position in the list
|
-- Swap the position in the list
|
||||||
local list = assert(item.parent)
|
local list = assert(item.parent)
|
||||||
local other_item = list.children[new_index]
|
local other_item = assert(list.children[new_index])
|
||||||
list.swap_children(old_index, new_index)
|
list.swap_children(old_index, new_index)
|
||||||
|
|
||||||
-- Swap the position in the top flow, offset by 1 because of settings button
|
-- Swap the position in the top flow, offset by 1 because of settings button
|
||||||
@@ -365,7 +366,7 @@ local function move_toolbar_button(player, item, offset)
|
|||||||
local other_element = Gui.get_left_element(other_left_element, player)
|
local other_element = Gui.get_left_element(other_left_element, player)
|
||||||
local left_index = element.get_index_in_parent()
|
local left_index = element.get_index_in_parent()
|
||||||
local other_index = other_element.get_index_in_parent()
|
local other_index = other_element.get_index_in_parent()
|
||||||
element.parent.swap_children(left_index, other_index)
|
assert(element.parent).swap_children(left_index, other_index)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If we are moving in/out of first/last place we need to update the move buttons
|
-- If we are moving in/out of first/last place we need to update the move buttons
|
||||||
@@ -393,7 +394,7 @@ end
|
|||||||
--- @param player LuaPlayer
|
--- @param player LuaPlayer
|
||||||
--- @param order Gui.ToolbarOrder
|
--- @param order Gui.ToolbarOrder
|
||||||
function Toolbar.set_order(player, order)
|
function Toolbar.set_order(player, order)
|
||||||
local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
|
local list = elements.toolbar_settings.data[player] --[[@as LuaGuiElement]]
|
||||||
local left_flow = Gui.get_left_flow(player)
|
local left_flow = Gui.get_left_flow(player)
|
||||||
local top_flow = Gui.get_top_flow(player)
|
local top_flow = Gui.get_top_flow(player)
|
||||||
|
|
||||||
@@ -466,7 +467,7 @@ end
|
|||||||
function Toolbar.get_state(player)
|
function Toolbar.get_state(player)
|
||||||
-- Get the order of toolbar buttons
|
-- Get the order of toolbar buttons
|
||||||
local order = {}
|
local order = {}
|
||||||
local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
|
local list = elements.toolbar_settings.data[player] --[[@as LuaGuiElement]]
|
||||||
for index, item in pairs(list.children) do
|
for index, item in pairs(list.children) do
|
||||||
order[index] = { name = item.name, favourite = elements.toolbar_list_item.data[item].set_favourite.state }
|
order[index] = { name = item.name, favourite = elements.toolbar_list_item.data[item].set_favourite.state }
|
||||||
end
|
end
|
||||||
@@ -487,7 +488,7 @@ end
|
|||||||
--- @param player LuaPlayer
|
--- @param player LuaPlayer
|
||||||
function Toolbar._create_elements(player)
|
function Toolbar._create_elements(player)
|
||||||
-- Add any missing items to the gui
|
-- Add any missing items to the gui
|
||||||
local toolbar_list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
|
local toolbar_list = elements.toolbar_settings.data[player] --[[@as LuaGuiElement]]
|
||||||
local previous_last_index = #toolbar_list.children_names
|
local previous_last_index = #toolbar_list.children_names
|
||||||
for define in pairs(Gui.top_elements) do
|
for define in pairs(Gui.top_elements) do
|
||||||
if define ~= elements.close_toolbar and toolbar_list[define.name] == nil then
|
if define ~= elements.close_toolbar and toolbar_list[define.name] == nil then
|
||||||
@@ -499,7 +500,8 @@ function Toolbar._create_elements(player)
|
|||||||
-- Reset the state of the previous last child
|
-- Reset the state of the previous last child
|
||||||
local children = toolbar_list.children
|
local children = toolbar_list.children
|
||||||
if previous_last_index > 0 then
|
if previous_last_index > 0 then
|
||||||
elements.toolbar_list_item.data[children[previous_last_index]].move_item_down.enabled = true
|
local previous_last = assert(children[previous_last_index])
|
||||||
|
elements.toolbar_list_item.data[previous_last].move_item_down.enabled = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Set the state of the move buttons for the first and last element
|
-- Set the state of the move buttons for the first and last element
|
||||||
@@ -513,7 +515,7 @@ end
|
|||||||
--- @param player LuaPlayer
|
--- @param player LuaPlayer
|
||||||
function Toolbar._ensure_consistency(player)
|
function Toolbar._ensure_consistency(player)
|
||||||
-- Update the toolbar buttons
|
-- Update the toolbar buttons
|
||||||
local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
|
local list = elements.toolbar_settings.data[player] --[[@as LuaGuiElement]]
|
||||||
for _, button in ipairs(toolbar_buttons) do
|
for _, button in ipairs(toolbar_buttons) do
|
||||||
-- Update the visible state based on if the player is allowed the button
|
-- Update the visible state based on if the player is allowed the button
|
||||||
local element = Gui.get_top_element(button, player)
|
local element = Gui.get_top_element(button, player)
|
||||||
@@ -619,7 +621,7 @@ elements.move_item_up = Gui.define("move_item_up")
|
|||||||
size = toolbar_button_small,
|
size = toolbar_button_small,
|
||||||
})
|
})
|
||||||
:on_click(function(def, player, element)
|
:on_click(function(def, player, element)
|
||||||
local item = assert(element.parent.parent)
|
local item = assert(assert(element.parent).parent)
|
||||||
move_toolbar_button(player, item, -1)
|
move_toolbar_button(player, item, -1)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -634,7 +636,7 @@ elements.move_item_down = Gui.define("move_item_down")
|
|||||||
size = toolbar_button_small,
|
size = toolbar_button_small,
|
||||||
})
|
})
|
||||||
:on_click(function(def, player, element)
|
:on_click(function(def, player, element)
|
||||||
local item = assert(element.parent.parent)
|
local item = assert(assert(element.parent).parent)
|
||||||
move_toolbar_button(player, item, 1)
|
move_toolbar_button(player, item, 1)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -658,7 +660,7 @@ elements.set_favourite = Gui.define("set_favourite")
|
|||||||
width = 180,
|
width = 180,
|
||||||
}
|
}
|
||||||
:on_checked_state_changed(function(def, player, element)
|
:on_checked_state_changed(function(def, player, element)
|
||||||
local define = ExpElement.get(element.tags.element_name --[[ @as string ]])
|
local define = ExpElement.get(element.tags.element_name --[[@as string]])
|
||||||
local top_element = Gui.get_top_element(define, player)
|
local top_element = Gui.get_top_element(define, player)
|
||||||
local had_visible = Toolbar.has_visible_buttons(player)
|
local had_visible = Toolbar.has_visible_buttons(player)
|
||||||
top_element.visible = element.state
|
top_element.visible = element.state
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@expcluster/lib_gui",
|
"name": "@expcluster/lib_gui",
|
||||||
"version": "0.1.0",
|
"version": "7.0.1",
|
||||||
"description": "Clusterio plugin providing a Lua GUI definition library.",
|
"description": "Clusterio plugin providing a Lua GUI definition library.",
|
||||||
"author": "Cooldude2606 <https://github.com/Cooldude2606>",
|
"author": "Cooldude2606 <https://github.com/Cooldude2606>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "explosivegaming/ExpCluster",
|
"repository": "explosivegaming/ExpCluster",
|
||||||
|
"type": "module",
|
||||||
"main": "dist/node/index.js",
|
"main": "dist/node/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "tsc --build && webpack-cli --env production"
|
"prepare": "tsc --build && webpack-cli --env production"
|
||||||
@@ -13,10 +14,10 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@clusterio/lib": "catalog:"
|
"@clusterio/lib": "workspace:^"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@clusterio/lib": "catalog:",
|
"@clusterio/lib": "workspace:^",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"webpack": "catalog:",
|
"webpack": "catalog:",
|
||||||
|
|||||||
+1
-1
@@ -4,5 +4,5 @@ export const plugin: lib.PluginDeclaration = {
|
|||||||
name: "exp_legacy",
|
name: "exp_legacy",
|
||||||
title: "exp_legacy",
|
title: "exp_legacy",
|
||||||
description: "Example Description. Plugin. Change me in index.ts",
|
description: "Example Description. Plugin. Change me in index.ts",
|
||||||
instanceEntrypoint: "./dist/node/instance",
|
instanceEntrypoint: "./dist/node/instance.js",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,21 +16,12 @@ return {
|
|||||||
"modules.data.quickbar",
|
"modules.data.quickbar",
|
||||||
"modules.data.alt-view",
|
"modules.data.alt-view",
|
||||||
"modules.data.tag",
|
"modules.data.tag",
|
||||||
-- 'modules.data.bonus',
|
|
||||||
"modules.data.personal-logistic",
|
|
||||||
"modules.data.language",
|
"modules.data.language",
|
||||||
--"modules.data.toolbar",
|
--"modules.data.toolbar",
|
||||||
|
|
||||||
--- GUI
|
--- GUI
|
||||||
"modules.gui.readme",
|
|
||||||
"modules.gui.rocket-info",
|
|
||||||
"modules.gui.warp-list",
|
"modules.gui.warp-list",
|
||||||
"modules.gui.player-list",
|
|
||||||
"modules.gui.vlayer",
|
"modules.gui.vlayer",
|
||||||
"modules.gui._role_updates",
|
|
||||||
|
|
||||||
"modules.graftorio.require", -- graftorio
|
"modules.graftorio.require", -- graftorio
|
||||||
--- Config Files
|
|
||||||
"config.expcore.permission_groups", -- loads some predefined permission groups
|
|
||||||
"config.expcore.roles", -- loads some predefined roles
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
local Roles = require("modules/exp_roles")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
admin_as_active = true, --- @setting admin_as_active When true admins will be treated as active regardless of afk time
|
admin_as_active = true, --- @setting admin_as_active When true admins will be treated as active regardless of afk time
|
||||||
@@ -8,6 +8,7 @@ return {
|
|||||||
trust_time = 3600 * 60 * 10, --- @setting trust_time The time in ticks that a player must be online for to count as trusted
|
trust_time = 3600 * 60 * 10, --- @setting trust_time The time in ticks that a player must be online for to count as trusted
|
||||||
update_time = 3600 * 30, --- @setting update_time How often in ticks the script checks for active players
|
update_time = 3600 * 30, --- @setting update_time How often in ticks the script checks for active players
|
||||||
custom_active_check = function(player)
|
custom_active_check = function(player)
|
||||||
return Roles.get_player_highest_role(player).index <= Roles.get_role_from_any("Veteran").index
|
local veteran = Roles.get_role_by_name("Veteran")
|
||||||
|
return veteran ~= nil and not Roles.get_player_highest_role(player):is_lower_than(veteran)
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,21 +3,17 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
--[[
|
--[[
|
||||||
TODO
|
|
||||||
force bonus
|
|
||||||
quick health regeneration
|
|
||||||
|
|
||||||
Base point is equal to the amount of standard value in each parameter.
|
Base point is equal to the amount of standard value in each parameter.
|
||||||
|
|
||||||
CMMS CRS CCS CISB CHB CRDB PBR
|
CMMS CRS CCS CISB CHB CRDB PBR
|
||||||
STD 30 90 32 20 16 12 60
|
STD 20 60 24 10 12 8 40
|
||||||
= 260
|
= 174
|
||||||
|
|
||||||
MAX 60 180 64 40 32 24 120
|
MAX 40 120 48 20 24 16 80
|
||||||
= 480
|
= 348
|
||||||
]]
|
]]
|
||||||
points = {
|
points = {
|
||||||
base = 260,
|
base = 174,
|
||||||
increase_percentage_per_role_level = 0.03,
|
increase_percentage_per_role_level = 0.03,
|
||||||
role_name = "Member",
|
role_name = "Member",
|
||||||
},
|
},
|
||||||
@@ -27,8 +23,7 @@ return {
|
|||||||
name = "character_mining_speed_modifier",
|
name = "character_mining_speed_modifier",
|
||||||
scale = 1,
|
scale = 1,
|
||||||
cost = 10,
|
cost = 10,
|
||||||
max_value = 6,
|
max_value = 4,
|
||||||
initial_value = 3,
|
|
||||||
value_step = 0.5,
|
value_step = 0.5,
|
||||||
is_percentage = true,
|
is_percentage = true,
|
||||||
},
|
},
|
||||||
@@ -36,8 +31,7 @@ return {
|
|||||||
name = "character_running_speed_modifier",
|
name = "character_running_speed_modifier",
|
||||||
scale = 1,
|
scale = 1,
|
||||||
cost = 60,
|
cost = 60,
|
||||||
max_value = 3,
|
max_value = 2,
|
||||||
initial_value = 1.5,
|
|
||||||
value_step = 0.25,
|
value_step = 0.25,
|
||||||
is_percentage = true,
|
is_percentage = true,
|
||||||
},
|
},
|
||||||
@@ -45,33 +39,29 @@ return {
|
|||||||
name = "character_crafting_speed_modifier",
|
name = "character_crafting_speed_modifier",
|
||||||
scale = 1,
|
scale = 1,
|
||||||
cost = 4,
|
cost = 4,
|
||||||
max_value = 16,
|
max_value = 12,
|
||||||
initial_value = 8,
|
|
||||||
value_step = 1,
|
value_step = 1,
|
||||||
is_percentage = true,
|
is_percentage = true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "character_inventory_slots_bonus",
|
name = "character_inventory_slots_bonus",
|
||||||
cost = 2,
|
|
||||||
scale = 10,
|
scale = 10,
|
||||||
max_value = 200,
|
cost = 2,
|
||||||
initial_value = 100,
|
max_value = 100,
|
||||||
value_step = 10,
|
value_step = 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "character_health_bonus",
|
name = "character_health_bonus",
|
||||||
scale = 50,
|
scale = 50,
|
||||||
cost = 4,
|
cost = 4,
|
||||||
max_value = 400,
|
max_value = 300,
|
||||||
initial_value = 200,
|
|
||||||
value_step = 50,
|
value_step = 50,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "character_reach_distance_bonus",
|
name = "character_reach_distance_bonus",
|
||||||
cost = 1,
|
|
||||||
scale = 1,
|
scale = 1,
|
||||||
max_value = 24,
|
cost = 1,
|
||||||
initial_value = 12,
|
max_value = 16,
|
||||||
value_step = 2,
|
value_step = 2,
|
||||||
combined_bonus = {
|
combined_bonus = {
|
||||||
"character_resource_reach_distance_bonus",
|
"character_resource_reach_distance_bonus",
|
||||||
@@ -80,104 +70,15 @@ return {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "personal_battery_recharge",
|
name = "personal_battery_recharge",
|
||||||
initial_value = 6,
|
|
||||||
max_value = 12,
|
|
||||||
value_step = 1,
|
|
||||||
scale = 4,
|
scale = 4,
|
||||||
cost = 40,
|
cost = 40,
|
||||||
|
max_value = 8,
|
||||||
|
value_step = 1,
|
||||||
is_special = true,
|
is_special = true,
|
||||||
},
|
},
|
||||||
--[[
|
|
||||||
['character_item_pickup_distance_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 20,
|
|
||||||
value_step = 1,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['character_loot_pickup_distance_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 20,
|
|
||||||
value_step = 1,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['character_item_drop_distance_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 20,
|
|
||||||
value_step = 1,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
}
|
|
||||||
]]
|
|
||||||
},
|
},
|
||||||
force_bonus = {
|
force_bonus = {
|
||||||
--[[
|
|
||||||
['character_mining_speed_modifier'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 6,
|
|
||||||
value_step = 0.5,
|
|
||||||
scale = 1,
|
|
||||||
cost = 10,
|
|
||||||
is_percentage = true
|
|
||||||
},
|
|
||||||
['character_running_speed_modifier'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 3,
|
|
||||||
value_step = 0.25,
|
|
||||||
scale = 1,
|
|
||||||
cost = 40,
|
|
||||||
is_percentage = true
|
|
||||||
},
|
|
||||||
['character_crafting_speed_modifier'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 16,
|
|
||||||
value_step = 1,
|
|
||||||
scale = 1,
|
|
||||||
cost = 4,
|
|
||||||
is_percentage = true
|
|
||||||
},
|
|
||||||
['character_inventory_slots_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 200,
|
|
||||||
value_step = 10,
|
|
||||||
scale = 100,
|
|
||||||
cost = 2,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['character_health_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 400,
|
|
||||||
value_step = 50,
|
|
||||||
cost = 4,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['character_reach_distance_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 24,
|
|
||||||
value_step = 2,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false,
|
|
||||||
combined_bonus = {
|
|
||||||
'character_resource_reach_distance_bonus',
|
|
||||||
'character_build_distance_bonus'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
['worker_robots_speed_modifier'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 0,
|
|
||||||
value_step = 0,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
]]
|
|
||||||
["worker_robots_battery_modifier"] = {
|
["worker_robots_battery_modifier"] = {
|
||||||
initial_value = 1,
|
|
||||||
max_value = 1,
|
max_value = 1,
|
||||||
value_step = 1,
|
value_step = 1,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
@@ -185,7 +86,6 @@ return {
|
|||||||
is_percentage = false,
|
is_percentage = false,
|
||||||
},
|
},
|
||||||
["worker_robots_storage_bonus"] = {
|
["worker_robots_storage_bonus"] = {
|
||||||
initial_value = 1,
|
|
||||||
max_value = 1,
|
max_value = 1,
|
||||||
value_step = 1,
|
value_step = 1,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
@@ -193,114 +93,13 @@ return {
|
|||||||
is_percentage = false,
|
is_percentage = false,
|
||||||
},
|
},
|
||||||
["following_robots_lifetime_modifier"] = {
|
["following_robots_lifetime_modifier"] = {
|
||||||
initial_value = 1,
|
|
||||||
max_value = 1,
|
max_value = 1,
|
||||||
value_step = 1,
|
value_step = 1,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
cost = 1,
|
cost = 1,
|
||||||
is_percentage = false,
|
is_percentage = false,
|
||||||
},
|
},
|
||||||
--[[
|
|
||||||
['character_item_pickup_distance_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 20,
|
|
||||||
value_step = 1,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['character_loot_pickup_distance_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 20,
|
|
||||||
value_step = 1,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['character_item_drop_distance_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 20,
|
|
||||||
value_step = 1,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['character_trash_slot_count'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 0,
|
|
||||||
value_step = 0,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['mining_drill_productivity_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 0,
|
|
||||||
value_step = 0,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['train_braking_force_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 0,
|
|
||||||
value_step = 0,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['laboratory_speed_modifier'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 0,
|
|
||||||
value_step = 0,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['laboratory_productivity_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 0,
|
|
||||||
value_step = 0,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['inserter_stack_size_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 0,
|
|
||||||
value_step = 0,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['stack_inserter_capacity_bonus'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 0,
|
|
||||||
value_step = 0,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
},
|
|
||||||
['artillery_range_modifier'] = {
|
|
||||||
initial_value = 0,
|
|
||||||
max_value = 0,
|
|
||||||
value_step = 0,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
}
|
|
||||||
]]
|
|
||||||
},
|
},
|
||||||
surface_bonus = {
|
surface_bonus = {
|
||||||
--[[
|
|
||||||
['solar_power_multiplier'] = {
|
|
||||||
initial_value = 1,
|
|
||||||
max_value = 1000,
|
|
||||||
value_step = 1,
|
|
||||||
scale = 1,
|
|
||||||
cost = 1,
|
|
||||||
is_percentage = false
|
|
||||||
}
|
|
||||||
]]
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
local ExpUtil = require("modules/exp_util")
|
local ExpUtil = require("modules/exp_util")
|
||||||
local Async = require("modules/exp_util/async")
|
local Async = require("modules/exp_util/async")
|
||||||
|
local config_server_detail = require("modules.exp_legacy.config.server_detail") --- @dep config.server_detail
|
||||||
|
|
||||||
local floor = math.floor
|
local floor = math.floor
|
||||||
local random = math.random
|
local random = math.random
|
||||||
@@ -24,14 +25,15 @@ local afk_time_units = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
--- @type table<string, LocalisedString | fun(player: LuaPlayer, is_command: boolean): LocalisedString>
|
||||||
messages = { --- @setting messages will trigger when ever the word is said
|
messages = { --- @setting messages will trigger when ever the word is said
|
||||||
["discord"] = { "info.discord" },
|
["discord"] = { "info.discord", config_server_detail["discord"] },
|
||||||
["expgaming"] = { "info.website" },
|
["expgaming"] = { "info.website", config_server_detail["website"] },
|
||||||
["website"] = { "info.website" },
|
["website"] = { "info.website", config_server_detail["website"] },
|
||||||
["status"] = { "info.status" },
|
["status"] = { "info.status", config_server_detail["status"] },
|
||||||
["github"] = { "info.github" },
|
["github"] = { "info.github", config_server_detail["github"] },
|
||||||
["patreon"] = { "info.patreon" },
|
["patreon"] = { "info.patreon", config_server_detail["patreon"] },
|
||||||
["donate"] = { "info.patreon" },
|
["donate"] = { "info.patreon", config_server_detail["patreon"] },
|
||||||
["command"] = { "info.custom-commands" },
|
["command"] = { "info.custom-commands" },
|
||||||
["commands"] = { "info.custom-commands" },
|
["commands"] = { "info.custom-commands" },
|
||||||
["softmod"] = { "info.softmod" },
|
["softmod"] = { "info.softmod" },
|
||||||
@@ -64,8 +66,9 @@ return {
|
|||||||
},
|
},
|
||||||
allow_command_prefix_for_messages = true, --- @setting allow_command_prefix_for_messages when true any message trigger will print to all player when prefixed
|
allow_command_prefix_for_messages = true, --- @setting allow_command_prefix_for_messages when true any message trigger will print to all player when prefixed
|
||||||
command_admin_only = false, --- @setting command_admin_only when true will only allow chat commands for admins
|
command_admin_only = false, --- @setting command_admin_only when true will only allow chat commands for admins
|
||||||
command_permission = "command/chat-commands", --- @setting command_permission the permission used to allow command prefixes
|
command_permission = "exp_scenario.chat.commands", --- @setting command_permission the permission used to allow command prefixes
|
||||||
command_prefix = "!", --- @setting command_prefix prefix used for commands below and to print to all players (if enabled above)
|
command_prefix = "!", --- @setting command_prefix prefix used for commands below and to print to all players (if enabled above)
|
||||||
|
--- @type table<string, LocalisedString | fun(player: LuaPlayer, is_command: boolean): LocalisedString>
|
||||||
commands = { --- @setting commands will trigger only when command prefix is given
|
commands = { --- @setting commands will trigger only when command prefix is given
|
||||||
["dev"] = { "exp_chat-auto-reply.reply-dev" },
|
["dev"] = { "exp_chat-auto-reply.reply-dev" },
|
||||||
["magic"] = { "exp_chat-auto-reply.reply-magic" },
|
["magic"] = { "exp_chat-auto-reply.reply-magic" },
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
--- Config file for the compliatrons including where they spawn and what messages they show
|
--- Config file for the compliatrons including where they spawn and what messages they show
|
||||||
-- @config Compilatron
|
-- @config Compilatron
|
||||||
|
local config_server_detail = require("modules.exp_legacy.config.server_detail") --- @dep config.server_detail
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message_cycle = 60 * 15, --- @setting message_cycle 15 seconds default, how often (in ticks) the messages will cycle
|
message_cycle = 60 * 15, --- @setting message_cycle 15 seconds default, how often (in ticks) the messages will cycle
|
||||||
@@ -9,16 +10,16 @@ return {
|
|||||||
spawn_surface = "nauvis",
|
spawn_surface = "nauvis",
|
||||||
entity_name = "small-biter",
|
entity_name = "small-biter",
|
||||||
messages = {
|
messages = {
|
||||||
{ "info.website" },
|
{ "info.website", config_server_detail["website"] },
|
||||||
{ "info.read-readme" },
|
{ "info.read-readme" },
|
||||||
{ "info.discord" },
|
{ "info.discord", config_server_detail["discord"] },
|
||||||
{ "info.softmod" },
|
{ "info.softmod" },
|
||||||
{ "info.redmew" },
|
{ "info.redmew" },
|
||||||
{ "info.custom-commands" },
|
{ "info.custom-commands" },
|
||||||
{ "info.status" },
|
{ "info.status", config_server_detail["status"] },
|
||||||
{ "info.lhd" },
|
{ "info.lhd" },
|
||||||
{ "info.github" },
|
{ "info.github", config_server_detail["github"] },
|
||||||
{ "info.patreon" },
|
{ "info.patreon", config_server_detail["patreon"] },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ return {
|
|||||||
show_playtime = true,
|
show_playtime = true,
|
||||||
entity_protection = true,
|
entity_protection = true,
|
||||||
player_reports = true,
|
player_reports = true,
|
||||||
player_warnings = true,
|
|
||||||
player_bans = true,
|
player_bans = true,
|
||||||
player_mutes = true,
|
player_mutes = true,
|
||||||
player_kicks = true,
|
player_kicks = true,
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
--- Use this file to add new permission groups to the game;
|
|
||||||
-- start with Permission_Groups.new_group('name');
|
|
||||||
-- then use either :allow_all() or :disallow_all() to set the default for non specified actions;
|
|
||||||
-- then use :allow{} and :disallow{} to specify certain actions to allow/disallow
|
|
||||||
-- @config Permission-Groups
|
|
||||||
|
|
||||||
-- local Event = require("modules/exp_legacy/utils/event") -- @dep utils.event
|
|
||||||
local Groups = require("modules.exp_legacy.expcore.permission_groups") --- @dep expcore.permission_groups
|
|
||||||
|
|
||||||
Groups.new_group("Admin")
|
|
||||||
:allow_all()
|
|
||||||
:disallow{
|
|
||||||
"add_permission_group", -- admin
|
|
||||||
"delete_permission_group",
|
|
||||||
"edit_permission_group",
|
|
||||||
"import_permissions_string",
|
|
||||||
"map_editor_action",
|
|
||||||
"toggle_map_editor",
|
|
||||||
"change_multiplayer_config",
|
|
||||||
"set_heat_interface_mode",
|
|
||||||
"set_heat_interface_temperature",
|
|
||||||
"set_infinity_container_filter_item",
|
|
||||||
"set_infinity_container_remove_unfiltered_items",
|
|
||||||
"set_infinity_pipe_filter",
|
|
||||||
}
|
|
||||||
|
|
||||||
Groups.new_group("Trusted")
|
|
||||||
:allow_all()
|
|
||||||
:disallow{
|
|
||||||
"add_permission_group", -- admin
|
|
||||||
"delete_permission_group",
|
|
||||||
"edit_permission_group",
|
|
||||||
"import_permissions_string",
|
|
||||||
"map_editor_action",
|
|
||||||
"toggle_map_editor",
|
|
||||||
"change_multiplayer_config",
|
|
||||||
"set_heat_interface_mode",
|
|
||||||
"set_heat_interface_temperature",
|
|
||||||
"set_infinity_container_filter_item",
|
|
||||||
"set_infinity_container_remove_unfiltered_items",
|
|
||||||
"set_infinity_pipe_filter",
|
|
||||||
"admin_action", -- trusted
|
|
||||||
}
|
|
||||||
|
|
||||||
Groups.new_group("Standard")
|
|
||||||
:allow_all()
|
|
||||||
:disallow{
|
|
||||||
"add_permission_group", -- admin
|
|
||||||
"delete_permission_group",
|
|
||||||
"edit_permission_group",
|
|
||||||
"import_permissions_string",
|
|
||||||
"map_editor_action",
|
|
||||||
"toggle_map_editor",
|
|
||||||
"change_multiplayer_config",
|
|
||||||
"set_heat_interface_mode",
|
|
||||||
"set_heat_interface_temperature",
|
|
||||||
"set_infinity_container_filter_item",
|
|
||||||
"set_infinity_container_remove_unfiltered_items",
|
|
||||||
"set_infinity_pipe_filter",
|
|
||||||
"admin_action", -- trusted
|
|
||||||
"change_programmable_speaker_alert_parameters", -- standard
|
|
||||||
"drop_item",
|
|
||||||
"open_new_platform_button_from_rocket_silo",
|
|
||||||
"set_rocket_silo_send_to_orbit_automated_mode",
|
|
||||||
}
|
|
||||||
|
|
||||||
Groups.new_group("Guest")
|
|
||||||
:allow_all()
|
|
||||||
:disallow{
|
|
||||||
"add_permission_group", -- admin
|
|
||||||
"delete_permission_group",
|
|
||||||
"edit_permission_group",
|
|
||||||
"import_permissions_string",
|
|
||||||
"map_editor_action",
|
|
||||||
"toggle_map_editor",
|
|
||||||
"change_multiplayer_config",
|
|
||||||
"set_heat_interface_mode",
|
|
||||||
"set_heat_interface_temperature",
|
|
||||||
"set_infinity_container_filter_item",
|
|
||||||
"set_infinity_container_remove_unfiltered_items",
|
|
||||||
"set_infinity_pipe_filter",
|
|
||||||
"admin_action", -- trusted
|
|
||||||
"change_programmable_speaker_alert_parameters", -- standard
|
|
||||||
"drop_item",
|
|
||||||
"open_new_platform_button_from_rocket_silo",
|
|
||||||
"set_rocket_silo_send_to_orbit_automated_mode",
|
|
||||||
"change_programmable_speaker_parameters", -- guest
|
|
||||||
"change_train_stop_station",
|
|
||||||
-- 'deconstruct',
|
|
||||||
"remove_cables",
|
|
||||||
"remove_train_station",
|
|
||||||
"reset_assembling_machine",
|
|
||||||
"rotate_entity",
|
|
||||||
-- 'use_artillery_remote', -- not in 2.0
|
|
||||||
"launch_rocket",
|
|
||||||
"cancel_research",
|
|
||||||
-- 'activate_cut', -- not in 2.0
|
|
||||||
"flush_opened_entity_fluid",
|
|
||||||
"flush_opened_entity_specific_fluid",
|
|
||||||
}
|
|
||||||
|
|
||||||
Groups.new_group("Restricted")
|
|
||||||
:disallow_all()
|
|
||||||
:allow("write_to_console")
|
|
||||||
|
|
||||||
--[[ These events are used until a role system is added to make it easier for our admins
|
|
||||||
|
|
||||||
local trusted_time = 60*60*60*10 -- 10 hour
|
|
||||||
local standard_time = 60*60*60*3 -- 3 hour
|
|
||||||
local function assign_group(player)
|
|
||||||
local current_group_name = player.permission_group and player.permission_group.name or 'None'
|
|
||||||
if player.admin then
|
|
||||||
Permission_Groups.set_player_group(player,'Admin')
|
|
||||||
elseif player.online_time > trusted_time or current_group_name == 'Trusted' then
|
|
||||||
Permission_Groups.set_player_group(player,'Trusted')
|
|
||||||
elseif player.online_time > standard_time or current_group_name == 'Standard' then
|
|
||||||
Permission_Groups.set_player_group(player,'Standard')
|
|
||||||
else
|
|
||||||
Permission_Groups.set_player_group(player,'Guest')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Event.add(defines.events.on_player_joined_game,function(event)
|
|
||||||
local player = game.players[event.player_index]
|
|
||||||
assign_group(player)
|
|
||||||
end)
|
|
||||||
|
|
||||||
Event.add(defines.events.on_player_promoted,function(event)
|
|
||||||
local player = game.players[event.player_index]
|
|
||||||
assign_group(player)
|
|
||||||
end)
|
|
||||||
|
|
||||||
Event.add(defines.events.on_player_demoted,function(event)
|
|
||||||
local player = game.players[event.player_index]
|
|
||||||
assign_group(player)
|
|
||||||
end)
|
|
||||||
|
|
||||||
local check_interval = 60*60*15 -- 15 minutes
|
|
||||||
Event.on_nth_tick(check_interval,function(event)
|
|
||||||
for _,player in pairs(game.connected_players) do
|
|
||||||
assign_group(player)
|
|
||||||
end
|
|
||||||
end)]]
|
|
||||||
@@ -1,368 +0,0 @@
|
|||||||
--- This is the main config file for the role system; file includes defines for roles and role flags and default values
|
|
||||||
-- @config Roles
|
|
||||||
|
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
|
|
||||||
local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data
|
|
||||||
local Statistics = PlayerData.Statistics
|
|
||||||
|
|
||||||
--- Role flags that will run when a player changes roles
|
|
||||||
Roles.define_flag_trigger("is_admin", function(player, state)
|
|
||||||
player.admin = state
|
|
||||||
end)
|
|
||||||
Roles.define_flag_trigger("is_spectator", function(player, state)
|
|
||||||
player.spectator = state
|
|
||||||
end)
|
|
||||||
Roles.define_flag_trigger("is_jail", function(player, state)
|
|
||||||
if player.character then
|
|
||||||
player.character.active = not state
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
--- Admin Roles
|
|
||||||
Roles.new_role("System", "SYS")
|
|
||||||
:set_permission_group("Default", true)
|
|
||||||
:set_flag("is_admin")
|
|
||||||
:set_flag("is_system")
|
|
||||||
:set_flag("is_spectator")
|
|
||||||
:set_flag("report-immune")
|
|
||||||
:set_flag("instant-respawn")
|
|
||||||
:set_allow_all()
|
|
||||||
|
|
||||||
Roles.new_role("Senior Administrator", "SAdmin")
|
|
||||||
:set_permission_group("Admin")
|
|
||||||
:set_custom_color{ r = 233, g = 63, b = 233 }
|
|
||||||
:set_flag("is_admin")
|
|
||||||
:set_flag("is_system")
|
|
||||||
:set_flag("is_spectator")
|
|
||||||
:set_flag("report-immune")
|
|
||||||
:set_flag("instant-respawn")
|
|
||||||
:set_parent("Administrator")
|
|
||||||
:allow{
|
|
||||||
"command/_rcon",
|
|
||||||
"command/debug",
|
|
||||||
"command/set-cheat-mode",
|
|
||||||
"command/research-all",
|
|
||||||
}
|
|
||||||
|
|
||||||
Roles.new_role("Administrator", "Admin")
|
|
||||||
:set_permission_group("Admin")
|
|
||||||
:set_custom_color{ r = 233, g = 63, b = 233 }
|
|
||||||
:set_flag("is_admin")
|
|
||||||
:set_flag("is_spectator")
|
|
||||||
:set_flag("report-immune")
|
|
||||||
:set_flag("instant-respawn")
|
|
||||||
:set_parent("Moderator")
|
|
||||||
:allow{
|
|
||||||
"gui/warp-list/bypass-proximity",
|
|
||||||
"gui/warp-list/bypass-cooldown",
|
|
||||||
"command/connect-all",
|
|
||||||
}
|
|
||||||
|
|
||||||
Roles.new_role("Moderator", "Mod")
|
|
||||||
:set_permission_group("Admin")
|
|
||||||
:set_custom_color{ r = 0, g = 170, b = 0 }
|
|
||||||
:set_flag("is_admin")
|
|
||||||
:set_flag("is_spectator")
|
|
||||||
:set_flag("report-immune")
|
|
||||||
:set_flag("instant-respawn")
|
|
||||||
:set_parent("Trainee")
|
|
||||||
:allow{
|
|
||||||
"command/assign-role",
|
|
||||||
"command/unassign-role",
|
|
||||||
"command/repair",
|
|
||||||
"command/kill/always",
|
|
||||||
"command/clear-tag/always",
|
|
||||||
"command/spawn/always",
|
|
||||||
"command/clear-reports",
|
|
||||||
"command/clear-warnings",
|
|
||||||
"command/clear-script-warnings",
|
|
||||||
"command/clear-last-warnings",
|
|
||||||
"command/clear-inventory",
|
|
||||||
"command/kill-enemies",
|
|
||||||
"command/remove-enemies",
|
|
||||||
--'command/bonus',
|
|
||||||
"command/home",
|
|
||||||
"command/set-home",
|
|
||||||
"command/get-home",
|
|
||||||
"command/return",
|
|
||||||
"command/connect-player",
|
|
||||||
"command/set-bot-queue",
|
|
||||||
"command/set-game-speed",
|
|
||||||
"command/set-friendly-fire",
|
|
||||||
"command/set-always-day",
|
|
||||||
"command/set-pollution-enabled",
|
|
||||||
"command/clear-pollution",
|
|
||||||
"gui/rocket-info/toggle-active",
|
|
||||||
"gui/rocket-info/remote_launch",
|
|
||||||
"gui/bonus",
|
|
||||||
"fast-tree-decon",
|
|
||||||
}
|
|
||||||
|
|
||||||
Roles.new_role("Trainee", "TrMod")
|
|
||||||
:set_permission_group("Admin")
|
|
||||||
:set_custom_color{ r = 0, g = 170, b = 0 }
|
|
||||||
:set_flag("is_admin")
|
|
||||||
:set_flag("is_spectator")
|
|
||||||
:set_flag("report-immune")
|
|
||||||
:set_parent("Veteran")
|
|
||||||
:allow{
|
|
||||||
"command/admin-chat",
|
|
||||||
"command/goto",
|
|
||||||
"command/teleport",
|
|
||||||
"command/bring",
|
|
||||||
"command/create-warning",
|
|
||||||
"command/get-warnings",
|
|
||||||
"command/get-reports",
|
|
||||||
"command/protect-entity",
|
|
||||||
"command/protect-area",
|
|
||||||
"command/protect-tag",
|
|
||||||
"command/jail",
|
|
||||||
"command/unjail",
|
|
||||||
"command/kick",
|
|
||||||
"command/ban",
|
|
||||||
"command/spectate",
|
|
||||||
"command/follow",
|
|
||||||
"command/search",
|
|
||||||
"command/search-online",
|
|
||||||
"command/search-amount",
|
|
||||||
"command/search-recent",
|
|
||||||
"command/clear-blueprints",
|
|
||||||
"gui/playerdata",
|
|
||||||
}
|
|
||||||
|
|
||||||
--- Trusted Roles
|
|
||||||
Roles.new_role("Board Member", "Board")
|
|
||||||
:set_permission_group("Trusted")
|
|
||||||
:set_custom_color{ r = 247, g = 246, b = 54 }
|
|
||||||
:set_flag("is_spectator")
|
|
||||||
:set_flag("report-immune")
|
|
||||||
:set_flag("instant-respawn")
|
|
||||||
:set_parent("Sponsor")
|
|
||||||
:allow{
|
|
||||||
"command/goto",
|
|
||||||
"command/repair",
|
|
||||||
"command/spectate",
|
|
||||||
"command/follow",
|
|
||||||
"gui/playerdata",
|
|
||||||
}
|
|
||||||
|
|
||||||
Roles.new_role("Senior Backer", "Backer")
|
|
||||||
:set_permission_group("Trusted")
|
|
||||||
:set_custom_color{ r = 238, g = 172, b = 44 }
|
|
||||||
:set_flag("is_spectator")
|
|
||||||
:set_flag("report-immune")
|
|
||||||
:set_flag("instant-respawn")
|
|
||||||
:set_parent("Sponsor")
|
|
||||||
:allow{
|
|
||||||
}
|
|
||||||
|
|
||||||
Roles.new_role("Sponsor", "Spon")
|
|
||||||
:set_permission_group("Trusted")
|
|
||||||
:set_custom_color{ r = 238, g = 172, b = 44 }
|
|
||||||
:set_flag("is_spectator")
|
|
||||||
:set_flag("report-immune")
|
|
||||||
:set_flag("instant-respawn")
|
|
||||||
:set_parent("Supporter")
|
|
||||||
:allow{
|
|
||||||
"gui/rocket-info/toggle-active",
|
|
||||||
"gui/rocket-info/remote_launch",
|
|
||||||
"gui/bonus",
|
|
||||||
--"command/bonus",
|
|
||||||
"command/home",
|
|
||||||
"command/set-home",
|
|
||||||
"command/get-home",
|
|
||||||
"command/return",
|
|
||||||
"fast-tree-decon",
|
|
||||||
}
|
|
||||||
|
|
||||||
Roles.new_role("Supporter", "Sup")
|
|
||||||
:set_permission_group("Trusted")
|
|
||||||
:set_custom_color{ r = 230, g = 99, b = 34 }
|
|
||||||
:set_flag("is_spectator")
|
|
||||||
:set_parent("Veteran")
|
|
||||||
:allow{
|
|
||||||
"command/tag-color",
|
|
||||||
"command/jail",
|
|
||||||
"command/unjail",
|
|
||||||
"command/set-join-message",
|
|
||||||
"command/remove-join-message",
|
|
||||||
}
|
|
||||||
|
|
||||||
Roles.new_role("Partner", "Part")
|
|
||||||
:set_permission_group("Trusted")
|
|
||||||
:set_custom_color{ r = 140, g = 120, b = 200 }
|
|
||||||
:set_flag("is_spectator")
|
|
||||||
:set_parent("Veteran")
|
|
||||||
:allow{
|
|
||||||
"command/jail",
|
|
||||||
"command/unjail",
|
|
||||||
}
|
|
||||||
|
|
||||||
local hours10, hours250 = 10 * 216000, 250 * 60
|
|
||||||
Roles.new_role("Veteran", "Vet")
|
|
||||||
:set_permission_group("Trusted")
|
|
||||||
:set_custom_color{ r = 140, g = 120, b = 200 }
|
|
||||||
:set_parent("Member")
|
|
||||||
:allow{
|
|
||||||
"command/chat-commands",
|
|
||||||
"command/clear-ground-items",
|
|
||||||
"command/clear-blueprints-radius",
|
|
||||||
"command/set-trains-to-automatic",
|
|
||||||
"command/set-auto-research",
|
|
||||||
}
|
|
||||||
:set_auto_assign_condition(function(player)
|
|
||||||
if player.online_time >= hours10 then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
local stats = Statistics:get(player, {})
|
|
||||||
local playtime, afk_time, map_count = stats.Playtime or 0, stats.AfkTime or 0, stats.MapsPlayed or 0
|
|
||||||
return playtime - afk_time >= hours250 and map_count >= 25
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
--- Standard User Roles
|
|
||||||
Roles.new_role("Member", "Mem")
|
|
||||||
:set_permission_group("Standard")
|
|
||||||
:set_custom_color{ r = 24, g = 172, b = 188 }
|
|
||||||
:set_flag("deconlog-bypass")
|
|
||||||
:set_parent("Regular")
|
|
||||||
:allow{
|
|
||||||
"gui/task-list/add",
|
|
||||||
"gui/task-list/edit",
|
|
||||||
"gui/warp-list/add",
|
|
||||||
"gui/warp-list/edit",
|
|
||||||
"gui/surveillance",
|
|
||||||
"gui/vlayer-edit",
|
|
||||||
"gui/tool",
|
|
||||||
"command/save-quickbar",
|
|
||||||
"command/vlayer-info",
|
|
||||||
"command/personal-logistic",
|
|
||||||
"command/lawnmower",
|
|
||||||
"command/waterfill",
|
|
||||||
"command/artillery",
|
|
||||||
}
|
|
||||||
|
|
||||||
local hours3, hours15 = 3 * 216000, 15 * 60
|
|
||||||
Roles.new_role("Regular", "Reg")
|
|
||||||
:set_permission_group("Standard")
|
|
||||||
:set_custom_color{ r = 79, g = 155, b = 163 }
|
|
||||||
:set_parent("Guest")
|
|
||||||
:allow{
|
|
||||||
"command/kill",
|
|
||||||
"command/rainbow",
|
|
||||||
"command/spawn",
|
|
||||||
"command/me",
|
|
||||||
"standard-decon",
|
|
||||||
"bypass-entity-protection",
|
|
||||||
"bypass-nukeprotect",
|
|
||||||
}
|
|
||||||
:set_auto_assign_condition(function(player)
|
|
||||||
if player.online_time >= hours3 then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
local stats = Statistics:get(player, {})
|
|
||||||
local playtime, afk_time, map_count = stats.Playtime or 0, stats.AfkTime or 0, stats.MapsPlayed or 0
|
|
||||||
return playtime - afk_time >= hours15 and map_count >= 5
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
--- Guest/Default role
|
|
||||||
local default = Roles.new_role("Guest", "")
|
|
||||||
:set_permission_group("Guest")
|
|
||||||
:set_custom_color{ r = 185, g = 187, b = 160 }
|
|
||||||
:allow{
|
|
||||||
"command/tag",
|
|
||||||
"command/tag-clear",
|
|
||||||
"command/commands",
|
|
||||||
"command/get-roles",
|
|
||||||
"command/locate",
|
|
||||||
"command/create-report",
|
|
||||||
"command/ratio",
|
|
||||||
"command/server-ups",
|
|
||||||
"command/save-data",
|
|
||||||
"command/data-preference",
|
|
||||||
"command/connect",
|
|
||||||
"gui/player-list",
|
|
||||||
--"gui/rocket-info",
|
|
||||||
"gui/science-info",
|
|
||||||
"gui/task-list",
|
|
||||||
"gui/warp-list",
|
|
||||||
"gui/readme",
|
|
||||||
"gui/vlayer",
|
|
||||||
"gui/research",
|
|
||||||
"gui/autofill",
|
|
||||||
"gui/module",
|
|
||||||
"gui/landfill",
|
|
||||||
"gui/production",
|
|
||||||
}
|
|
||||||
|
|
||||||
--- Jail role
|
|
||||||
Roles.new_role("Jail")
|
|
||||||
:set_permission_group("Restricted")
|
|
||||||
:set_custom_color{ r = 50, g = 50, b = 50 }
|
|
||||||
:set_block_auto_assign(true)
|
|
||||||
:set_flag("defer_role_changes")
|
|
||||||
:disallow(default.allowed)
|
|
||||||
|
|
||||||
--- System defaults which are required to be set
|
|
||||||
Roles.set_root("System")
|
|
||||||
Roles.set_default("Guest")
|
|
||||||
|
|
||||||
Roles.define_role_order{
|
|
||||||
"System", -- Best to keep root at top
|
|
||||||
"Senior Administrator",
|
|
||||||
"Administrator",
|
|
||||||
"Moderator",
|
|
||||||
"Trainee",
|
|
||||||
"Board Member",
|
|
||||||
"Senior Backer",
|
|
||||||
"Sponsor",
|
|
||||||
"Supporter",
|
|
||||||
"Partner",
|
|
||||||
"Veteran",
|
|
||||||
"Member",
|
|
||||||
"Regular",
|
|
||||||
"Jail",
|
|
||||||
"Guest", -- Default must be last if you want to apply restrictions to other roles
|
|
||||||
}
|
|
||||||
|
|
||||||
Roles.override_player_roles{
|
|
||||||
["PHIDIAS0303"] = { "Moderator", "Board Member", "Member" },
|
|
||||||
["aldldl"] = { "Administrator", "Moderator", "Member" },
|
|
||||||
["arty714"] = { "Senior Administrator", "Moderator", "Member" },
|
|
||||||
["Cooldude2606"] = { "Senior Administrator", "Moderator", "Member" },
|
|
||||||
["Drahc_pro"] = { "Administrator", "Moderator", "Member" },
|
|
||||||
["mark9064"] = { "Administrator", "Moderator", "Member" },
|
|
||||||
["7h3w1z4rd"] = { "Moderator", "Member" },
|
|
||||||
["FlipHalfling90"] = { "Moderator", "Member" },
|
|
||||||
["hamsterbryan"] = { "Moderator", "Member" },
|
|
||||||
["HunterOfGames"] = { "Moderator", "Member" },
|
|
||||||
["NextIdea"] = { "Moderator", "Member" },
|
|
||||||
["TheKernel32"] = { "Moderator", "Member" },
|
|
||||||
["TheKernel64"] = { "Moderator", "Member" },
|
|
||||||
["tovernaar123"] = { "Moderator", "Member" },
|
|
||||||
["UUBlueFire"] = { "Moderator", "Member" },
|
|
||||||
["AssemblyStorm"] = { "Moderator", "Member" },
|
|
||||||
["banakeg"] = { "Moderator", "Member" },
|
|
||||||
["connormkii"] = { "Moderator", "Member" },
|
|
||||||
["cydes"] = { "Moderator", "Member" },
|
|
||||||
["darklich14"] = { "Moderator", "Member" },
|
|
||||||
["facere"] = { "Moderator", "Member" },
|
|
||||||
["freek18"] = { "Moderator", "Member" },
|
|
||||||
["Gizan"] = { "Moderator", "Member" },
|
|
||||||
["LoicB"] = { "Moderator", "Member" },
|
|
||||||
["M74132"] = { "Moderator", "Member" },
|
|
||||||
["mafisch3"] = { "Moderator", "Member" },
|
|
||||||
["maplesyrup01"] = { "Moderator", "Member" },
|
|
||||||
["ookl"] = { "Moderator", "Member" },
|
|
||||||
["Phoenix27833"] = { "Moderator", "Member" },
|
|
||||||
["porelos"] = { "Moderator", "Member" },
|
|
||||||
["Ruuyji"] = { "Moderator", "Member" },
|
|
||||||
["samy115"] = { "Moderator", "Member" },
|
|
||||||
["SilentLog"] = { "Moderator", "Member" },
|
|
||||||
["Tcheko"] = { "Moderator", "Member" },
|
|
||||||
["thadius856"] = { "Moderator", "Member" },
|
|
||||||
["whoami32"] = { "Moderator", "Member" },
|
|
||||||
["Windbomb"] = { "Moderator", "Member" },
|
|
||||||
["XenoCyber"] = { "Moderator", "Member" },
|
|
||||||
}
|
|
||||||
@@ -7,33 +7,24 @@
|
|||||||
|
|
||||||
local ExpUtil = require("modules/exp_util")
|
local ExpUtil = require("modules/exp_util")
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
|
local Roles = require("modules/exp_roles")
|
||||||
local Reports = require("modules.exp_legacy.modules.control.reports") --- @dep modules.control.reports
|
local Reports = require("modules.exp_legacy.modules.control.reports") --- @dep modules.control.reports
|
||||||
local Warnings = require("modules.exp_legacy.modules.control.warnings") --- @dep modules.control.warnings
|
local Jail = require("modules/exp_scenario/control/jail")
|
||||||
local Jail = require("modules.exp_legacy.modules.control.jail") --- @dep modules.control.jail
|
|
||||||
local Colors = require("modules/exp_util/include/color")
|
local Colors = require("modules/exp_util/include/color")
|
||||||
local format_player_name = ExpUtil.format_player_name_locale
|
local format_player_name = ExpUtil.format_player_name_locale
|
||||||
|
|
||||||
local SelectedPlayer, SelectedAction
|
--- Accessors injected by the gui so the actions can read the selected player and set the selected action
|
||||||
local function set_datastores(player, action)
|
local get_selected_player --- @type fun(player: LuaPlayer): LuaPlayer
|
||||||
SelectedPlayer, SelectedAction = player, action
|
local set_selected_action --- @type fun(player: LuaPlayer, action: string?)
|
||||||
end
|
local function set_accessors(player_getter, action_setter)
|
||||||
|
get_selected_player, set_selected_action = player_getter, action_setter
|
||||||
-- auth that will only allow when on player's of lower roles
|
|
||||||
local function auth_lower_role(player, selected_player_name)
|
|
||||||
local player_highest = Roles.get_player_highest_role(player)
|
|
||||||
local action_player_highest = Roles.get_player_highest_role(selected_player_name)
|
|
||||||
if player_highest.index < action_player_highest.index then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- gets the action player and a coloured name for the action to be used on
|
-- gets the action player and a coloured name for the action to be used on
|
||||||
local function get_action_player_name(player)
|
local function get_action_player(player)
|
||||||
local selected_player_name = SelectedPlayer:get(player)
|
local selected_player = get_selected_player(player) --[[@as LuaPlayer]]
|
||||||
local selected_player = game.players[selected_player_name]
|
|
||||||
local selected_player_color = format_player_name(selected_player)
|
local selected_player_color = format_player_name(selected_player)
|
||||||
return selected_player_name, selected_player_color
|
return selected_player, selected_player_color
|
||||||
end
|
end
|
||||||
|
|
||||||
-- teleports one player to another
|
-- teleports one player to another
|
||||||
@@ -63,12 +54,11 @@ end
|
|||||||
|
|
||||||
--- Teleports the user to the action player
|
--- Teleports the user to the action player
|
||||||
-- @element goto_player
|
-- @element goto_player
|
||||||
local goto_player = new_button("utility/export", { "player-list.goto-player" })
|
local goto_player = new_button("utility/export", { "exp-gui_player-list.goto-player" })
|
||||||
:on_click(function(def, player, element)
|
:on_click(function(def, player, element)
|
||||||
local selected_player_name = get_action_player_name(player)
|
local selected_player = get_action_player(player)
|
||||||
local selected_player = game.players[selected_player_name]
|
|
||||||
if not player.character or not selected_player.character then
|
if not player.character or not selected_player.character then
|
||||||
player.print({ "expcore-commands.reject-player-alive" }, Colors.orange_red)
|
player.print({ "exp-commands-parse.player-alive" }, Colors.orange_red)
|
||||||
else
|
else
|
||||||
teleport(player, selected_player)
|
teleport(player, selected_player)
|
||||||
end
|
end
|
||||||
@@ -76,12 +66,11 @@ local goto_player = new_button("utility/export", { "player-list.goto-player" })
|
|||||||
|
|
||||||
--- Teleports the action player to the user
|
--- Teleports the action player to the user
|
||||||
-- @element bring_player
|
-- @element bring_player
|
||||||
local bring_player = new_button("utility/import", { "player-list.bring-player" })
|
local bring_player = new_button("utility/import", { "exp-gui_player-list.bring-player" })
|
||||||
:on_click(function(def, player, element)
|
:on_click(function(def, player, element)
|
||||||
local selected_player_name = get_action_player_name(player)
|
local selected_player = get_action_player(player)
|
||||||
local selected_player = game.players[selected_player_name]
|
|
||||||
if not player.character or not selected_player.character then
|
if not player.character or not selected_player.character then
|
||||||
player.print({ "expcore-commands.reject-player-alive" }, Colors.orange_red)
|
player.print({ "exp-commands-parse.player-alive" }, Colors.orange_red)
|
||||||
else
|
else
|
||||||
teleport(selected_player, player)
|
teleport(selected_player, player)
|
||||||
end
|
end
|
||||||
@@ -89,118 +78,100 @@ local bring_player = new_button("utility/import", { "player-list.bring-player" }
|
|||||||
|
|
||||||
--- Reports the action player, requires a reason to be given
|
--- Reports the action player, requires a reason to be given
|
||||||
-- @element report_player
|
-- @element report_player
|
||||||
local report_player = new_button("utility/spawn_flag", { "player-list.report-player" })
|
local report_player = new_button("utility/spawn_flag", { "exp-gui_player-list.report-player" })
|
||||||
:on_click(function(def, player, element)
|
:on_click(function(def, player, element)
|
||||||
local selected_player_name = get_action_player_name(player)
|
local selected_player = get_action_player(player)
|
||||||
if Reports.is_reported(selected_player_name, player.name) then
|
if Reports.is_reported(selected_player.name, player.name) then
|
||||||
player.print({ "exp-commands_report.already-reported" }, Colors.orange_red)
|
player.print({ "exp-commands_report.already-reported" }, Colors.orange_red)
|
||||||
else
|
else
|
||||||
SelectedAction:set(player, "command/report")
|
set_selected_action(player, "exp_scenario.command.create_report")
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local function report_player_callback(player, reason)
|
local function report_player_callback(player, reason)
|
||||||
local selected_player_name, selected_player_color = get_action_player_name(player)
|
local selected_player, selected_player_color = get_action_player(player)
|
||||||
local by_player_name_color = format_player_name(player)
|
local by_player_name_color = format_player_name(player)
|
||||||
game.print{ "exp-commands_reports.response", selected_player_color, reason }
|
game.print{ "exp-commands_reports.response", selected_player_color, reason }
|
||||||
Roles.print_to_roles_higher("Trainee", { "exp-commands_reports.response-admin", selected_player_color, by_player_name_color, reason })
|
local trainee = Roles.get_role_by_name("Trainee")
|
||||||
Reports.report_player(selected_player_name, player.name, reason)
|
for _, role in ipairs(trainee and Roles.get_higher_roles(trainee) or {}) do
|
||||||
end
|
role:print{ "exp-commands_reports.response-admin", selected_player_color, by_player_name_color, reason }
|
||||||
|
end
|
||||||
--- Gives the action player a warning, requires a reason
|
Reports.report_player(selected_player.name, player.name, reason)
|
||||||
-- @element warn_player
|
|
||||||
local warn_player = new_button("utility/spawn_flag", { "player-list.warn-player" })
|
|
||||||
:on_click(function(def, player, element)
|
|
||||||
SelectedAction:set(player, "command/give-warning")
|
|
||||||
end)
|
|
||||||
|
|
||||||
local function warn_player_callback(player, reason)
|
|
||||||
local selected_player_name, selected_player_color = get_action_player_name(player)
|
|
||||||
local by_player_name_color = format_player_name(player)
|
|
||||||
game.print{ "exp-commands_warnings.create", selected_player_color, by_player_name_color, reason }
|
|
||||||
Warnings.add_warning(selected_player_name, player.name, reason)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Jails the action player, requires a reason
|
--- Jails the action player, requires a reason
|
||||||
-- @element jail_player
|
-- @element jail_player
|
||||||
local jail_player = new_button("utility/multiplayer_waiting_icon", { "player-list.jail-player" })
|
local jail_player = new_button("utility/multiplayer_waiting_icon", { "exp-gui_player-list.jail-player" })
|
||||||
:on_click(function(def, player, element)
|
:on_click(function(def, player, element)
|
||||||
local selected_player_name, selected_player_color = get_action_player_name(player)
|
local selected_player, selected_player_color = get_action_player(player)
|
||||||
if Jail.is_jailed(selected_player_name) then
|
if Jail.is_jailed(selected_player) then
|
||||||
player.print({ "exp-commands_jail.already-jailed", selected_player_color }, Colors.orange_red)
|
player.print({ "exp-commands_jail.already-jailed", selected_player_color }, Colors.orange_red)
|
||||||
else
|
else
|
||||||
SelectedAction:set(player, "command/jail")
|
set_selected_action(player, "exp_scenario.command.jail")
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local function jail_player_callback(player, reason)
|
local function jail_player_callback(player, reason)
|
||||||
local selected_player_name, selected_player_color = get_action_player_name(player)
|
local selected_player, selected_player_color = get_action_player(player)
|
||||||
local by_player_name_color = format_player_name(player)
|
local by_player_name_color = format_player_name(player)
|
||||||
game.print{ "exp-commands_jail.jailed", selected_player_color, by_player_name_color, reason }
|
game.print{ "exp-commands_jail.jailed", selected_player_color, by_player_name_color, reason }
|
||||||
Jail.jail_player(selected_player_name, player.name, reason)
|
Jail.jail_player(selected_player, player.name, reason)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Kicks the action player, requires a reason
|
--- Kicks the action player, requires a reason
|
||||||
-- @element kick_player
|
-- @element kick_player
|
||||||
local kick_player = new_button("utility/warning_icon", { "player-list.kick-player" })
|
local kick_player = new_button("utility/warning_icon", { "exp-gui_player-list.kick-player" })
|
||||||
:on_click(function(def, player, element)
|
:on_click(function(def, player, element)
|
||||||
SelectedAction:set(player, "command/kick")
|
set_selected_action(player, "exp_scenario.gui.player_list.kick")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local function kick_player_callback(player, reason)
|
local function kick_player_callback(player, reason)
|
||||||
local selected_player = get_action_player_name(player)
|
local selected_player = get_action_player(player)
|
||||||
game.kick_player(selected_player, reason)
|
game.kick_player(selected_player, reason)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Bans the action player, requires a reason
|
--- Bans the action player, requires a reason
|
||||||
-- @element ban_player
|
-- @element ban_player
|
||||||
local ban_player = new_button("utility/danger_icon", { "player-list.ban-player" })
|
local ban_player = new_button("utility/danger_icon", { "exp-gui_player-list.ban-player" })
|
||||||
:on_click(function(def, player, element)
|
:on_click(function(def, player, element)
|
||||||
SelectedAction:set(player, "command/ban")
|
set_selected_action(player, "exp_scenario.gui.player_list.ban")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local function ban_player_callback(player, reason)
|
local function ban_player_callback(player, reason)
|
||||||
local selected_player = get_action_player_name(player)
|
local selected_player = get_action_player(player)
|
||||||
game.ban_player(selected_player, reason)
|
game.ban_player(selected_player, reason)
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
set_datastores = set_datastores,
|
set_accessors = set_accessors,
|
||||||
buttons = {
|
buttons = {
|
||||||
["command/teleport"] = {
|
["exp_scenario.command.teleport"] = {
|
||||||
auth = function(player, selected_player)
|
auth = function(player, selected_player)
|
||||||
return player.name ~= selected_player.name
|
return player.name ~= selected_player.name
|
||||||
end, -- cant teleport to your self
|
end, -- cant teleport to your self
|
||||||
goto_player,
|
goto_player,
|
||||||
bring_player,
|
bring_player,
|
||||||
},
|
},
|
||||||
["command/report"] = {
|
["exp_scenario.command.create_report"] = {
|
||||||
auth = function(player, selected_player)
|
auth = function(player, selected_player)
|
||||||
if player == selected_player then return false end
|
if player == selected_player then return false end
|
||||||
if not Roles.player_allowed(player, "command/give-warning") then
|
return not Roles.player_has_permission(selected_player, "exp_scenario.bypass.reports")
|
||||||
return not Roles.player_has_flag(selected_player, "report-immune")
|
end, -- can report any player that isn't immune
|
||||||
end
|
|
||||||
end, -- can report any player that isn't immune and you aren't able to give warnings
|
|
||||||
reason_callback = report_player_callback,
|
reason_callback = report_player_callback,
|
||||||
report_player,
|
report_player,
|
||||||
},
|
},
|
||||||
["command/give-warning"] = {
|
["exp_scenario.command.jail"] = {
|
||||||
auth = auth_lower_role, -- warn a lower user, replaces report
|
auth = Roles.player_outranks,
|
||||||
reason_callback = warn_player_callback,
|
|
||||||
warn_player,
|
|
||||||
},
|
|
||||||
["command/jail"] = {
|
|
||||||
auth = auth_lower_role,
|
|
||||||
reason_callback = jail_player_callback,
|
reason_callback = jail_player_callback,
|
||||||
jail_player,
|
jail_player,
|
||||||
},
|
},
|
||||||
["command/kick"] = {
|
["exp_scenario.gui.player_list.kick"] = {
|
||||||
auth = auth_lower_role,
|
auth = Roles.player_outranks,
|
||||||
reason_callback = kick_player_callback,
|
reason_callback = kick_player_callback,
|
||||||
kick_player,
|
kick_player,
|
||||||
},
|
},
|
||||||
["command/ban"] = {
|
["exp_scenario.gui.player_list.ban"] = {
|
||||||
auth = auth_lower_role,
|
auth = Roles.player_outranks,
|
||||||
reason_callback = ban_player_callback,
|
reason_callback = ban_player_callback,
|
||||||
ban_player,
|
ban_player,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ return {
|
|||||||
allow_zoom_to_map = true, --- @setting allow_zoom_to_map false will disable the zoom to map feature
|
allow_zoom_to_map = true, --- @setting allow_zoom_to_map false will disable the zoom to map feature
|
||||||
allow_remote_launch = true, --- @setting allow_remote_launch false removes the remote launch button for all players
|
allow_remote_launch = true, --- @setting allow_remote_launch false removes the remote launch button for all players
|
||||||
remote_launch_admins_only = false, --- @setting remote_launch_admins_only true will remove the remote launch button for all non (game) admins
|
remote_launch_admins_only = false, --- @setting remote_launch_admins_only true will remove the remote launch button for all non (game) admins
|
||||||
remote_launch_role_permission = "gui/rocket-info/remote_launch", --- @setting remote_launch_role_permission value used by custom permission system to allow or disallow the button
|
remote_launch_role_permission = "exp_scenario.gui.rocket_info.remote_launch", --- @setting remote_launch_role_permission value used by custom permission system to allow or disallow the button
|
||||||
allow_toggle_active = true, --- @setting allow_toggle_active false removes the remote toggle auto launch button for all players
|
allow_toggle_active = true, --- @setting allow_toggle_active false removes the remote toggle auto launch button for all players
|
||||||
toggle_active_admins_only = false, --- @setting toggle_active_admins_only true will remove the toggle auto launch button for all non (game) admins
|
toggle_active_admins_only = false, --- @setting toggle_active_admins_only true will remove the toggle auto launch button for all non (game) admins
|
||||||
toggle_active_role_permission = "gui/rocket-info/toggle-active", --- @setting toggle_active_role_permission value used by custom permission system to allow or disallow the button
|
toggle_active_role_permission = "exp_scenario.gui.rocket_info.toggle_active", --- @setting toggle_active_role_permission value used by custom permission system to allow or disallow the button
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
-- Adding tasks
|
-- Adding tasks
|
||||||
allow_add_task = "all", --- @setting allow_add_task dictates who is allowed to add new tasks; values: all, admin, expcore.roles, none
|
allow_add_task = "all", --- @setting allow_add_task dictates who is allowed to add new tasks; values: all, admin, exp_roles, none
|
||||||
expcore_roles_allow_add_task = "gui/task-list/add", --- @setting expcore_roles_allow_add_task if expcore.roles is used then this is the required permission
|
exp_roles_allow_add_task = "exp_scenario.gui.task_list.add", --- @setting exp_roles_allow_add_task if exp_roles is used then this is the required permission
|
||||||
|
|
||||||
-- Editing tasks
|
-- Editing tasks
|
||||||
allow_edit_task = "expcore.roles", --- @setting allow_edit_task dictates who is allowed to edit existing tasks; values: all, admin, expcore.roles, none
|
allow_edit_task = "exp_roles", --- @setting allow_edit_task dictates who is allowed to edit existing tasks; values: all, admin, exp_roles, none
|
||||||
expcore_roles_allow_edit_task = "gui/task-list/edit", --- @setting expcore_roles_allow_edit_task if expcore.roles is used then this is the required permission
|
exp_roles_allow_edit_task = "exp_scenario.gui.task_list.edit", --- @setting exp_roles_allow_edit_task if exp_roles is used then this is the required permission
|
||||||
user_can_edit_own_tasks = true, --- @settings if true then the user who made the task can edit it regardless of the allow_edit_task setting
|
user_can_edit_own_tasks = true, --- @settings if true then the user who made the task can edit it regardless of the allow_edit_task setting
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,23 +8,23 @@ return {
|
|||||||
default_icon = { type = "item", name = "discharge-defense-equipment" }, --- @setting default_icon the default icon that will be used for warps
|
default_icon = { type = "item", name = "discharge-defense-equipment" }, --- @setting default_icon the default icon that will be used for warps
|
||||||
|
|
||||||
-- Warp cooldowns
|
-- Warp cooldowns
|
||||||
bypass_warp_cooldown = "expcore.roles", --- @setting bypass_warp_cooldown dictates who the warp cooldown is applied to; values: all, admin, expcore.roles, none
|
bypass_warp_cooldown = "exp_roles", --- @setting bypass_warp_cooldown dictates who the warp cooldown is applied to; values: all, admin, exp_roles, none
|
||||||
expcore_roles_bypass_warp_cooldown = "gui/warp-list/bypass-cooldown", --- @setting expcore_roles_bypass_warp_cooldown if expcore.roles is used then this is the required permission
|
exp_roles_bypass_warp_cooldown = "exp_scenario.gui.warp_list.bypass_cooldown", --- @setting exp_roles_bypass_warp_cooldown if exp_roles is used then this is the required permission
|
||||||
cooldown_duration = 60, --- @setting cooldown_duration the duration of the warp cooldown in seconds
|
cooldown_duration = 60, --- @setting cooldown_duration the duration of the warp cooldown in seconds
|
||||||
|
|
||||||
-- Warp proximity
|
-- Warp proximity
|
||||||
bypass_warp_proximity = "expcore.roles", --- @setting bypass_warp_proximity dictates who the warp proximity is applied to; values: all, admin, expcore.roles, none
|
bypass_warp_proximity = "exp_roles", --- @setting bypass_warp_proximity dictates who the warp proximity is applied to; values: all, admin, exp_roles, none
|
||||||
expcore_roles_bypass_warp_proximity = "gui/warp-list/bypass-proximity", --- @setting expcore_roles_bypass_warp_proximity if expcore.roles is used then this is the required permission
|
exp_roles_bypass_warp_proximity = "exp_scenario.gui.warp_list.bypass_proximity", --- @setting exp_roles_bypass_warp_proximity if exp_roles is used then this is the required permission
|
||||||
standard_proximity_radius = 4, --- @setting standard_proximity_radius the minimum distance a player is allowed to be to a warp in order to use it
|
standard_proximity_radius = 4, --- @setting standard_proximity_radius the minimum distance a player is allowed to be to a warp in order to use it
|
||||||
spawn_proximity_radius = 20, --- @setting spawn_proximity_radius the minimum distance a player is allowed to be from they spawn point to use warps
|
spawn_proximity_radius = 20, --- @setting spawn_proximity_radius the minimum distance a player is allowed to be from they spawn point to use warps
|
||||||
|
|
||||||
-- Adding warps
|
-- Adding warps
|
||||||
allow_add_warp = "expcore.roles", --- @setting allow_add_warp dictates who is allowed to add warps; values: all, admin, expcore.roles, none
|
allow_add_warp = "exp_roles", --- @setting allow_add_warp dictates who is allowed to add warps; values: all, admin, exp_roles, none
|
||||||
expcore_roles_allow_add_warp = "gui/warp-list/add", --- @setting expcore_roles_allow_add_warp if expcore.roles is used then this is the required permission
|
exp_roles_allow_add_warp = "exp_scenario.gui.warp_list.add", --- @setting exp_roles_allow_add_warp if exp_roles is used then this is the required permission
|
||||||
|
|
||||||
-- Editing warps
|
-- Editing warps
|
||||||
allow_edit_warp = "expcore.roles", --- @setting allow_edit_warp dictates who is allowed to edit warps; values: all, admin, expcore.roles, none
|
allow_edit_warp = "exp_roles", --- @setting allow_edit_warp dictates who is allowed to edit warps; values: all, admin, exp_roles, none
|
||||||
expcore_roles_allow_edit_warp = "gui/warp-list/edit", --- @setting expcore_roles_allow_edit_warp if expcore.roles is used then this is the required permission
|
exp_roles_allow_edit_warp = "exp_scenario.gui.warp_list.edit", --- @setting exp_roles_allow_edit_warp if exp_roles is used then this is the required permission
|
||||||
user_can_edit_own_warps = false, --- @settings user_can_edit_own_warps if true then the user who made the warp can edit it regardless of the allow_edit_warp setting
|
user_can_edit_own_warps = false, --- @settings user_can_edit_own_warps if true then the user who made the warp can edit it regardless of the allow_edit_warp setting
|
||||||
|
|
||||||
-- Warp area generation
|
-- Warp area generation
|
||||||
|
|||||||
@@ -4,4 +4,5 @@
|
|||||||
return {
|
return {
|
||||||
fluid = true, --- @setting fluid When true, checks for for fluid pipes when removing miners
|
fluid = true, --- @setting fluid When true, checks for for fluid pipes when removing miners
|
||||||
chest = true, --- @setting chest When true, checks for for chest when removing miners
|
chest = true, --- @setting chest When true, checks for for chest when removing miners
|
||||||
|
beacon = true, --- @setting beacon When true, checks for for beacon when removing miners
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,6 @@ return {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ignore_permission = "bypass-nukeprotect", -- @setting ignore_permission The permission that nukeprotect will ignore
|
ignore_permission = "exp_scenario.bypass.nuke_protection", -- @setting ignore_permission The permission that nukeprotect will ignore
|
||||||
ignore_admins = true, -- @setting ignore_admins Ignore admins, true by default. Allows usage outside of the roles module
|
ignore_admins = true, -- @setting ignore_admins Ignore admins, true by default. Allows usage outside of the roles module
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,17 @@
|
|||||||
return {
|
return {
|
||||||
ignore_admins = true, --- @setting ignore_admins If admins are ignored by the protection filter
|
ignore_admins = true, --- @setting ignore_admins If admins are ignored by the protection filter
|
||||||
ignore_permission = "bypass-entity-protection", --- @setting ignore_permission Players with this permission will be ignored by the protection filter, leave nil if expcore.roles is not used
|
ignore_permission = "exp_scenario.bypass.entity_protection", --- @setting ignore_permission Players with this permission will be ignored by the protection filter, leave nil to protect against every player
|
||||||
repeat_count = 5, --- @setting repeat_count Number of protected entities that must be removed within repeat_lifetime in order to trigger repeated removal protection
|
repeat_count = 5, --- @setting repeat_count Number of protected entities that must be removed within repeat_lifetime in order to trigger repeated removal protection
|
||||||
repeat_lifetime = 3600 * 20, --- @setting repeat_lifetime The length of time, in ticks, that protected removals will be remembered for
|
repeat_lifetime = 3600 * 20, --- @setting repeat_lifetime The length of time, in ticks, that protected removals will be remembered for
|
||||||
refresh_rate = 3600 * 5, --- @setting refresh_rate How often the age of protected removals are checked against repeat_lifetime
|
refresh_rate = 3600 * 5, --- @setting refresh_rate How often the age of protected removals are checked against repeat_lifetime
|
||||||
always_protected_names = { --- @setting always_protected_names Names of entities which are always protected
|
always_protected_names = { --- @setting always_protected_names Names of entities which are always protected
|
||||||
|
|
||||||
},
|
},
|
||||||
always_protected_types = { --- @setting always_protected_types Types of entities which are always protected
|
always_protected_types = { --- @setting always_protected_types Types of entities which are always protected
|
||||||
"boiler", "generator", "offshore-pump", "power-switch", "reactor", "rocket-silo",
|
"boiler", "generator", "offshore-pump", "reactor", "heat-exchanger", "heat-pipe", "fusion-reactor", "fusion-generator", "power-switch", "rocket-silo",
|
||||||
},
|
},
|
||||||
always_trigger_repeat_names = { --- @setting always_trigger_repeat_names Names of entities which always trigger repeated removal protection
|
always_trigger_repeat_names = { --- @setting always_trigger_repeat_names Names of entities which always trigger repeated removal protection
|
||||||
|
|
||||||
},
|
},
|
||||||
always_trigger_repeat_types = { --- @setting always_trigger_repeat_types Types of entities which always trigger repeated removal protection
|
always_trigger_repeat_types = { --- @setting always_trigger_repeat_types Types of entities which always trigger repeated removal protection
|
||||||
"reactor", "rocket-silo",
|
"reactor", "fusion-reactor", "rocket-silo",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
-- @config Repair
|
-- @config Repair
|
||||||
|
|
||||||
return {
|
return {
|
||||||
disallow = { --- @setting disallow items in this list will never be repaired
|
|
||||||
["loader"] = true,
|
|
||||||
["fast-loader"] = true,
|
|
||||||
["express-loader"] = true,
|
|
||||||
["electric-energy-interface"] = true,
|
|
||||||
["infinity-chest"] = true,
|
|
||||||
},
|
|
||||||
max_range = 50, --- @setting max_range the max range that can be used with the repair command
|
|
||||||
allow_blueprint_repair = false, --- @setting allow_blueprint_repair when true will allow blueprints (things not destroyed by biters) to be build instantly using the repair command
|
allow_blueprint_repair = false, --- @setting allow_blueprint_repair when true will allow blueprints (things not destroyed by biters) to be build instantly using the repair command
|
||||||
allow_ghost_revive = true, --- @setting allow_ghost_revive when true will allow ghosts (things destroyed by biters) to be build instantly using the repair command
|
allow_ghost_revive = true, --- @setting allow_ghost_revive when true will allow ghosts (things destroyed by biters) to be build instantly using the repair command
|
||||||
allow_heal_entities = true, --- @setting allow_heal_entities when true will heal entities to full health that are within range
|
allow_heal_entities = true, --- @setting allow_heal_entities when true will heal entities to full health that are within range
|
||||||
|
|||||||
@@ -12,16 +12,6 @@ return {
|
|||||||
-- this enable 20 more inventory for each mining productivity level up to 4
|
-- this enable 20 more inventory for each mining productivity level up to 4
|
||||||
bonus_inventory = {
|
bonus_inventory = {
|
||||||
enabled = true,
|
enabled = true,
|
||||||
log = {
|
|
||||||
["base"] = {
|
|
||||||
["name"] = "mining-productivity-4",
|
|
||||||
["level"] = 4
|
|
||||||
},
|
|
||||||
["space-age"] = {
|
|
||||||
["name"] = "mining-productivity-3",
|
|
||||||
["level"] = 3
|
|
||||||
},
|
|
||||||
},
|
|
||||||
res = {
|
res = {
|
||||||
-- Mining Productivity
|
-- Mining Productivity
|
||||||
["mining-productivity"] = true,
|
["mining-productivity"] = true,
|
||||||
@@ -33,7 +23,19 @@ return {
|
|||||||
rate = 5,
|
rate = 5,
|
||||||
limit = 20,
|
limit = 20,
|
||||||
},
|
},
|
||||||
file_name = "log/research.log",
|
file = {
|
||||||
|
name = "log/research.log",
|
||||||
|
final_res = {
|
||||||
|
["base"] = {
|
||||||
|
["name"] = "mining-productivity-4",
|
||||||
|
["level"] = 4
|
||||||
|
},
|
||||||
|
["space-age"] = {
|
||||||
|
["name"] = "mining-productivity-3",
|
||||||
|
["level"] = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
milestone = {
|
milestone = {
|
||||||
["base"] = {
|
["base"] = {
|
||||||
["automation"] = 600,
|
["automation"] = 600,
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
--- Config file for the server detail
|
||||||
|
-- @config Server
|
||||||
|
|
||||||
|
return {
|
||||||
|
community_name = "Explosive Gaming",
|
||||||
|
discord = "https://discord.explosivegaming.nl",
|
||||||
|
website = "https://www.explosivegaming.nl",
|
||||||
|
status = "https://status.explosivegaming.nl",
|
||||||
|
github = "https://github.com/explosivegaming/ExpCluster",
|
||||||
|
patreon = "https://www.patreon.com/ExpGaming"
|
||||||
|
}
|
||||||
@@ -188,7 +188,7 @@ return {
|
|||||||
amount = 4000,
|
amount = 4000,
|
||||||
size = { 26, 27 },
|
size = { 26, 27 },
|
||||||
-- offset = {-64,-32}
|
-- offset = {-64,-32}
|
||||||
offset = { -64, -64 },
|
offset = { x = -64, y = -64 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled = false,
|
enabled = false,
|
||||||
@@ -196,7 +196,7 @@ return {
|
|||||||
amount = 4000,
|
amount = 4000,
|
||||||
size = { 26, 27 },
|
size = { 26, 27 },
|
||||||
-- offset = {-64, 0}
|
-- offset = {-64, 0}
|
||||||
offset = { 64, -64 },
|
offset = { x = 64, y = -64 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled = false,
|
enabled = false,
|
||||||
@@ -204,7 +204,7 @@ return {
|
|||||||
amount = 4000,
|
amount = 4000,
|
||||||
size = { 22, 20 },
|
size = { 22, 20 },
|
||||||
-- offset = {-64, 32}
|
-- offset = {-64, 32}
|
||||||
offset = { -64, 64 },
|
offset = { x = -64, y = 64 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled = false,
|
enabled = false,
|
||||||
@@ -212,7 +212,7 @@ return {
|
|||||||
amount = 4000,
|
amount = 4000,
|
||||||
size = { 22, 20 },
|
size = { 22, 20 },
|
||||||
-- offset = {-64, -64}
|
-- offset = {-64, -64}
|
||||||
offset = { 64, 64 },
|
offset = { x = 64, y = 64 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled = false,
|
enabled = false,
|
||||||
@@ -220,7 +220,7 @@ return {
|
|||||||
amount = 4000,
|
amount = 4000,
|
||||||
size = { 22, 20 },
|
size = { 22, 20 },
|
||||||
-- offset = {-64, -96}
|
-- offset = {-64, -96}
|
||||||
offset = { 0, 64 },
|
offset = { x = 0, y = 64 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -233,7 +233,7 @@ return {
|
|||||||
num_patches = 4,
|
num_patches = 4,
|
||||||
amount = 4000000,
|
amount = 4000000,
|
||||||
-- offset = {-80, -12},
|
-- offset = {-80, -12},
|
||||||
offset = { -12, 64 },
|
offset = { x = -12, y = 64 },
|
||||||
-- offset_next = {0, 6}
|
-- offset_next = {0, 6}
|
||||||
offset_next = { 6, 0 },
|
offset_next = { 6, 0 },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,6 +35,20 @@ return {
|
|||||||
storage_output = 1, -- >0 allows for item teleportation (allowed_items only)
|
storage_output = 1, -- >0 allows for item teleportation (allowed_items only)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
--- @class Vlayer.ItemProperties
|
||||||
|
--- @field starting_value number
|
||||||
|
--- @field required_area number
|
||||||
|
--- @field production number?
|
||||||
|
--- @field discharge number?
|
||||||
|
--- @field capacity number?
|
||||||
|
--- @field surface_area number?
|
||||||
|
--- @field fuel_value number?
|
||||||
|
--- @field power boolean?
|
||||||
|
--- @field modded boolean? Set when the item is registered from a modded equivalent
|
||||||
|
--- @field base_game_equivalent string?
|
||||||
|
--- @field multiplier number?
|
||||||
|
|
||||||
|
--- @type table<string, Vlayer.ItemProperties>
|
||||||
allowed_items = { --- @setting allowed_items List of all items allowed in vlayer storage and their properties
|
allowed_items = { --- @setting allowed_items List of all items allowed in vlayer storage and their properties
|
||||||
--[[
|
--[[
|
||||||
Allowed properties:
|
Allowed properties:
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
--- Config file for the warning system, this is very similar to reports but is for the use of moderators rather than normal users.
|
|
||||||
-- @config Warnings
|
|
||||||
|
|
||||||
return {
|
|
||||||
actions = { --- @setting actions what actions are taking at number of warnings
|
|
||||||
-- if a localized string is used then __1__ will by_player_name and __2__ will be the current warning count (auto inserted)
|
|
||||||
{ "warnings.received", "" },
|
|
||||||
{ "warnings.received", "" },
|
|
||||||
{ "warnings.received", { "warnings.pre-kick" } },
|
|
||||||
function(player, by_player_name, number_of_warnings)
|
|
||||||
local str = {
|
|
||||||
"You received a warning from ",
|
|
||||||
by_player_name,
|
|
||||||
". You have ",
|
|
||||||
number_of_warnings,
|
|
||||||
" warnings. You were kicked for having too many warnings; you may rejoin if you wish.",
|
|
||||||
}
|
|
||||||
game.kick_player(player, table.concat(str, "")) -- Does not support locale strings
|
|
||||||
-- game.kick_player(player, { "warnings.received", by_player_name, number_of_warnings, { "warnings.kick" } })
|
|
||||||
end,
|
|
||||||
{ "warnings.received", { "warnings.pre-pre-ban" } },
|
|
||||||
{ "warnings.received", { "warnings.pre-ban" } },
|
|
||||||
function(player, by_player_name, number_of_warnings)
|
|
||||||
local str = {
|
|
||||||
"You received a warning from ",
|
|
||||||
by_player_name,
|
|
||||||
". You have ",
|
|
||||||
number_of_warnings,
|
|
||||||
" warnings. You were banned for having too many warnings; visit https://www.explosivegaming.nl to request a ban appeal.",
|
|
||||||
}
|
|
||||||
game.kick_player(player, table.concat(str, "")) -- Does not support locale strings
|
|
||||||
-- game.ban_player(player, { "warnings.received", by_player_name, number_of_warnings, { "warnings.ban", { "links.website" } } })
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
script_warning_cool_down = 30, --- @setting script_warning_cool_down time for a script warning (given by script) to be removed (in minutes)
|
|
||||||
script_warning_limit = 5, --- @setting script_warning_limit the number of script warnings (given by script) that are allowed before full warnings are given
|
|
||||||
}
|
|
||||||
@@ -149,9 +149,23 @@ PlayerData.Statistics:combine('JoinCount')
|
|||||||
local Storage = require("modules/exp_util/storage")
|
local Storage = require("modules/exp_util/storage")
|
||||||
|
|
||||||
local DatastoreManager = {}
|
local DatastoreManager = {}
|
||||||
local Datastores = {}
|
local Datastores = {} --- @type table<string, Datastore>
|
||||||
local Datastore = {}
|
|
||||||
local Data = {}
|
local Data = {}
|
||||||
|
|
||||||
|
--- @class Datastore
|
||||||
|
--- @field name string
|
||||||
|
--- @field value_name string
|
||||||
|
--- @field auto_save boolean
|
||||||
|
--- @field save_to_disk boolean
|
||||||
|
--- @field propagate_changes boolean
|
||||||
|
--- @field serializer function | false
|
||||||
|
--- @field parent Datastore | false
|
||||||
|
--- @field children table<string, Datastore>
|
||||||
|
--- @field metadata table
|
||||||
|
--- @field events table<string, function[]>
|
||||||
|
--- @field data table
|
||||||
|
local Datastore = {}
|
||||||
|
|
||||||
local copy = table.deep_copy
|
local copy = table.deep_copy
|
||||||
local trace = debug.traceback
|
local trace = debug.traceback
|
||||||
local table_to_json = helpers.table_to_json
|
local table_to_json = helpers.table_to_json
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ end
|
|||||||
|
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local ext, var
|
local ext --- @type table<string, any>
|
||||||
|
local var --- @type table<string, any>
|
||||||
local concat = table.concat
|
local concat = table.concat
|
||||||
|
|
||||||
local External = {}
|
local External = {}
|
||||||
@@ -29,12 +30,13 @@ end
|
|||||||
|
|
||||||
]]
|
]]
|
||||||
function External.valid()
|
function External.valid()
|
||||||
if storage.ext == nil then return false end
|
local stored = storage.ext
|
||||||
if ext == storage.ext and var == ext.var then
|
if stored == nil then return false end
|
||||||
|
if ext == stored and var == ext.var then
|
||||||
return var ~= nil
|
return var ~= nil
|
||||||
else
|
else
|
||||||
ext = storage.ext
|
ext = stored
|
||||||
var = ext.var
|
var = stored.var
|
||||||
return var ~= nil
|
return var ~= nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -48,7 +50,7 @@ local servers = External.get_servers()
|
|||||||
]]
|
]]
|
||||||
function External.get_servers()
|
function External.get_servers()
|
||||||
assert(ext, "No external data was found, use External.valid() to ensure external data exists.")
|
assert(ext, "No external data was found, use External.valid() to ensure external data exists.")
|
||||||
return assert(ext.servers, "No server list was found, please ensure that the external service is running")
|
return (assert(ext.servers, "No server list was found, please ensure that the external service is running"))
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[-- Gets a table of all the servers filtered by name, key is the server id, value is the server details
|
--[[-- Gets a table of all the servers filtered by name, key is the server id, value is the server details
|
||||||
@@ -83,7 +85,7 @@ function External.get_current_server()
|
|||||||
assert(ext, "No external data was found, use External.valid() to ensure external data exists.")
|
assert(ext, "No external data was found, use External.valid() to ensure external data exists.")
|
||||||
local servers = assert(ext.servers, "No server list was found, please ensure that the external service is running")
|
local servers = assert(ext.servers, "No server list was found, please ensure that the external service is running")
|
||||||
local server_id = assert(ext.current, "No current id was found, please ensure that the external service is running")
|
local server_id = assert(ext.current, "No current id was found, please ensure that the external service is running")
|
||||||
return assert(servers[server_id], "No details found for server with id: " .. tostring(server_id))
|
return (assert(servers[server_id], "No details found for server with id: " .. tostring(server_id)))
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[-- Gets the details of the given server
|
--[[-- Gets the details of the given server
|
||||||
@@ -97,7 +99,7 @@ local server = External.get_server_details('eu-01')
|
|||||||
function External.get_server_details(server_id)
|
function External.get_server_details(server_id)
|
||||||
assert(ext, "No external data was found, use External.valid() to ensure external data exists.")
|
assert(ext, "No external data was found, use External.valid() to ensure external data exists.")
|
||||||
local servers = assert(ext.servers, "No server list was found, please ensure that the external service is running")
|
local servers = assert(ext.servers, "No server list was found, please ensure that the external service is running")
|
||||||
return assert(servers[server_id], "No details found for server with id: " .. tostring(server_id))
|
return (assert(servers[server_id], "No details found for server with id: " .. tostring(server_id)))
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[-- Gets the status of the given server
|
--[[-- Gets the status of the given server
|
||||||
|
|||||||
@@ -1,355 +0,0 @@
|
|||||||
--[[-- Core Module - Permission Groups
|
|
||||||
- Permission group making for factorio so you never have to make one by hand again
|
|
||||||
@core Groups
|
|
||||||
@alias Permissions_Groups
|
|
||||||
|
|
||||||
@usage--- Example Group (Allow All)
|
|
||||||
-- here we will create an admin group however we do not want them to use the map editor or mess with the permission groups
|
|
||||||
Permission_Groups.new_group('Admin') -- this defines a new group called "Admin"
|
|
||||||
:allow_all() -- this makes the default to allow any input action unless set other wise
|
|
||||||
:disallow{ -- here we disallow the input action we don't want them to use
|
|
||||||
'add_permission_group',
|
|
||||||
'delete_permission_group',
|
|
||||||
'import_permissions_string',
|
|
||||||
'map_editor_action',
|
|
||||||
'toggle_map_editor'
|
|
||||||
}
|
|
||||||
|
|
||||||
@usage--- Example Group (Disallow All)
|
|
||||||
-- here we will create a group that cant do anything but talk in chat
|
|
||||||
Permission_Groups.new_group('Restricted') -- this defines a new group called "Restricted"
|
|
||||||
:disallow_all() -- this makes the default to disallow any input action unless set other wise
|
|
||||||
:allow('write_to_console') -- here we allow them to chat, {} can be used here if we had more than one action
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
local Event = require("modules/exp_legacy/utils/event")
|
|
||||||
local Async = require("modules/exp_util/async")
|
|
||||||
|
|
||||||
local PermissionsGroups = {
|
|
||||||
groups = {}, -- store for the different groups that are created
|
|
||||||
_prototype = {}, -- stores functions that are used on group instances
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Async function to add players to permission groups
|
|
||||||
local add_to_permission_group_async =
|
|
||||||
Async.register(function(permission_group, player)
|
|
||||||
permission_group.add_player(player)
|
|
||||||
end)
|
|
||||||
PermissionsGroups.add_to_permission_group_async = add_to_permission_group_async
|
|
||||||
|
|
||||||
-- Async function to remove players from permission groups
|
|
||||||
local remove_from_permission_group_async =
|
|
||||||
Async.register(function(permission_group, player)
|
|
||||||
permission_group.remove_player(player)
|
|
||||||
end)
|
|
||||||
PermissionsGroups.remove_from_permission_group_async = remove_from_permission_group_async
|
|
||||||
|
|
||||||
--- Getters.
|
|
||||||
-- Functions that get permission groups
|
|
||||||
-- @section getters
|
|
||||||
|
|
||||||
--[[-- Defines a new permission group that can have it actions set in the config
|
|
||||||
@tparam string name the name of the new group
|
|
||||||
@treturn Permissions_Groups._prototype the new group made with function to allow and disallow actions
|
|
||||||
|
|
||||||
@usage-- Defining a new permission group
|
|
||||||
Groups.new_group('Admin')
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups.new_group(name)
|
|
||||||
local group = setmetatable({
|
|
||||||
name = name,
|
|
||||||
actions = {},
|
|
||||||
allow_all_actions = true,
|
|
||||||
}, {
|
|
||||||
__index = PermissionsGroups._prototype,
|
|
||||||
})
|
|
||||||
PermissionsGroups.groups[name] = group
|
|
||||||
return group
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- Returns the group with the given name, case sensitive
|
|
||||||
@tparam string name the name of the group to get
|
|
||||||
@treturn ?Permissions_Groups._prototype|nil the group with that name or nil if non found
|
|
||||||
|
|
||||||
@usage-- Getting a permision group
|
|
||||||
local admin_group = Groups.get_group_by_name('Admin')
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups.get_group_by_name(name)
|
|
||||||
return PermissionsGroups.groups[name]
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- Returns the group that a player is in
|
|
||||||
@tparam LuaPlayer player the player to get the group of can be name index etc
|
|
||||||
@treturn ?Permissions_Groups._prototype|nil the group with that player or nil if non found
|
|
||||||
|
|
||||||
@usage-- Get your permission group
|
|
||||||
local group = Groups.get_group_from_player(game.player)
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups.get_group_from_player(player)
|
|
||||||
local group = player.permission_group
|
|
||||||
if group then
|
|
||||||
return PermissionsGroups.groups[group.name]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Setters.
|
|
||||||
-- Functions that control all groups
|
|
||||||
-- @section players
|
|
||||||
|
|
||||||
--[[-- Reloads/creates all permission groups and sets them to they configured state
|
|
||||||
|
|
||||||
@usage-- Reload the permission groups, used internally
|
|
||||||
Groups.reload_permissions()
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups.reload_permissions()
|
|
||||||
for _, group in pairs(PermissionsGroups.groups) do
|
|
||||||
group:create()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- Sets a player's group to the one given, a player can only have one group at a time
|
|
||||||
@tparam LuaPlayer player the player to effect can be name index etc
|
|
||||||
@tparam string group the name of the group to give to the player
|
|
||||||
@treturn boolean true if the player was added successfully, false other wise
|
|
||||||
|
|
||||||
@usage-- Set your permission group
|
|
||||||
Groups.set_player_group(game.player, 'Admin')
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups.set_player_group(player, group)
|
|
||||||
group = PermissionsGroups.get_group_by_name(group)
|
|
||||||
if not group or not player then return false end
|
|
||||||
group:add_player(player)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Actions.
|
|
||||||
-- Functions that control group actions
|
|
||||||
-- @section actions
|
|
||||||
|
|
||||||
--[[-- Sets the allow state of an action for this group, used internally but is safe to use else where
|
|
||||||
@tparam ?string|defines.input_action action the action that you want to set the state of
|
|
||||||
@tparam boolean state the state that you want to set it to, true = allow, false = disallow
|
|
||||||
@treturn Permissions_Groups._prototype returns self so function can be chained
|
|
||||||
|
|
||||||
@usage-- Set an action to be disallowed
|
|
||||||
group:set_action('toggle_map_editor', false)
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups._prototype:set_action(action, state)
|
|
||||||
local input_action = defines.input_action[action] --[[ @as defines.input_action? ]]
|
|
||||||
if input_action == nil then input_action = action end
|
|
||||||
assert(type(input_action) == "number", tostring(action) .. " is not a valid input action")
|
|
||||||
self.actions[input_action] = state
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- Sets an action or actions to be allowed for this group even with disallow_all triggered, Do not use in runtime
|
|
||||||
@tparam string|Array<string> actions the action or actions that you want to allow for this group
|
|
||||||
@treturn Permissions_Groups._prototype returns self so function can be chained
|
|
||||||
|
|
||||||
@usage-- Allow some actions
|
|
||||||
group:allow{
|
|
||||||
'write_to_console'
|
|
||||||
}
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups._prototype:allow(actions)
|
|
||||||
if type(actions) ~= "table" then
|
|
||||||
actions = { actions }
|
|
||||||
end
|
|
||||||
for _, action in pairs(actions) do
|
|
||||||
self:set_action(action, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- Sets an action or actions to be disallowed for this group even with allow_all triggered, Do not use in runtime
|
|
||||||
@tparam string|Array<string> actions the action or actions that you want to disallow for this group
|
|
||||||
@treturn Permissions_Groups._prototype returns self so function can be chained
|
|
||||||
|
|
||||||
@usage-- Disalow some actions
|
|
||||||
group:disallow{
|
|
||||||
'add_permission_group',
|
|
||||||
'delete_permission_group',
|
|
||||||
'import_permissions_string',
|
|
||||||
'map_editor_action',
|
|
||||||
'toggle_map_editor'
|
|
||||||
}
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups._prototype:disallow(actions)
|
|
||||||
if type(actions) ~= "table" then
|
|
||||||
actions = { actions }
|
|
||||||
end
|
|
||||||
for _, action in pairs(actions) do
|
|
||||||
self:set_action(action, false)
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- Sets the default state for any actions not given to be allowed, useful with :disallow
|
|
||||||
@treturn Permissions_Groups._prototype returns self so function can be chained
|
|
||||||
|
|
||||||
@usage-- Allow all actions unless given by disallow
|
|
||||||
group:allow_all()
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups._prototype:allow_all()
|
|
||||||
self.allow_all_actions = true
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- Sets the default state for any action not given to be disallowed, useful with :allow
|
|
||||||
@treturn Permissions_Groups._prototype returns self so function can be chained
|
|
||||||
|
|
||||||
@usage-- Disallow all actions unless given by allow
|
|
||||||
group:disallow_all()
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups._prototype:disallow_all()
|
|
||||||
self.allow_all_actions = false
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- Returns if an input action is allowed for this group
|
|
||||||
@tparam ?string|defines.input_action action the action that you want to test for
|
|
||||||
@treturn boolean true if the group is allowed the action, false other wise
|
|
||||||
|
|
||||||
@usage-- Test if a group is allowed an action
|
|
||||||
local allowed = group:is_allowed('write_to_console')
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups._prototype:is_allowed(action)
|
|
||||||
if type(action) == "string" then
|
|
||||||
action = defines.input_action[action]
|
|
||||||
end
|
|
||||||
local state = self.actions[action]
|
|
||||||
if state == nil then
|
|
||||||
state = self.allow_all_actions
|
|
||||||
end
|
|
||||||
return state
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Players.
|
|
||||||
-- Functions that control group players
|
|
||||||
-- @section players
|
|
||||||
|
|
||||||
--[[-- Creates or updates the permission group with the configured actions, used internally
|
|
||||||
@treturn LuaPermissionGroup the permission group that was created
|
|
||||||
|
|
||||||
@usage-- Create the permission group so players can be added, used internally
|
|
||||||
group:create()
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups._prototype:create()
|
|
||||||
local group = self:get_raw()
|
|
||||||
if not group then
|
|
||||||
group = game.permissions.create_group(self.name) --[[@as LuaPermissionGroup]]
|
|
||||||
end
|
|
||||||
for _, action in pairs(defines.input_action) do
|
|
||||||
group.set_allows_action(action, self:is_allowed(action))
|
|
||||||
end
|
|
||||||
|
|
||||||
return group
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- Returns the LuaPermissionGroup that was created with this group object, used internally
|
|
||||||
@treturn LuaPermissionGroup the raw lua permission group
|
|
||||||
|
|
||||||
@usage-- Get the factorio api permision group, used internally
|
|
||||||
local permission_group = group:get_raw()
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups._prototype:get_raw()
|
|
||||||
return game.permissions.get_group(self.name)
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- Adds a player to this group
|
|
||||||
@tparam LuaPlayer player LuaPlayer the player you want to add to this group can be name or index etc
|
|
||||||
@treturn boolean true if the player was added successfully, false other wise
|
|
||||||
|
|
||||||
@usage-- Add a player to this permission group
|
|
||||||
group:add_player(game.player)
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups._prototype:add_player(player)
|
|
||||||
local group = self:get_raw()
|
|
||||||
if not group or not player then return false end
|
|
||||||
add_to_permission_group_async(group, player)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- Removes a player from this group
|
|
||||||
@tparam LuaPlayer player LuaPlayer the player you want to remove from this group can be name or index etc
|
|
||||||
@treturn boolean true if the player was removed successfully, false other wise
|
|
||||||
|
|
||||||
@usage-- Remove a player from this permission group
|
|
||||||
group:remove_player(game.player)
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups._prototype:remove_player(player)
|
|
||||||
local group = self:get_raw()
|
|
||||||
if not group or not player then return false end
|
|
||||||
remove_from_permission_group_async(group, player)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- Returns all player that are in this group with the option to filter to online/offline only
|
|
||||||
@tparam[opt] boolean online if nil returns all players, if true online players only, if false returns online players only
|
|
||||||
@treturn table a table of players that are in this group; filtered if online param is given
|
|
||||||
|
|
||||||
@usage-- Get all players in this group
|
|
||||||
local online_players = group:get_players()
|
|
||||||
|
|
||||||
@usage-- Get all online players in this group
|
|
||||||
local online_players = group:get_players(true)
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups._prototype:get_players(online)
|
|
||||||
local players = {}
|
|
||||||
local group = self:get_raw()
|
|
||||||
if group then
|
|
||||||
if online == nil then
|
|
||||||
return group.players
|
|
||||||
else
|
|
||||||
for _, player in pairs(group.players) do
|
|
||||||
if player.connected == online then
|
|
||||||
table.insert(player, player)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return players
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- Prints a message to every player in this group
|
|
||||||
@tparam string message the message that you want to send to the players
|
|
||||||
@treturn number the number of players that received the message
|
|
||||||
|
|
||||||
@usage-- Print a message to all players in thie group
|
|
||||||
group:print('Hello, World!')
|
|
||||||
|
|
||||||
]]
|
|
||||||
function PermissionsGroups._prototype:print(message)
|
|
||||||
local players = self:get_players(true)
|
|
||||||
for _, player in pairs(players) do
|
|
||||||
player.print(message)
|
|
||||||
end
|
|
||||||
|
|
||||||
return #players
|
|
||||||
end
|
|
||||||
|
|
||||||
-- when the game starts it will make the permission groups
|
|
||||||
Event.on_init(function()
|
|
||||||
PermissionsGroups.reload_permissions()
|
|
||||||
end)
|
|
||||||
|
|
||||||
return PermissionsGroups
|
|
||||||
@@ -55,7 +55,7 @@ PlayerData:set_serializer(Datastore.name_serializer) -- use player name
|
|||||||
|
|
||||||
--- Store and enum for the data saving preference
|
--- Store and enum for the data saving preference
|
||||||
local DataSavingPreference = PlayerData:combine("DataSavingPreference")
|
local DataSavingPreference = PlayerData:combine("DataSavingPreference")
|
||||||
local PreferenceEnum = { "All", "Statistics", "Settings", "Required" }
|
local PreferenceEnum = { "All", "Statistics", "Settings", "Required" } --- @type table<string | number, string | number>
|
||||||
for k, v in ipairs(PreferenceEnum) do PreferenceEnum[v] = k end
|
for k, v in ipairs(PreferenceEnum) do PreferenceEnum[v] = k end
|
||||||
|
|
||||||
DataSavingPreference:set_default("All")
|
DataSavingPreference:set_default("All")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,256 @@
|
|||||||
|
# addons
|
||||||
|
[info]
|
||||||
|
players-online=There are __1__ players online
|
||||||
|
total-map-time=This map has been on for __1__
|
||||||
|
discord=Join us on our discord at: __1__
|
||||||
|
website=Please visit our website at: __1__
|
||||||
|
status=Want to check if out servers are down? Visit: __1__
|
||||||
|
github=Want to help improve our server with some extra features? Help us at: __1__
|
||||||
|
patreon=Consider supporting our server at: __1__
|
||||||
|
custom-commands=We use custom commands, such as /tag and /me, use /chelp for more info.
|
||||||
|
read-readme=Make sure you have read the information gui (It can be found through the info mark on the top left)
|
||||||
|
softmod=We run a softmod on our servers. A softmod is a custom scenario that runs on this server, an example is the player list.
|
||||||
|
redmew=We don't talk about redmew here; they beat us to 1000 members ;-;
|
||||||
|
lhd=All trains must be LHD! This is a long standing rule on our servers, please respect this.
|
||||||
|
|
||||||
|
# data
|
||||||
|
[join-message]
|
||||||
|
description-add=Sets / Gets your custom join message.
|
||||||
|
description-remove=Removes you custom join message.
|
||||||
|
arg-message=Your custom join message.
|
||||||
|
greet=[color=0,1,0] Welcome to __1__ community server! If you like the server join our discord: __2__ [/color]
|
||||||
|
message-set=Your join message has been updated.
|
||||||
|
message-get=Your join message is: __1__
|
||||||
|
message-cleared=Your join message has been cleared.
|
||||||
|
|
||||||
|
[quickbar]
|
||||||
|
saved=Your quickbar filters have been saved.
|
||||||
|
|
||||||
|
[exp-settings]
|
||||||
|
Colour=Colour
|
||||||
|
Colour-tooltip=Your player colour
|
||||||
|
Colour-value-tooltip=Change by using /color
|
||||||
|
JoinMessage=Join Message
|
||||||
|
JoinMessage-tooltip=The message displayed when you join
|
||||||
|
JoinMessage-value-tooltip=Change by using /join-message
|
||||||
|
QuickbarFilters=Quickbar Filters
|
||||||
|
QuickbarFilters-tooltip=The filters on your quickbar
|
||||||
|
QuickbarFilters-value-tooltip=Change by using /save-quickbar
|
||||||
|
UsesAlt=Alt View
|
||||||
|
UsesAlt-tooltip=Whether you use alt view when you play
|
||||||
|
UsesAlt-value-tooltip=Change by pressing __CONTROL__show-info__
|
||||||
|
UsesServerUps=Server UPS
|
||||||
|
UsesServerUps-tooltip=Whether the current server UPS is shown
|
||||||
|
UsesServerUps-value-tooltip=Change by using /server-ups
|
||||||
|
Tag=Player Tag
|
||||||
|
Tag-tooltip=The tag shown after your name
|
||||||
|
Tag-value-tooltip=Change by using /tag
|
||||||
|
TagColor=Player Tag color
|
||||||
|
TagColor-tooltip=The color of the tag shown after your name
|
||||||
|
TagColor-value-tooltip=Change by using /tag-color
|
||||||
|
Bonus=Player Bonus
|
||||||
|
Bonus-tooltip=The bonus given to your character
|
||||||
|
Bonus-value-tooltip=Change by using /bonus
|
||||||
|
HasEnabledDecon=Quick Tree Decon
|
||||||
|
ToolbarState=Toolbox
|
||||||
|
ToolbarState-tooltip=The order and favourites in your toolbox
|
||||||
|
ToolbarState-value-tooltip=This value is calculated automatically when you leave the game
|
||||||
|
|
||||||
|
[exp-statistics]
|
||||||
|
MapsPlayed=Maps Played
|
||||||
|
MapsPlayed-tooltip=The number of unique maps you have played on
|
||||||
|
JoinCount=Join Count
|
||||||
|
JoinCount-tooltip=The number of times you have joined our servers
|
||||||
|
Playtime=Playtime
|
||||||
|
Playtime-tooltip=The amount of time you have spent on our servers
|
||||||
|
AfkTime=AFK Time
|
||||||
|
AfkTime-tooltip=The amount of time you have been AFK on our servers
|
||||||
|
ChatMessages=Messages
|
||||||
|
ChatMessages-tooltip=The number of messages you have sent in chat
|
||||||
|
CommandsUsed=Commands
|
||||||
|
CommandsUsed-tooltip=The number of commands you have used
|
||||||
|
RocketsLaunched=Rockets Launched
|
||||||
|
RocketsLaunched-tooltip=The number of rockets launched while you were online
|
||||||
|
ResearchCompleted=Research Completed
|
||||||
|
ResearchCompleted-tooltip=The number of research projects completed while you were online
|
||||||
|
MachinesBuilt=Machines Built
|
||||||
|
MachinesBuilt-tooltip=The number of machines you have built
|
||||||
|
MachinesRemoved=Machines Removed
|
||||||
|
MachinesRemoved-tooltip=The number of machines you have removed
|
||||||
|
TilesBuilt=Tiles Placed
|
||||||
|
TilesBuilt-tooltip=The number of tiles you have placed
|
||||||
|
TilesRemoved=Tiles Removed
|
||||||
|
TilesRemoved-tooltip=The number of tiles you have removed
|
||||||
|
TreesDestroyed=Trees Destroyed
|
||||||
|
TreesDestroyed-tooltip=The number of trees you have destroyed
|
||||||
|
OreMined=Ore Mined
|
||||||
|
OreMined-tooltip=The amount of ore you have mined
|
||||||
|
ItemsCrafted=Items Crafted
|
||||||
|
ItemsCrafted-tooltip=The number of items you have crafted
|
||||||
|
ItemsPickedUp=Items Picked Up
|
||||||
|
ItemsPickedUp-tooltip=The number of items you have picked up
|
||||||
|
Kills=Kills
|
||||||
|
Kills-tooltip=The number of biters and biter bases you have squished
|
||||||
|
Deaths=Deaths
|
||||||
|
Deaths-tooltip=The number of times you have died
|
||||||
|
DamageDealt=Damage Dealt
|
||||||
|
DamageDealt-tooltip=The amount of damage you have dealt to other forces
|
||||||
|
DistanceTravelled=Distance Travelled
|
||||||
|
DistanceTravelled-tooltip=The total distance in tiles that you have travelled
|
||||||
|
CapsulesUsed=Capsules Used
|
||||||
|
CapsulesUsed-tooltip=The number of capsules you have used
|
||||||
|
EntityRepaired=Machines Repaired
|
||||||
|
EntityRepaired-tooltip=The number of machines which you have repaired
|
||||||
|
DeconstructionPlannerUsed=Decon Planner Used
|
||||||
|
DeconstructionPlannerUsed-tooltip=The number of times you have used the deconstruction planner
|
||||||
|
MapTagsMade=Map Tags Created
|
||||||
|
MapTagsMade-tooltip=The number of map tags you have created
|
||||||
|
DamageDeathRatio=Damage Death Ratio
|
||||||
|
DamageDeathRatio-tooltip=Damage over Death
|
||||||
|
KillDeathRatio=Kill Death Ratio
|
||||||
|
KillDeathRatio-tooltip=Kill over Death
|
||||||
|
SessionTime=Session Time
|
||||||
|
SessionTime-tooltip=Session Time
|
||||||
|
BuildRatio=Build Ratio
|
||||||
|
BuildRatio-tooltip=Build over Remove
|
||||||
|
RocketPerHour=Rocket Per Hour
|
||||||
|
RocketPerHour-tooltip=Rocket Launched Over Play Time in Hour
|
||||||
|
TreeKillPerMinute=Tree Kill Per Minute
|
||||||
|
TreeKillPerMinute-tooltip=Tree Destroyed Per Minute
|
||||||
|
NetPlayTime=Net Play Time
|
||||||
|
NetPlayTime-tooltip=Net Play Time
|
||||||
|
AFKTimeRatio=AFK Time Ratio
|
||||||
|
AFKTimeRatio-tooltip=AFK Time over Play Time
|
||||||
|
Locale=Locale
|
||||||
|
Locale-tooltip=The language of player interface
|
||||||
|
|
||||||
|
# expcore
|
||||||
|
[expcore-data]
|
||||||
|
description-preference=Allows you to set/get your data saving preference.
|
||||||
|
description-data=Writes all your player data to a file on your computer.
|
||||||
|
arg-option=One of "All", "Statistics", "Settings", "Required"
|
||||||
|
set-preference=You data saving preference has been set to __1__. Existing data will not be effected until you rejoin.
|
||||||
|
get-preference=You data saving preference is __1__. Use /set-preference to change this. Use /save-data to get a local copy of your data.
|
||||||
|
get-data=Your player data has been writen to file, location: factorio/script_output/expgaming_player_data.json
|
||||||
|
data-failed=Your player data has failed to load. Any changes to your data will not be saved.
|
||||||
|
data-restore=Your player data has been restored and changes will now save when you leave.
|
||||||
|
preference=Saving Preference
|
||||||
|
preference-tooltip=Which categories will be saved when you leave the game
|
||||||
|
preference-value-tooltip=Change by using /set-preference
|
||||||
|
preference-All=All data will be saved
|
||||||
|
preference-Statistics=Only statistics, settings, and required data will be saved
|
||||||
|
preference-Settings=Only settings and required data will be saved
|
||||||
|
preference-Required=Only required data will be saved
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
[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.
|
||||||
|
set=Your bonus has been set to __1__.
|
||||||
|
get=Your bonus is __1__.
|
||||||
|
perm=You dont have enough permission to set more than __1__.
|
||||||
|
display-fmms=Mining
|
||||||
|
display-fmms-tooltip=Force manual mining speed
|
||||||
|
display-frs=Running
|
||||||
|
display-frs-tooltip=Force running speed
|
||||||
|
display-fcs=Crafting
|
||||||
|
display-fcs-tooltip=Force crafting speed
|
||||||
|
display-fisb=Inventory
|
||||||
|
display-fisb-tooltip=Force inventory slots bonus
|
||||||
|
display-fhb=Health
|
||||||
|
display-fhb-tooltip=Force health bonus
|
||||||
|
display-frdb=Reach
|
||||||
|
display-frdb-tooltip=Force reach distance bonus
|
||||||
|
display-fwrsm=Robot Speed
|
||||||
|
display-fwrsm-tooltip=Force worker robots speed modifier
|
||||||
|
display-fwrbm=Robot Battery
|
||||||
|
display-fwrbm-tooltip=Force worker robots battery modifier
|
||||||
|
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.
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
[links]
|
|
||||||
discord=https://discord.explosivegaming.nl
|
|
||||||
website=https://www.explosivegaming.nl
|
|
||||||
status=https://status.explosivegaming.nl
|
|
||||||
github=https://github.com/explosivegaming/ExpCluster
|
|
||||||
patreon=https://www.patreon.com/ExpGaming
|
|
||||||
|
|
||||||
[info]
|
|
||||||
players-online=There are __1__ players online
|
|
||||||
total-map-time=This map has been on for __1__
|
|
||||||
discord=Join us on our discord at: https://discord.explosivegaming.nl
|
|
||||||
website=Please visit our website at: https://www.explosivegaming.nl
|
|
||||||
status=Want to check if out servers are down? Visit: https://status.explosivegaming.nl
|
|
||||||
github=Want to help improve our server with some extra features? Help us at: https://github.com/explosivegaming/ExpCluster
|
|
||||||
patreon=Consider supporting our server at: https://www.patreon.com/ExpGaming
|
|
||||||
custom-commands=We use custom commands, such as /tag and /me, use /chelp for more info.
|
|
||||||
read-readme=Make sure you have read the information gui (It can be found through the info mark on the top left)
|
|
||||||
softmod=We run a softmod on our servers. A softmod is a custom scenario that runs on this server, an example is the player list.
|
|
||||||
redmew=We don't talk about redmew here; they beat us to 1000 members ;-;
|
|
||||||
lhd=All trains must be LHD! This is a long standing rule on our servers, please respect this.
|
|
||||||
|
|
||||||
[warnings]
|
|
||||||
received=You received a warning from __1__. You have __2__ warnings. __3__
|
|
||||||
pre-kick=This is your last warning before you are kicked.
|
|
||||||
kick=You were kicked for having too many warnings; you may rejoin if you wish.
|
|
||||||
pre-pre-ban=You are close to receiving a ban; successful ban appeals are unlikely.
|
|
||||||
pre-ban=This your LAST warning before you are BANNED! Successful ban appeals are unlikely.
|
|
||||||
ban=You were banned for having too many warnings; visit __1__ to request a ban appeal.
|
|
||||||
script-warning=You are receiving script warnings; if you recive too many you will receive a permanent warning (__1__/__2__)
|
|
||||||
script-warning-removed=A script warning has expired (__1__/__2__)
|
|
||||||
script-warning-limit=__1__ has received a permanent warning from the script.
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
[join-message]
|
|
||||||
description-add=Sets / Gets your custom join message.
|
|
||||||
description-remove=Removes you custom join message.
|
|
||||||
arg-message=Your custom join message.
|
|
||||||
greet=[color=0,1,0] Welcome to explosive gaming community server! If you like the server join our discord: __1__ [/color]
|
|
||||||
message-set=Your join message has been updated.
|
|
||||||
message-get=Your join message is: __1__
|
|
||||||
message-cleared=Your join message has been cleared.
|
|
||||||
|
|
||||||
[quickbar]
|
|
||||||
saved=Your quickbar filters have been saved.
|
|
||||||
|
|
||||||
[exp-required]
|
|
||||||
Warnings=Warnings
|
|
||||||
Warnings-tooltip=The total number of warnings you have received from staff
|
|
||||||
Warnings-value-tooltip=The total number of warnings you have received from staff
|
|
||||||
|
|
||||||
[exp-settings]
|
|
||||||
Colour=Colour
|
|
||||||
Colour-tooltip=Your player colour
|
|
||||||
Colour-value-tooltip=Change by using /color
|
|
||||||
JoinMessage=Join Message
|
|
||||||
JoinMessage-tooltip=The message displayed when you join
|
|
||||||
JoinMessage-value-tooltip=Change by using /join-message
|
|
||||||
QuickbarFilters=Quickbar Filters
|
|
||||||
QuickbarFilters-tooltip=The filters on your quickbar
|
|
||||||
QuickbarFilters-value-tooltip=Change by using /save-quickbar
|
|
||||||
UsesAlt=Alt View
|
|
||||||
UsesAlt-tooltip=Whether you use alt view when you play
|
|
||||||
UsesAlt-value-tooltip=Change by pressing __CONTROL__show-info__
|
|
||||||
UsesServerUps=Server UPS
|
|
||||||
UsesServerUps-tooltip=Whether the current server UPS is shown
|
|
||||||
UsesServerUps-value-tooltip=Change by using /server-ups
|
|
||||||
Tag=Player Tag
|
|
||||||
Tag-tooltip=The tag shown after your name
|
|
||||||
Tag-value-tooltip=Change by using /tag
|
|
||||||
TagColor=Player Tag color
|
|
||||||
TagColor-tooltip=The color of the tag shown after your name
|
|
||||||
TagColor-value-tooltip=Change by using /tag-color
|
|
||||||
Bonus=Player Bonus
|
|
||||||
Bonus-tooltip=The bonus given to your character
|
|
||||||
Bonus-value-tooltip=Change by using /bonus
|
|
||||||
HasEnabledDecon=Quick Tree Decon
|
|
||||||
ToolbarState=Toolbox
|
|
||||||
ToolbarState-tooltip=The order and favourites in your toolbox
|
|
||||||
ToolbarState-value-tooltip=This value is calculated automatically when you leave the game
|
|
||||||
|
|
||||||
[exp-statistics]
|
|
||||||
MapsPlayed=Maps Played
|
|
||||||
MapsPlayed-tooltip=The number of unique maps you have played on
|
|
||||||
JoinCount=Join Count
|
|
||||||
JoinCount-tooltip=The number of times you have joined our servers
|
|
||||||
Playtime=Playtime
|
|
||||||
Playtime-tooltip=The amount of time you have spent on our servers
|
|
||||||
AfkTime=AFK Time
|
|
||||||
AfkTime-tooltip=The amount of time you have been AFK on our servers
|
|
||||||
ChatMessages=Messages
|
|
||||||
ChatMessages-tooltip=The number of messages you have sent in chat
|
|
||||||
CommandsUsed=Commands
|
|
||||||
CommandsUsed-tooltip=The number of commands you have used
|
|
||||||
RocketsLaunched=Rockets Launched
|
|
||||||
RocketsLaunched-tooltip=The number of rockets launched while you were online
|
|
||||||
ResearchCompleted=Research Completed
|
|
||||||
ResearchCompleted-tooltip=The number of research projects completed while you were online
|
|
||||||
MachinesBuilt=Machines Built
|
|
||||||
MachinesBuilt-tooltip=The number of machines you have built
|
|
||||||
MachinesRemoved=Machines Removed
|
|
||||||
MachinesRemoved-tooltip=The number of machines you have removed
|
|
||||||
TilesBuilt=Tiles Placed
|
|
||||||
TilesBuilt-tooltip=The number of tiles you have placed
|
|
||||||
TilesRemoved=Tiles Removed
|
|
||||||
TilesRemoved-tooltip=The number of tiles you have removed
|
|
||||||
TreesDestroyed=Trees Destroyed
|
|
||||||
TreesDestroyed-tooltip=The number of trees you have destroyed
|
|
||||||
OreMined=Ore Mined
|
|
||||||
OreMined-tooltip=The amount of ore you have mined
|
|
||||||
ItemsCrafted=Items Crafted
|
|
||||||
ItemsCrafted-tooltip=The number of items you have crafted
|
|
||||||
ItemsPickedUp=Items Picked Up
|
|
||||||
ItemsPickedUp-tooltip=The number of items you have picked up
|
|
||||||
Kills=Kills
|
|
||||||
Kills-tooltip=The number of biters and biter bases you have squished
|
|
||||||
Deaths=Deaths
|
|
||||||
Deaths-tooltip=The number of times you have died
|
|
||||||
DamageDealt=Damage Dealt
|
|
||||||
DamageDealt-tooltip=The amount of damage you have dealt to other forces
|
|
||||||
DistanceTravelled=Distance Travelled
|
|
||||||
DistanceTravelled-tooltip=The total distance in tiles that you have travelled
|
|
||||||
CapsulesUsed=Capsules Used
|
|
||||||
CapsulesUsed-tooltip=The number of capsules you have used
|
|
||||||
EntityRepaired=Machines Repaired
|
|
||||||
EntityRepaired-tooltip=The number of machines which you have repaired
|
|
||||||
DeconstructionPlannerUsed=Decon Planner Used
|
|
||||||
DeconstructionPlannerUsed-tooltip=The number of times you have used the deconstruction planner
|
|
||||||
MapTagsMade=Map Tags Created
|
|
||||||
MapTagsMade-tooltip=The number of map tags you have created
|
|
||||||
DamageDeathRatio=Damage Death Ratio
|
|
||||||
DamageDeathRatio-tooltip=Damage over Death
|
|
||||||
KillDeathRatio=Kill Death Ratio
|
|
||||||
KillDeathRatio-tooltip=Kill over Death
|
|
||||||
SessionTime=Session Time
|
|
||||||
SessionTime-tooltip=Session Time
|
|
||||||
BuildRatio=Build Ratio
|
|
||||||
BuildRatio-tooltip=Build over Remove
|
|
||||||
RocketPerHour=Rocket Per Hour
|
|
||||||
RocketPerHour-tooltip=Rocket Launched Over Play Time in Hour
|
|
||||||
TreeKillPerMinute=Tree Kill Per Minute
|
|
||||||
TreeKillPerMinute-tooltip=Tree Destroyed Per Minute
|
|
||||||
NetPlayTime=Net Play Time
|
|
||||||
NetPlayTime-tooltip=Net Play Time
|
|
||||||
AFKTimeRatio=AFK Time Ratio
|
|
||||||
AFKTimeRatio-tooltip=AFK Time over Play Time
|
|
||||||
Locale=Locale
|
|
||||||
Locale-tooltip=The language of player interface
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
[expcore-roles]
|
|
||||||
error-log-format-flag=[ERROR] roleFlag/__1__ :: __2__
|
|
||||||
error-log-format-assign=[ERROR] rolePromote/__1__ :: __2__
|
|
||||||
game-message-assign=__1__ has been assigned to __2__ by __3__
|
|
||||||
game-message-unassign=__1__ has been unassigned from __2__ by __3__
|
|
||||||
reject-role=Invalid Role Name.
|
|
||||||
reject-player-role=Player has a higher role.
|
|
||||||
|
|
||||||
[expcore-data]
|
|
||||||
description-preference=Allows you to set/get your data saving preference.
|
|
||||||
description-data=Writes all your player data to a file on your computer.
|
|
||||||
arg-option=One of "All", "Statistics", "Settings", "Required"
|
|
||||||
set-preference=You data saving preference has been set to __1__. Existing data will not be effected until you rejoin.
|
|
||||||
get-preference=You data saving preference is __1__. Use /set-preference to change this. Use /save-data to get a local copy of your data.
|
|
||||||
get-data=Your player data has been writen to file, location: factorio/script_output/expgaming_player_data.json
|
|
||||||
data-failed=Your player data has failed to load. Any changes to your data will not be saved.
|
|
||||||
data-restore=Your player data has been restored and changes will now save when you leave.
|
|
||||||
preference=Saving Preference
|
|
||||||
preference-tooltip=Which categories will be saved when you leave the game
|
|
||||||
preference-value-tooltip=Change by using /set-preference
|
|
||||||
preference-All=All data will be saved
|
|
||||||
preference-Statistics=Only statistics, settings, and required data will be saved
|
|
||||||
preference-Settings=Only settings and required data will be saved
|
|
||||||
preference-Required=Only required data will be saved
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
[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
|
|
||||||
warn-player=Warn 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
|
|
||||||
|
|
||||||
[rocket-info]
|
|
||||||
main-tooltip=Rocket Info
|
|
||||||
launch-tooltip=Launch rocket
|
|
||||||
launch-tooltip-disabled=Launch rocket (not ready)
|
|
||||||
toggle-rocket-tooltip=Disable auto launch
|
|
||||||
toggle-rocket-tooltip-disabled=Enable auto launch
|
|
||||||
toggle-section-tooltip=Expand Section
|
|
||||||
toggle-section-collapse-tooltip=Collapse Section
|
|
||||||
section-caption-stats=Statistics
|
|
||||||
section-tooltip-stats=Statistics about how rockets are launched
|
|
||||||
section-caption-milestones=Milestones
|
|
||||||
section-tooltip-milestones=The times when milestones were met
|
|
||||||
section-caption-progress=Build Progress
|
|
||||||
section-tooltip-progress=The progress for your rocket silos
|
|
||||||
progress-no-silos=You have no rocket silos
|
|
||||||
data-caption-first-launch=First Launch
|
|
||||||
data-tooltip-first-launch=The time of the first launch
|
|
||||||
data-caption-last-launch=Last Launch
|
|
||||||
data-tooltip-last-launch=The time of the last launch
|
|
||||||
data-caption-fastest-launch=Fastest Launch
|
|
||||||
data-tooltip-fastest-launch=The time taken for the fastest launch
|
|
||||||
data-caption-total-rockets=Total Launched
|
|
||||||
data-tooltip-total-rockets=Total number of rockets launched by your force
|
|
||||||
value-tooltip-total-rockets=__1__% of all rockets on this map
|
|
||||||
data-caption-avg-launch=Average Time
|
|
||||||
data-tooltip-avg-launch=The average amount of time taken to launch a rocket
|
|
||||||
data-caption-avg-launch-n=Average Time __1__
|
|
||||||
data-tooltip-avg-launch-n=The average amount of time taken to launch the last __1__ rockets
|
|
||||||
data-caption-milestone-n=Milestone __1__
|
|
||||||
data-tooltip-milestone-n=Time taken to each __1__ rockets
|
|
||||||
data-caption-milestone-next=N/A
|
|
||||||
data-tooltip-milestone-next=Not yet achieved
|
|
||||||
progress-x-pos=X __1__
|
|
||||||
progress-y-pos=Y __1__
|
|
||||||
progress-label-tooltip=View on map
|
|
||||||
progress-launched=Launched
|
|
||||||
progress-caption=__1__%
|
|
||||||
progress-tooltip=This silo has launched __1__ rockets
|
|
||||||
launch-failed=Failed to launch rocket, please wait a few seconds and try again.
|
|
||||||
|
|
||||||
[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
|
|
||||||
|
|
||||||
[readme]
|
|
||||||
main-tooltip=Infomation
|
|
||||||
welcome-tab=Welcome
|
|
||||||
welcome-tooltip=Welcome to Explosive Gaming
|
|
||||||
welcome-general=Welcome to Explosive Gaming; we host many factorio servers. While you are here, we ask you to follow our rules. You can find these in the tab above. You can also find our custom commands and links to our other servers. This map has been online for __2__.\nPlease note that our servers reset periodically, the next reset is: __1__
|
|
||||||
welcome-roles=We run a custom role system to help protect the work of others. As a result you may not be able to use your deconstruction planner yet or drop item on the groud. Roles also give you access to some custom features such as adding tasks to our task list or making new warp points.\nYou have been assigned the roles: __1__
|
|
||||||
welcome-chat=Chatting can be difficult for new players because it’s different than other games! It’s very simple, the button you need to press is the “GRAVE/TILDE” key (which is located under the “ESC key”) - If you would like to change the key, go to your Controls tab in options.\nThe setting you need to change is “Toggle chat (and Lua console)” you currently have it set to "__CONTROL__toggle-console__"
|
|
||||||
rules-tab=Rules
|
|
||||||
rules-tooltip=Rules for our server
|
|
||||||
rules-general=By playing on our servers you accept the rules below. If you are supected of breaking a rule then you will be questioned by one of our moderators. If required you will be banned from our servers, appeals can be made by contacting an Administrator on our discord.
|
|
||||||
rules-1=Hacking / cheating / abusing bugs will not be tolerated.
|
|
||||||
rules-2=Any bugs or exploits found should be reported to our staff members.
|
|
||||||
rules-3=Be respectful to other players in chat, this includes any other forms of communication.
|
|
||||||
rules-4=Taking all items from a belt or logistics network is forbidden: sharing resources is mandatory.
|
|
||||||
rules-5=Spamming of chat, bots, unlimited chests, or concrete is not allowed.
|
|
||||||
rules-6=Before removing major parts of the factory tell other players why you are removing it.
|
|
||||||
rules-7=Do not cause unnecessary lag by placing/removing tiles with bots or using active provider chests, if think some thing might cause lag please ask staff first.
|
|
||||||
rules-8=Do not walk in random directions with no reason, this is to reduce map download times.
|
|
||||||
rules-9=Do not use speakers on global or with alerts without prior permission, no one wants to hear your music at full volume without knowing where the off switch is.
|
|
||||||
rules-10=Do not rotate belts, deactivate belts with wires, or cause production to stop without good reason, this goes for inserters and spliters as well.
|
|
||||||
rules-11=Do not make train roundabouts. Other loops such as RoRo stations are allowed.
|
|
||||||
rules-12=When using trains, use the same size other players have used, many players use 1-2-1, 2-4-2, or 3-8-3, meaning 1 engine 2 cargo 1 engine.
|
|
||||||
rules-13=Trains are Left Hand Drive (LHD) only, this means trains drive on the left side of the tracks.
|
|
||||||
rules-14=Do not beg for roles, advertise other servers, or link to harmful sites.
|
|
||||||
rules-15=Use common sense, report rule breakers, and Administrators have the final word.
|
|
||||||
commands-tab=Commands
|
|
||||||
commands-tooltip=Commands which you are able to use
|
|
||||||
commands-general=We have lots of custom commands which you are able to use. Below you can find a list of all the commands that you are allowed to use and what they do. If you need more information or want to search for a command you can use our /search-help command.
|
|
||||||
servers-tab=Servers
|
|
||||||
servers-tooltip=Links to our other servers and sites
|
|
||||||
servers-general=This is only one of our servers for factorio, we host many of others as well. Below you can find details about all the servers that we host as well as links to our external services such as discord or github.
|
|
||||||
servers-factorio=Factorio Servers
|
|
||||||
servers-1=S1 Public
|
|
||||||
servers-d1=This is our 48 hour reset server.
|
|
||||||
servers-2=S2 Off
|
|
||||||
servers-d2=This server is closed.
|
|
||||||
servers-3=S3 Weekly
|
|
||||||
servers-d3=This is our weekly reset server.
|
|
||||||
servers-4=S4 Monthly
|
|
||||||
servers-d4=This is our monthly reset server.
|
|
||||||
servers-5=S5 Modded
|
|
||||||
servers-d5=This is our modded server, see discord for details.
|
|
||||||
servers-6=S6 Donator
|
|
||||||
servers-d6=This is our donator only server, online when requested.
|
|
||||||
servers-7=S7 Event
|
|
||||||
servers-d7=This is our event server, we try to run events at least once per week.
|
|
||||||
servers-8=S8 T̷-̶s̶-̴:̷
|
|
||||||
servers-d8=N̵o̴ ̶o̷-̶e̵ ̴k̸n̷-̶w̵s̸ ̴w̷h̷a̶-̶ ̷h̴a̴p̷p̴e̷n̷s̸ ̷o̶n̴ ̷t̶h̴-̶s̶ ̷s̷e̶r̸v̸e̴r̷,̶ ̸i̸t̴ ̷m̶-̸g̴h̶t̷ ̸n̸-̶t̵ ̷e̴v̸e̸n̶t̷ ̵-̷x̴i̵s̶t̸.̸
|
|
||||||
servers-connect-Offline=Server is currently offline
|
|
||||||
servers-connect-Current=This is your current server
|
|
||||||
servers-connect-Version=Server is on a different version: __1__
|
|
||||||
servers-connect-Password=Server requires a password
|
|
||||||
servers-connect-Modded=Server requires mods to be downloaded
|
|
||||||
servers-connect-Online=Server is online
|
|
||||||
servers-external=External Links
|
|
||||||
servers-open-in-browser=Open in your browser
|
|
||||||
backers-tab=Backers
|
|
||||||
backers-tooltip=People who have helped make our server
|
|
||||||
backers-general=We would like to thank all our staff and backers who have helped our community grow. Our staff have helped to keep our servers safe from trolls and a fun place to play. Our backers have helped us to cover our running costs and provide a great community for us all to enjoy together.
|
|
||||||
backers-management=Administrators
|
|
||||||
backers-board=Board Members and Senior Backers
|
|
||||||
backers-staff=Staff Members
|
|
||||||
backers-backers=Sponsors and Supporters
|
|
||||||
backers-active=Active Players
|
|
||||||
data-tab=Data
|
|
||||||
data-tooltip=All of your stored player data
|
|
||||||
data-general=Our servers will save your player data so that we can sync it between servers. All of your data can be found below, if you wish to save a copy locally then use /save-data. If you want to change what data is saved then use /set-preference.
|
|
||||||
data-settings=Settings
|
|
||||||
data-statistics=Statistics
|
|
||||||
data-required=Required
|
|
||||||
data-misc=Miscellaneous
|
|
||||||
data-format=__1____2__
|
|
||||||
|
|
||||||
[bonus]
|
|
||||||
description=Get / Set the amount of bonus you receive.
|
|
||||||
arg-amount=Amount to set your bonus to, 0 will disable bonus.
|
|
||||||
set=Your bonus has been set to __1__.
|
|
||||||
get=Your bonus is __1__.
|
|
||||||
perm=You dont have enough permission to set more than __1__.
|
|
||||||
display-fmms=Mining
|
|
||||||
display-fmms-tooltip=Force manual mining speed
|
|
||||||
display-frs=Running
|
|
||||||
display-frs-tooltip=Force running speed
|
|
||||||
display-fcs=Crafting
|
|
||||||
display-fcs-tooltip=Force crafting speed
|
|
||||||
display-fisb=Inventory
|
|
||||||
display-fisb-tooltip=Force inventory slots bonus
|
|
||||||
display-fhb=Health
|
|
||||||
display-fhb-tooltip=Force health bonus
|
|
||||||
display-frdb=Reach
|
|
||||||
display-frdb-tooltip=Force reach distance bonus
|
|
||||||
display-fwrsm=Robot Speed
|
|
||||||
display-fwrsm-tooltip=Force worker robots speed modifier
|
|
||||||
display-fwrbm=Robot Battery
|
|
||||||
display-fwrbm-tooltip=Force worker robots battery modifier
|
|
||||||
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 GUI
|
|
||||||
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.
|
|
||||||
enter=Entered vlayer selection mode.
|
|
||||||
exit=Exited vlayer selection mode.
|
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
# addons
|
||||||
|
[info]
|
||||||
|
players-online=現在有 __1__ 人在線
|
||||||
|
total-map-time=地圖時間為 __1__
|
||||||
|
discord=加入社群: __1__
|
||||||
|
website=訪問網站: __1__
|
||||||
|
status=伺服器狀態: __1__
|
||||||
|
github=增加功能或回報錯誤: __1__
|
||||||
|
patreon=支持我們: __1__
|
||||||
|
custom-commands=這裹使用了自制指令,如 /tag 和 /me。可使用 /chelp 查看更多。
|
||||||
|
read-readme=確保你已閱讀相關資訊。按左上 i 圖標可再次查看
|
||||||
|
softmod=這裹用了自設情境,是一種軟裝模組。
|
||||||
|
redmew=We don't talk about redmew here; they beat us to 1000 members ;-;
|
||||||
|
lhd=列車必須是左則通行。這是本服務器長久以來的規則。
|
||||||
|
|
||||||
|
# data
|
||||||
|
[join-message]
|
||||||
|
description-add=設定 / 取得你的加入信息。
|
||||||
|
description-remove=移除你的加入信息。
|
||||||
|
arg-message=你的加入信息。
|
||||||
|
greet=[color=0,1,0] 歡迎來到 __1__ 的服務器! 若果你喜歡本服務器可加入 Discord: __2__ [/color]
|
||||||
|
message-set=你的加入信息已更新。
|
||||||
|
message-get=你的加入信息為: __1__
|
||||||
|
message-cleared=你的加入信息已清除。
|
||||||
|
|
||||||
|
[quickbar]
|
||||||
|
saved=你的工具列已儲存。
|
||||||
|
|
||||||
|
[exp-settings]
|
||||||
|
Colour=顏色
|
||||||
|
Colour-tooltip=你的人物顏色
|
||||||
|
Colour-value-tooltip=使用 /color 來更換你的人物顏色
|
||||||
|
JoinMessage=加入信息
|
||||||
|
JoinMessage-tooltip=你加入時所顯示的信息
|
||||||
|
JoinMessage-value-tooltip=使用 /join-message 來更換加入信息
|
||||||
|
QuickbarFilters=快捷欄
|
||||||
|
QuickbarFilters-tooltip=快捷欄的選項
|
||||||
|
QuickbarFilters-value-tooltip=使用 /save-quickbar 來更換快捷欄的選項
|
||||||
|
UsesAlt=細節模式
|
||||||
|
UsesAlt-tooltip=你加入時是否使用細節模式
|
||||||
|
UsesAlt-value-tooltip=按下 __CONTROL__show-info__ 來更改
|
||||||
|
UsesServerUps=服務器更新數 (SUPS)
|
||||||
|
UsesServerUps-tooltip=現在是否顯示服務器更新數 (SUPS)
|
||||||
|
UsesServerUps-value-tooltip=使用 /server-ups 來更換顯示
|
||||||
|
Tag=你的人物標籤
|
||||||
|
Tag-tooltip=你的人物標籤
|
||||||
|
Tag-value-tooltip=使用 /tag 來更換你的人物標籤
|
||||||
|
TagColor=人物標籤顏色
|
||||||
|
TagColor-tooltip=你的人物標籤顏色
|
||||||
|
TagColor-value-tooltip=使用 /tag-color 來更換你的人物標籤顏色
|
||||||
|
Bonus=人物附加屬性
|
||||||
|
Bonus-tooltip=你的人物附加屬性
|
||||||
|
Bonus-value-tooltip=使用 /bonus 來更改
|
||||||
|
HasEnabledDecon=快速拆除樹木
|
||||||
|
ToolbarState=工具箱
|
||||||
|
ToolbarState-tooltip=次序和喜愛項目
|
||||||
|
ToolbarState-value-tooltip=此值在離開時自動計算
|
||||||
|
|
||||||
|
[exp-statistics]
|
||||||
|
MapsPlayed=地圖遊玩次數
|
||||||
|
MapsPlayed-tooltip=你在本服務器遊玩過的地圖數
|
||||||
|
JoinCount=登入次數
|
||||||
|
JoinCount-tooltip=你在本服務器遊玩的次數
|
||||||
|
Playtime=遊玩時間
|
||||||
|
Playtime-tooltip=你在本服務器遊玩的時間
|
||||||
|
AfkTime=掛機時間
|
||||||
|
AfkTime-tooltip=你在本服務器掛機的時間
|
||||||
|
ChatMessages=傳送信息次數
|
||||||
|
ChatMessages-tooltip=你在本服務器發送信息的次數
|
||||||
|
CommandsUsed=使用指令次數
|
||||||
|
CommandsUsed-tooltip=你在本服務器使用指令的次數
|
||||||
|
RocketsLaunched=火箭發射次數
|
||||||
|
RocketsLaunched-tooltip=你在本服務器在線時所發射過火箭的次數
|
||||||
|
ResearchCompleted=研究次數
|
||||||
|
ResearchCompleted-tooltip=你在本服務器在線時完成研究的次數
|
||||||
|
MachinesBuilt=機器建造次數
|
||||||
|
MachinesBuilt-tooltip=你在本服務器建造過機器的次數
|
||||||
|
MachinesRemoved=機器拆除次數
|
||||||
|
MachinesRemoved-tooltip=你在本服務器拆除過機器的次數
|
||||||
|
TilesBuilt=地磚建造次數
|
||||||
|
TilesBuilt-tooltip=你在本服務器建造過地磚的次數
|
||||||
|
TilesRemoved=地磚拆除次數
|
||||||
|
TilesRemoved-tooltip=你在本服務器拆除過地磚的次數
|
||||||
|
TreesDestroyed=樹木拆除次數
|
||||||
|
TreesDestroyed-tooltip=你在本服務器拆除過樹木的次數
|
||||||
|
OreMined=挖礦次數
|
||||||
|
OreMined-tooltip=你在本服務器挖掘礦的次數
|
||||||
|
ItemsCrafted=物品合成數
|
||||||
|
ItemsCrafted-tooltip=你在本服務器合成過物品的次數
|
||||||
|
ItemsPickedUp=物品拾起數
|
||||||
|
ItemsPickedUp-tooltip=你在本服務器起過物品的次數
|
||||||
|
Kills=撃殺數
|
||||||
|
Kills-tooltip=你在本服務器殺過的敵人數量
|
||||||
|
Deaths=死亡次數
|
||||||
|
Deaths-tooltip=你在本服務器死過的數量
|
||||||
|
DamageDealt=傷害量
|
||||||
|
DamageDealt-tooltip=你在本伺服器對敵人所造成的傷害量
|
||||||
|
DistanceTravelled=移動距離
|
||||||
|
DistanceTravelled-tooltip=你在本服務器的總移動距離
|
||||||
|
CapsulesUsed=膠囊使用次數
|
||||||
|
CapsulesUsed-tooltip=你在本服務器使用過膠囊的次數
|
||||||
|
EntityRepaired=機器維修次數
|
||||||
|
EntityRepaired-tooltip=你在本服務器維修過機器的次數
|
||||||
|
DeconstructionPlannerUsed=拆除規劃器使用次數
|
||||||
|
DeconstructionPlannerUsed-tooltip=你在本服務器使用過拆除規劃器的次數
|
||||||
|
MapTagsMade=地圖標籤數量
|
||||||
|
MapTagsMade-tooltip=你在本服務器放過的地圖標籤數量
|
||||||
|
DamageDeathRatio=傷害死亡比
|
||||||
|
DamageDeathRatio-tooltip=傷害除以死亡
|
||||||
|
KillDeathRatio=撃殺死亡比
|
||||||
|
KillDeathRatio-tooltip=撃殺除以死亡
|
||||||
|
SessionTime=平均遊玩時間
|
||||||
|
SessionTime-tooltip=平均遊玩時間
|
||||||
|
BuildRatio=建築比
|
||||||
|
BuildRatio-tooltip=建築次數除以拆除次數
|
||||||
|
RocketPerHour=每小時火箭發射次數
|
||||||
|
RocketPerHour-tooltip=火箭發射次數除以遊玩時間
|
||||||
|
TreeKillPerMinute=每分鐘樹木拆除數
|
||||||
|
TreeKillPerMinute-tooltip=樹木拆除數除以遊玩時間
|
||||||
|
NetPlayTime=淨遊玩時間
|
||||||
|
NetPlayTime-tooltip=遊玩時間減掛機時間
|
||||||
|
AFKTimeRatio=掛機時間比
|
||||||
|
AFKTimeRatio-tooltip=掛機時間除以遊玩時間
|
||||||
|
Locale=語言
|
||||||
|
Locale-tooltip=用戶介面語言
|
||||||
|
|
||||||
|
# expcore
|
||||||
|
[expcore-data]
|
||||||
|
description-preference=允許寫入資料。
|
||||||
|
description-data=把用戶資料寫入電腦。
|
||||||
|
arg-option="All", "Statistics", "Settings", "Required" 之中
|
||||||
|
set-preference=你的喜好已設定為 __1__ 。在你重新加入前將不會有任何改動。
|
||||||
|
get-preference=你的喜好為 __1__ 。使用 /set-preference 來更改,使用 /save-data 來獲得一份資料副本。
|
||||||
|
get-data=你的個人資料已寫入: factorio/script_output/expgaming_player_data.json
|
||||||
|
data-failed=無法載入你的個人資料,任何改動將不會保存。
|
||||||
|
data-restore=已載入你的個人資料,所有改動將會被保存。
|
||||||
|
preference=儲存喜好
|
||||||
|
preference-tooltip=那種資料將在退出時保存
|
||||||
|
preference-value-tooltip=使用 /set-preference 來更換儲存喜好
|
||||||
|
preference-All=所有資料都會被儲存
|
||||||
|
preference-Statistics=只有數據, 設定和資料會被儲存
|
||||||
|
preference-Settings=只有設定和資料會被儲存
|
||||||
|
preference-Required=只有需要的資料會被儲存
|
||||||
|
|
||||||
|
# gui
|
||||||
|
[player-list]
|
||||||
|
main-tooltip=用戶名單
|
||||||
|
open-action-bar=選項
|
||||||
|
close-action-bar=關閉選項
|
||||||
|
reason-confirm=確認原因
|
||||||
|
reason-entry=輸入原因
|
||||||
|
goto-player=傳送到用戶
|
||||||
|
bring-player=傳送用戶到自己
|
||||||
|
report-player=舉報用戶
|
||||||
|
jail-player=監禁用戶
|
||||||
|
kick-player=踢除用戶
|
||||||
|
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 來停用。
|
||||||
|
set=你的 Bonus 已設為 __1__。
|
||||||
|
get=你的 Bonus 為 __1__。
|
||||||
|
perm=你沒有足夠權限設多過 __1__。
|
||||||
|
display-fmms=挖掘速度
|
||||||
|
display-fmms-tooltip=勢力挖掘速度
|
||||||
|
display-frs=跑步速度
|
||||||
|
display-frs-tooltip=勢力跑步速度
|
||||||
|
display-fcs=合成速度
|
||||||
|
display-fcs-tooltip=勢力合成速度
|
||||||
|
display-fisb=儲存位
|
||||||
|
display-fisb-tooltip=勢力儲存位
|
||||||
|
display-fhb=生命
|
||||||
|
display-fhb-tooltip=勢力生命
|
||||||
|
display-frdb=到達距離
|
||||||
|
display-frdb-tooltip=勢力到達距離
|
||||||
|
display-fwrsm=機械人速度
|
||||||
|
display-fwrsm-tooltip=勢力機械人速度
|
||||||
|
display-fwrbm=機械人電力
|
||||||
|
display-fwrbm-tooltip=勢力機械人電力
|
||||||
|
display-fwrsb=械人儲存
|
||||||
|
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選上來設定喜好
|
||||||
|
reset=重設
|
||||||
|
toggle=啟用喜好
|
||||||
|
move-up=向上
|
||||||
|
move-down=向下
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
[links]
|
|
||||||
discord=https://discord.explosivegaming.nl
|
|
||||||
website=https://www.explosivegaming.nl
|
|
||||||
status=https://status.explosivegaming.nl
|
|
||||||
github=https://github.com/explosivegaming/ExpCluster
|
|
||||||
patreon=https://www.patreon.com/ExpGaming
|
|
||||||
|
|
||||||
[info]
|
|
||||||
players-online=現在有 __1__ 人在線
|
|
||||||
total-map-time=地圖時間為 __1__
|
|
||||||
discord=加入社群: https://discord.explosivegaming.nl
|
|
||||||
website=訪問網站: https://www.explosivegaming.nl
|
|
||||||
status=伺服器狀態: https://status.explosivegaming.nl
|
|
||||||
github=增加功能或回報錯誤: https://github.com/explosivegaming/ExpCluster
|
|
||||||
patreon=支持我們: https://www.patreon.com/ExpGaming
|
|
||||||
custom-commands=這裹使用了自制指令,如 /tag 和 /me。可使用 /chelp 查看更多。
|
|
||||||
read-readme=確保你已閱讀相關資訊。按左上 i 圖標可再次查看。
|
|
||||||
softmod=這裹用了自設情境,是一種軟裝模組。
|
|
||||||
redmew=
|
|
||||||
lhd=列車必須是左則通行。這是本服務器長久以來的規則。
|
|
||||||
|
|
||||||
[warnings]
|
|
||||||
received=你已被 __1__ 警告了,現在共有 __2__ 個警告。 __3__
|
|
||||||
pre-kick=這是在被踢離之前的最後警告。
|
|
||||||
kick=你已因為被警告太多次而被請離,不過你之後還是可以回來的。
|
|
||||||
pre-pre-ban=按照你的行為,你有可能會被封禁。
|
|
||||||
pre-ban=這是在被封禁之前的最後警告。
|
|
||||||
ban=你已因為被警告太多次而被封禁; 可以到 __1__ 申訴.
|
|
||||||
script-warning=這是系統發出的自動警告 (__1__/__2__)
|
|
||||||
script-warning-removed=系統發出的自動警告已失效 (__1__/__2__)
|
|
||||||
script-warning-limit=__1__ 已被系統發出了一則自動警告。
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
[join-message]
|
|
||||||
description-add=設定 / 取得你的加入信息。
|
|
||||||
description-remove=移除你的加入信息。
|
|
||||||
arg-message=你的加入信息。
|
|
||||||
greet=[color=0,1,0] 歡迎來到 EXP 的服務器! 若果你喜歡本服務器可加入 DISCORD: __1__ [/color]
|
|
||||||
message-set=你的加入信息已更新。
|
|
||||||
message-get=你的加入信息為: __1__
|
|
||||||
message-cleared=你的加入信息已清除。
|
|
||||||
|
|
||||||
[quickbar]
|
|
||||||
saved=你的工具列已儲存。
|
|
||||||
|
|
||||||
[exp-required]
|
|
||||||
Warnings=警告
|
|
||||||
Warnings-tooltip=你所有收到的警告
|
|
||||||
Warnings-value-tooltip=你所有收到的警告
|
|
||||||
|
|
||||||
[exp-settings]
|
|
||||||
Colour=顏色
|
|
||||||
Colour-tooltip=你的人物顏色
|
|
||||||
Colour-value-tooltip=使用 /color 來更換你的人物顏色
|
|
||||||
JoinMessage=加入信息
|
|
||||||
JoinMessage-tooltip=你加入時所顯示的信息
|
|
||||||
JoinMessage-value-tooltip=使用 /join-message 來更換加入信息
|
|
||||||
QuickbarFilters=快捷欄
|
|
||||||
QuickbarFilters-tooltip=快捷欄的選項
|
|
||||||
QuickbarFilters-value-tooltip=使用 /save-quickbar 來更換快捷欄的選項
|
|
||||||
UsesAlt=細節模式
|
|
||||||
UsesAlt-tooltip=你加入時是否使用細節模式
|
|
||||||
UsesAlt-value-tooltip=按下 __CONTROL__show-info__ 來更改
|
|
||||||
UsesServerUps=服務器更新數 (SUPS)
|
|
||||||
UsesServerUps-tooltip=現在是否顯示服務器更新數 (SUPS)
|
|
||||||
UsesServerUps-value-tooltip=使用 /server-ups 來更換顯示
|
|
||||||
Tag=你的人物標籤
|
|
||||||
Tag-tooltip=你的人物標籤
|
|
||||||
Tag-value-tooltip=使用 /tag 來更換你的人物標籤
|
|
||||||
TagColor=人物標籤顏色
|
|
||||||
TagColor-tooltip=你的人物標籤顏色
|
|
||||||
TagColor-value-tooltip=使用 /tag-color 來更換你的人物標籤顏色
|
|
||||||
Bonus=人物附加屬性
|
|
||||||
Bonus-tooltip=你的人物附加屬性
|
|
||||||
Bonus-value-tooltip=使用 /bonus 來更改
|
|
||||||
HasEnabledDecon=快速拆除樹木
|
|
||||||
ToolbarState=工具箱
|
|
||||||
ToolbarState-tooltip=次序和喜愛項目
|
|
||||||
ToolbarState-value-tooltip=此值在離開時自動計算
|
|
||||||
|
|
||||||
[exp-statistics]
|
|
||||||
MapsPlayed=地圖遊玩次數
|
|
||||||
MapsPlayed-tooltip=你在本服務器遊玩過的地圖數
|
|
||||||
JoinCount=登入次數
|
|
||||||
JoinCount-tooltip=你在本服務器遊玩的次數
|
|
||||||
Playtime=遊玩時間
|
|
||||||
Playtime-tooltip=你在本服務器遊玩的時間
|
|
||||||
AfkTime=掛機時間
|
|
||||||
AfkTime-tooltip=你在本服務器掛機的時間
|
|
||||||
ChatMessages=傳送信息次數
|
|
||||||
ChatMessages-tooltip=你在本服務器發送信息的次數
|
|
||||||
CommandsUsed=使用指令次數
|
|
||||||
CommandsUsed-tooltip=你在本服務器使用指令的次數
|
|
||||||
RocketsLaunched=火箭發射次數
|
|
||||||
RocketsLaunched-tooltip=你在本服務器在線時所發射過火箭的次數
|
|
||||||
ResearchCompleted=研究次數
|
|
||||||
ResearchCompleted-tooltip=你在本服務器在線時完成研究的次數
|
|
||||||
MachinesBuilt=機器建造次數
|
|
||||||
MachinesBuilt-tooltip=你在本服務器建造過機器的次數
|
|
||||||
MachinesRemoved=機器拆除次數
|
|
||||||
MachinesRemoved-tooltip=你在本服務器拆除過機器的次數
|
|
||||||
TilesBuilt=地磚建造次數
|
|
||||||
TilesBuilt-tooltip=你在本服務器建造過地磚的次數
|
|
||||||
TilesRemoved=地磚拆除次數
|
|
||||||
TilesRemoved-tooltip=你在本服務器拆除過地磚的次數
|
|
||||||
TreesDestroyed=樹木拆除次數
|
|
||||||
TreesDestroyed-tooltip=你在本服務器拆除過樹木的次數
|
|
||||||
OreMined=挖礦次數
|
|
||||||
OreMined-tooltip=你在本服務器挖掘礦的次數
|
|
||||||
ItemsCrafted=物品合成數
|
|
||||||
ItemsCrafted-tooltip=你在本服務器合成過物品的次數
|
|
||||||
ItemsPickedUp=物品拾起數
|
|
||||||
ItemsPickedUp-tooltip=你在本服務器起過物品的次數
|
|
||||||
Kills=撃殺數
|
|
||||||
Kills-tooltip=你在本服務器殺過的敵人數量
|
|
||||||
Deaths=死亡次數
|
|
||||||
Deaths-tooltip=你在本服務器死過的數量
|
|
||||||
DamageDealt=傷害量
|
|
||||||
DamageDealt-tooltip=你在本伺服器對敵人所造成的傷害量
|
|
||||||
DistanceTravelled=移動距離
|
|
||||||
DistanceTravelled-tooltip=你在本服務器的總移動距離
|
|
||||||
CapsulesUsed=膠囊使用次數
|
|
||||||
CapsulesUsed-tooltip=你在本服務器使用過膠囊的次數
|
|
||||||
EntityRepaired=機器維修次數
|
|
||||||
EntityRepaired-tooltip=你在本服務器維修過機器的次數
|
|
||||||
DeconstructionPlannerUsed=拆除規劃器使用次數
|
|
||||||
DeconstructionPlannerUsed-tooltip=你在本服務器使用過拆除規劃器的次數
|
|
||||||
MapTagsMade=地圖標籤數量
|
|
||||||
MapTagsMade-tooltip=你在本服務器放過的地圖標籤數量
|
|
||||||
DamageDeathRatio=傷害死亡比
|
|
||||||
DamageDeathRatio-tooltip=傷害除以死亡
|
|
||||||
KillDeathRatio=撃殺死亡比
|
|
||||||
KillDeathRatio-tooltip=撃殺除以死亡
|
|
||||||
SessionTime=平均遊玩時間
|
|
||||||
SessionTime-tooltip=平均遊玩時間
|
|
||||||
BuildRatio=建築比
|
|
||||||
BuildRatio-tooltip=建築次數除以拆除次數
|
|
||||||
RocketPerHour=每小時火箭發射次數
|
|
||||||
RocketPerHour-tooltip=火箭發射次數除以遊玩時間
|
|
||||||
TreeKillPerMinute=每分鐘樹木拆除數
|
|
||||||
TreeKillPerMinute-tooltip=樹木拆除數除以遊玩時間
|
|
||||||
NetPlayTime=淨遊玩時間
|
|
||||||
NetPlayTime-tooltip=遊玩時間減掛機時間
|
|
||||||
AFKTimeRatio=掛機時間比
|
|
||||||
AFKTimeRatio-tooltip=掛機時間除以遊玩時間
|
|
||||||
Locale=語言
|
|
||||||
Locale-tooltip=用戶介面語言
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
[expcore-roles]
|
|
||||||
error-log-format-flag=[ERROR] roleFlag/__1__ :: __2__
|
|
||||||
error-log-format-assign=[ERROR] rolePromote/__1__ :: __2__
|
|
||||||
game-message-assign=__1__ 已被 __3__ 指派到身分組 __2__
|
|
||||||
game-message-unassign=__1__ 已被 __3__ 取消指派到身分組 __2__
|
|
||||||
reject-role=無效的身分組。
|
|
||||||
reject-player-role=用戶已有更高的身分組。
|
|
||||||
|
|
||||||
[expcore-data]
|
|
||||||
description-preference=允許寫入資料。
|
|
||||||
description-data=把用戶資料寫入電腦
|
|
||||||
arg-option="All", "Statistics", "Settings", "Required" 之中
|
|
||||||
set-preference=你的喜好已設定為 __1__ 。在你重新加入前將不會有任何改動。
|
|
||||||
get-preference=你的喜好為 __1__ 。使用 /set-preference 來更改,使用 /save-data 來獲得一份資料副本。
|
|
||||||
get-data=你的個人資料已寫入: factorio/script_output/expgaming_player_data.json
|
|
||||||
data-failed=無法載入你的個人資料,任何改動將不會保存。
|
|
||||||
data-restore=已載入你的個人資料,所有改動將會被保存。
|
|
||||||
preference=儲存喜好
|
|
||||||
preference-tooltip=那種資料將在退出時保存
|
|
||||||
preference-value-tooltip=使用 /set-preference 來更換儲存喜好
|
|
||||||
preference-All=所有資料都會被儲存
|
|
||||||
preference-Statistics=只有數據, 設定和資料會被儲存
|
|
||||||
preference-Settings=只有設定和資料會被儲存
|
|
||||||
preference-Required=只有需要的資料會被儲存
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
[player-list]
|
|
||||||
main-tooltip=用戶名單
|
|
||||||
open-action-bar=選項
|
|
||||||
close-action-bar=關閉選項
|
|
||||||
reason-confirm=確認原因
|
|
||||||
reason-entry=輸入原因
|
|
||||||
goto-player=傳送到用戶
|
|
||||||
bring-player=傳送用戶到自己
|
|
||||||
report-player=舉報用戶
|
|
||||||
warn-player=警告用戶
|
|
||||||
jail-player=監禁用戶
|
|
||||||
kick-player=踢除用戶
|
|
||||||
ban-player=封禁用戶
|
|
||||||
afk-time=__1__% 地圖時間\n上次移動: __2__
|
|
||||||
open-map=__1__ __2__\n__3__\n按下打開地圖
|
|
||||||
|
|
||||||
[rocket-info]
|
|
||||||
main-tooltip=火箭資訊
|
|
||||||
launch-tooltip=發射火箭
|
|
||||||
launch-tooltip-disabled=發射火箭 (未準備)
|
|
||||||
toggle-rocket-tooltip=取消自動發射
|
|
||||||
toggle-rocket-tooltip-disabled=啟用自動發射
|
|
||||||
toggle-section-tooltip=擴張欄
|
|
||||||
toggle-section-collapse-tooltip=收縮欄
|
|
||||||
section-caption-stats=數據
|
|
||||||
section-tooltip-stats=火箭發射數據
|
|
||||||
section-caption-milestones=里程碑
|
|
||||||
section-tooltip-milestones=里程碑達成次數
|
|
||||||
section-caption-progress=建造程度
|
|
||||||
section-tooltip-progress=火箭建造程度
|
|
||||||
progress-no-silos=你沒有火箭發射井
|
|
||||||
data-caption-first-launch=首次發射
|
|
||||||
data-tooltip-first-launch=首次發射時間
|
|
||||||
data-caption-last-launch=最後發射
|
|
||||||
data-tooltip-last-launch=最後發射時間
|
|
||||||
data-caption-fastest-launch=最快發射
|
|
||||||
data-tooltip-fastest-launch=最快發射時間
|
|
||||||
data-caption-total-rockets=總發射次數
|
|
||||||
data-tooltip-total-rockets=我方總發射次數
|
|
||||||
value-tooltip-total-rockets=__1__% 的火箭發射都在本地圖上。
|
|
||||||
data-caption-avg-launch=平均時間
|
|
||||||
data-tooltip-avg-launch=火箭發射平均時間
|
|
||||||
data-caption-avg-launch-n=平均時間 __1__
|
|
||||||
data-tooltip-avg-launch-n=__1__ 次火箭發射的平均時間
|
|
||||||
data-caption-milestone-n=里程碑 __1__
|
|
||||||
data-tooltip-milestone-n=__1__ 次火箭發射的時間
|
|
||||||
data-caption-milestone-next=N/A
|
|
||||||
data-tooltip-milestone-next=未達成
|
|
||||||
progress-x-pos=X __1__
|
|
||||||
progress-y-pos=Y __1__
|
|
||||||
progress-label-tooltip=在地圖上看
|
|
||||||
progress-launched=已發射
|
|
||||||
progress-caption=__1__ %
|
|
||||||
progress-tooltip=該火箭發射井發射了 __1__ 次
|
|
||||||
launch-failed=火箭發射失敗, 請過一會再試。
|
|
||||||
|
|
||||||
[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=修改傳送陣圖案
|
|
||||||
|
|
||||||
[readme]
|
|
||||||
main-tooltip=資訊
|
|
||||||
welcome-tab=歡迎
|
|
||||||
welcome-tooltip=歡迎來到 Explosive Gaming
|
|
||||||
welcome-general=歡迎來到 Explosive Gaming; 你在這裏的時候要遵守規則。 你可以在左上方的介面找到更多資訊。 地圖時間 __2__。\n下次地圖重設: __1__
|
|
||||||
welcome-roles=我們有自訂的身份組來保護其他用戶。 所以你有機會不能使用拆除規劃器或掉東西。 身份組給予你各種權限\n你有以下身份組: __1__
|
|
||||||
welcome-chat=你可以使用 「重音符/抑音符」 鍵 (ESC 鍵 下方) - 你也可以在選項中修改按鍵。\n你目前設定為 「__CONTROL__toggle-console__」
|
|
||||||
rules-tab=規則
|
|
||||||
rules-tooltip=服務器規則
|
|
||||||
rules-general=你在本服務器遊玩即等同同意以下規則。管理員有機會就相關事項提問。你可能會因為違反規則而被封禁,你可以在 Discord 中提出反對。
|
|
||||||
rules-1=開掛或濫用漏洞將不會被容忍。
|
|
||||||
rules-2=當你找到漏洞可以通知職員。
|
|
||||||
rules-3=請在聊天室中尊重其他人。
|
|
||||||
rules-4=請不要把所有物品全部取走。
|
|
||||||
rules-5=請不要濫發信息,大量鋪設地磚,或不為箱子設置上限,浪費資源。
|
|
||||||
rules-6=當你想大量拆除原有設施前請通知其他用戶。
|
|
||||||
rules-7=請不要無故使用機器人來大量鋪設或拆除地磚,濫用主動出貨箱。
|
|
||||||
rules-8=請不要隨意移動來開大量地圖,這有助減少地圖下載時間。
|
|
||||||
rules-9=請不要無故使用全圖喇叭。
|
|
||||||
rules-10=請不要無故轉動傳送帶方向,停用傳送帶,或停止生產。
|
|
||||||
rules-11=請不要用迴旋處。 火車站尾返程則是例外。
|
|
||||||
rules-12=請跟其他人使用相同的火車組成。常用的有 1-2-1,2-4-2,或 3-8-3,即1個動車組拖2個貨車組再拖不同方向的1個動車組。
|
|
||||||
rules-13=火車必須是左上右下 (LHD)。
|
|
||||||
rules-14=禁止要求身份組,發廣告,發惡意連結。
|
|
||||||
rules-15=使用常識, 舉報違規人員,管理員有最終決定權。
|
|
||||||
commands-tab=指令
|
|
||||||
commands-tooltip=你可用的指令
|
|
||||||
commands-general=這裏有自訂的指令。你可以在下邊找到你可用的指令及其功能介紹。你可以用 /search-help 來取得更多資訊。
|
|
||||||
servers-tab=伺服器
|
|
||||||
servers-tooltip=到其他伺服器及網站的連結
|
|
||||||
servers-general=這是其中一個伺服器。你可以在下方找到其他同樣是由我們運行的伺服器資料。
|
|
||||||
servers-factorio=異星工廠伺服器
|
|
||||||
servers-1=S1 公共
|
|
||||||
servers-d1=這個服務器每兩日重設。
|
|
||||||
servers-2=S2 -
|
|
||||||
servers-d2=這個服務器沒在營運。
|
|
||||||
servers-3=S3 周
|
|
||||||
servers-d3=這個服務器每週重設。
|
|
||||||
servers-4=S4 月
|
|
||||||
servers-d4=這個服務器每月重設。
|
|
||||||
servers-5=S5 模組
|
|
||||||
servers-d5=這個服務器運行模組,可在 Discord 中得到更多資訊。
|
|
||||||
servers-6=S6 Donator
|
|
||||||
servers-d6=這個服務器為支持者運行,可在 Discord 中得到更多資訊。
|
|
||||||
servers-7=S7 項目
|
|
||||||
servers-d7=這個服務器只在有項目期間運行。
|
|
||||||
servers-8=S8 T̷-̶s̶-̴:̷
|
|
||||||
servers-d8=N̵o̴ ̶o̷-̶e̵ ̴k̸n̷-̶w̵s̸ ̴w̷h̷a̶-̶ ̷h̴a̴p̷p̴e̷n̷s̸ ̷o̶n̴ ̷t̶h̴-̶s̶ ̷s̷e̶r̸v̸e̴r̷,̶ ̸i̸t̴ ̷m̶-̸g̴h̶t̷ ̸n̸-̶t̵ ̷e̴v̸e̸n̶t̷ ̵-̷x̴i̵s̶t̸。̸
|
|
||||||
servers-connect-Offline=該服務器目前沒有在運行
|
|
||||||
servers-connect-Current=你目前所在的伺服器
|
|
||||||
servers-connect-Version=該服務器運行不同的遊戲版本: __1__
|
|
||||||
servers-connect-Password=該服務器需要密碼
|
|
||||||
servers-connect-Modded=該服務器需要下載模組
|
|
||||||
servers-connect-Online=該服務器在線
|
|
||||||
servers-external=外部連結
|
|
||||||
servers-open-in-browser=可在你的瀏覽器中打開
|
|
||||||
backers-tab=支持者
|
|
||||||
backers-tooltip=支持者
|
|
||||||
backers-general=他們為服務器運行提供了不少幫助。
|
|
||||||
backers-management=系統管理員
|
|
||||||
backers-board=議員
|
|
||||||
backers-staff=職員
|
|
||||||
backers-backers=支持者
|
|
||||||
backers-active=活躍玩家
|
|
||||||
data-tab=資料
|
|
||||||
data-tooltip=所有已存資料
|
|
||||||
data-general=資料會自動存到各個服務器中。 你也可以用 /save-data 存一份副本。 也可用 /set-preference 來更改喜好。
|
|
||||||
data-settings=設定
|
|
||||||
data-statistics=數據
|
|
||||||
data-required=需要
|
|
||||||
data-misc=雜項
|
|
||||||
data-format=__1____2__
|
|
||||||
|
|
||||||
[bonus]
|
|
||||||
description=取得 / 設定 Bonus 量。
|
|
||||||
arg-amount=Bonus 數量, 0 來停用。
|
|
||||||
set=你的 Bonus 已設為 __1__。
|
|
||||||
get=你的 Bonus 為 __1__。
|
|
||||||
perm=你沒有足夠權限設多過 __1__。
|
|
||||||
display-fmms=挖掘速度
|
|
||||||
display-fmms-tooltip=勢力挖掘速度
|
|
||||||
display-frs=跑步速度
|
|
||||||
display-frs-tooltip=勢力跑步速度
|
|
||||||
display-fcs=合成速度
|
|
||||||
display-fcs-tooltip=勢力合成速度
|
|
||||||
display-fisb=儲存位
|
|
||||||
display-fisb-tooltip=勢力儲存位
|
|
||||||
display-fhb=生命
|
|
||||||
display-fhb-tooltip=勢力生命
|
|
||||||
display-frdb=到達距離
|
|
||||||
display-frdb-tooltip=勢力到達距離
|
|
||||||
display-fwrsm=機械人速度
|
|
||||||
display-fwrsm-tooltip=勢力機械人速度
|
|
||||||
display-fwrbm=機械人電力
|
|
||||||
display-fwrbm-tooltip=勢力機械人電力
|
|
||||||
display-fwrsb=械人儲存
|
|
||||||
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 功能。
|
|
||||||
enter=現在進入 vlayer 區域選擇
|
|
||||||
exit=已進入 vlayer 區域選擇
|
|
||||||
|
|
||||||
[toolbar]
|
|
||||||
main-caption=工具箱
|
|
||||||
main-tooltip=工具箱設定\n選上來設定喜好
|
|
||||||
reset=重設
|
|
||||||
toggle=啟用喜好
|
|
||||||
move-up=向上
|
|
||||||
move-down=向下
|
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
# addons
|
||||||
|
[info]
|
||||||
|
players-online=現在有 __1__ 人在線
|
||||||
|
total-map-time=地圖時間為 __1__
|
||||||
|
discord=加入社群: __1__
|
||||||
|
website=訪問網站: __1__
|
||||||
|
status=伺服器狀態: __1__
|
||||||
|
github=增加功能或回報錯誤: __1__
|
||||||
|
patreon=支持我們: __1__
|
||||||
|
custom-commands=這裹使用了自制指令,如 /tag 和 /me。可使用 /chelp 查看更多。
|
||||||
|
read-readme=確保你已閱讀相關資訊。按左上 i 圖標可再次查看
|
||||||
|
softmod=這裹用了自設情境,是一種軟裝模組。
|
||||||
|
redmew=We don't talk about redmew here; they beat us to 1000 members ;-;
|
||||||
|
lhd=列車必須是左則通行。這是本服務器長久以來的規則。
|
||||||
|
|
||||||
|
# data
|
||||||
|
[join-message]
|
||||||
|
description-add=設定 / 取得你的加入信息。
|
||||||
|
description-remove=移除你的加入信息。
|
||||||
|
arg-message=你的加入信息。
|
||||||
|
greet=[color=0,1,0] 歡迎來到 __1__ 的服務器! 若果你喜歡本服務器可加入 Discord: __2__ [/color]
|
||||||
|
message-set=你的加入信息已更新。
|
||||||
|
message-get=你的加入信息為: __1__
|
||||||
|
message-cleared=你的加入信息已清除。
|
||||||
|
|
||||||
|
[quickbar]
|
||||||
|
saved=你的工具列已儲存。
|
||||||
|
|
||||||
|
[exp-settings]
|
||||||
|
Colour=顏色
|
||||||
|
Colour-tooltip=你的人物顏色
|
||||||
|
Colour-value-tooltip=使用 /color 來更換你的人物顏色
|
||||||
|
JoinMessage=加入信息
|
||||||
|
JoinMessage-tooltip=你加入時所顯示的信息
|
||||||
|
JoinMessage-value-tooltip=使用 /join-message 來更換加入信息
|
||||||
|
QuickbarFilters=快捷欄
|
||||||
|
QuickbarFilters-tooltip=快捷欄的選項
|
||||||
|
QuickbarFilters-value-tooltip=使用 /save-quickbar 來更換快捷欄的選項
|
||||||
|
UsesAlt=細節模式
|
||||||
|
UsesAlt-tooltip=你加入時是否使用細節模式
|
||||||
|
UsesAlt-value-tooltip=按下 __CONTROL__show-info__ 來更改
|
||||||
|
UsesServerUps=服務器更新數 (SUPS)
|
||||||
|
UsesServerUps-tooltip=現在是否顯示服務器更新數 (SUPS)
|
||||||
|
UsesServerUps-value-tooltip=使用 /server-ups 來更換顯示
|
||||||
|
Tag=你的人物標籤
|
||||||
|
Tag-tooltip=你的人物標籤
|
||||||
|
Tag-value-tooltip=使用 /tag 來更換你的人物標籤
|
||||||
|
TagColor=人物標籤顏色
|
||||||
|
TagColor-tooltip=你的人物標籤顏色
|
||||||
|
TagColor-value-tooltip=使用 /tag-color 來更換你的人物標籤顏色
|
||||||
|
Bonus=人物附加屬性
|
||||||
|
Bonus-tooltip=你的人物附加屬性
|
||||||
|
Bonus-value-tooltip=使用 /bonus 來更改
|
||||||
|
HasEnabledDecon=快速拆除樹木
|
||||||
|
ToolbarState=工具箱
|
||||||
|
ToolbarState-tooltip=次序和喜愛項目
|
||||||
|
ToolbarState-value-tooltip=此值在離開時自動計算
|
||||||
|
|
||||||
|
[exp-statistics]
|
||||||
|
MapsPlayed=地圖遊玩次數
|
||||||
|
MapsPlayed-tooltip=你在本服務器遊玩過的地圖數
|
||||||
|
JoinCount=登入次數
|
||||||
|
JoinCount-tooltip=你在本服務器遊玩的次數
|
||||||
|
Playtime=遊玩時間
|
||||||
|
Playtime-tooltip=你在本服務器遊玩的時間
|
||||||
|
AfkTime=掛機時間
|
||||||
|
AfkTime-tooltip=你在本服務器掛機的時間
|
||||||
|
ChatMessages=傳送信息次數
|
||||||
|
ChatMessages-tooltip=你在本服務器發送信息的次數
|
||||||
|
CommandsUsed=使用指令次數
|
||||||
|
CommandsUsed-tooltip=你在本服務器使用指令的次數
|
||||||
|
RocketsLaunched=火箭發射次數
|
||||||
|
RocketsLaunched-tooltip=你在本服務器在線時所發射過火箭的次數
|
||||||
|
ResearchCompleted=研究次數
|
||||||
|
ResearchCompleted-tooltip=你在本服務器在線時完成研究的次數
|
||||||
|
MachinesBuilt=機器建造次數
|
||||||
|
MachinesBuilt-tooltip=你在本服務器建造過機器的次數
|
||||||
|
MachinesRemoved=機器拆除次數
|
||||||
|
MachinesRemoved-tooltip=你在本服務器拆除過機器的次數
|
||||||
|
TilesBuilt=地磚建造次數
|
||||||
|
TilesBuilt-tooltip=你在本服務器建造過地磚的次數
|
||||||
|
TilesRemoved=地磚拆除次數
|
||||||
|
TilesRemoved-tooltip=你在本服務器拆除過地磚的次數
|
||||||
|
TreesDestroyed=樹木拆除次數
|
||||||
|
TreesDestroyed-tooltip=你在本服務器拆除過樹木的次數
|
||||||
|
OreMined=挖礦次數
|
||||||
|
OreMined-tooltip=你在本服務器挖掘礦的次數
|
||||||
|
ItemsCrafted=物品合成數
|
||||||
|
ItemsCrafted-tooltip=你在本服務器合成過物品的次數
|
||||||
|
ItemsPickedUp=物品拾起數
|
||||||
|
ItemsPickedUp-tooltip=你在本服務器起過物品的次數
|
||||||
|
Kills=撃殺數
|
||||||
|
Kills-tooltip=你在本服務器殺過的敵人數量
|
||||||
|
Deaths=死亡次數
|
||||||
|
Deaths-tooltip=你在本服務器死過的數量
|
||||||
|
DamageDealt=傷害量
|
||||||
|
DamageDealt-tooltip=你在本伺服器對敵人所造成的傷害量
|
||||||
|
DistanceTravelled=移動距離
|
||||||
|
DistanceTravelled-tooltip=你在本服務器的總移動距離
|
||||||
|
CapsulesUsed=膠囊使用次數
|
||||||
|
CapsulesUsed-tooltip=你在本服務器使用過膠囊的次數
|
||||||
|
EntityRepaired=機器維修次數
|
||||||
|
EntityRepaired-tooltip=你在本服務器維修過機器的次數
|
||||||
|
DeconstructionPlannerUsed=拆除規劃器使用次數
|
||||||
|
DeconstructionPlannerUsed-tooltip=你在本服務器使用過拆除規劃器的次數
|
||||||
|
MapTagsMade=地圖標籤數量
|
||||||
|
MapTagsMade-tooltip=你在本服務器放過的地圖標籤數量
|
||||||
|
DamageDeathRatio=傷害死亡比
|
||||||
|
DamageDeathRatio-tooltip=傷害除以死亡
|
||||||
|
KillDeathRatio=撃殺死亡比
|
||||||
|
KillDeathRatio-tooltip=撃殺除以死亡
|
||||||
|
SessionTime=平均遊玩時間
|
||||||
|
SessionTime-tooltip=平均遊玩時間
|
||||||
|
BuildRatio=建築比
|
||||||
|
BuildRatio-tooltip=建築次數除以拆除次數
|
||||||
|
RocketPerHour=每小時火箭發射次數
|
||||||
|
RocketPerHour-tooltip=火箭發射次數除以遊玩時間
|
||||||
|
TreeKillPerMinute=每分鐘樹木拆除數
|
||||||
|
TreeKillPerMinute-tooltip=樹木拆除數除以遊玩時間
|
||||||
|
NetPlayTime=淨遊玩時間
|
||||||
|
NetPlayTime-tooltip=遊玩時間減掛機時間
|
||||||
|
AFKTimeRatio=掛機時間比
|
||||||
|
AFKTimeRatio-tooltip=掛機時間除以遊玩時間
|
||||||
|
Locale=語言
|
||||||
|
Locale-tooltip=用戶介面語言
|
||||||
|
|
||||||
|
# expcore
|
||||||
|
[expcore-data]
|
||||||
|
description-preference=允許寫入資料。
|
||||||
|
description-data=把用戶資料寫入電腦。
|
||||||
|
arg-option="All", "Statistics", "Settings", "Required" 之中
|
||||||
|
set-preference=你的喜好已設定為 __1__ 。在你重新加入前將不會有任何改動。
|
||||||
|
get-preference=你的喜好為 __1__ 。使用 /set-preference 來更改,使用 /save-data 來獲得一份資料副本。
|
||||||
|
get-data=你的個人資料已寫入: factorio/script_output/expgaming_player_data.json
|
||||||
|
data-failed=無法載入你的個人資料,任何改動將不會保存。
|
||||||
|
data-restore=已載入你的個人資料,所有改動將會被保存。
|
||||||
|
preference=儲存喜好
|
||||||
|
preference-tooltip=那種資料將在退出時保存
|
||||||
|
preference-value-tooltip=使用 /set-preference 來更換儲存喜好
|
||||||
|
preference-All=所有資料都會被儲存
|
||||||
|
preference-Statistics=只有數據, 設定和資料會被儲存
|
||||||
|
preference-Settings=只有設定和資料會被儲存
|
||||||
|
preference-Required=只有需要的資料會被儲存
|
||||||
|
|
||||||
|
# gui
|
||||||
|
[player-list]
|
||||||
|
main-tooltip=用戶名單
|
||||||
|
open-action-bar=選項
|
||||||
|
close-action-bar=關閉選項
|
||||||
|
reason-confirm=確認原因
|
||||||
|
reason-entry=輸入原因
|
||||||
|
goto-player=傳送到用戶
|
||||||
|
bring-player=傳送用戶到自己
|
||||||
|
report-player=舉報用戶
|
||||||
|
jail-player=監禁用戶
|
||||||
|
kick-player=踢除用戶
|
||||||
|
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 來停用。
|
||||||
|
set=你的 Bonus 已設為 __1__。
|
||||||
|
get=你的 Bonus 為 __1__。
|
||||||
|
perm=你沒有足夠權限設多過 __1__。
|
||||||
|
display-fmms=挖掘速度
|
||||||
|
display-fmms-tooltip=勢力挖掘速度
|
||||||
|
display-frs=跑步速度
|
||||||
|
display-frs-tooltip=勢力跑步速度
|
||||||
|
display-fcs=合成速度
|
||||||
|
display-fcs-tooltip=勢力合成速度
|
||||||
|
display-fisb=儲存位
|
||||||
|
display-fisb-tooltip=勢力儲存位
|
||||||
|
display-fhb=生命
|
||||||
|
display-fhb-tooltip=勢力生命
|
||||||
|
display-frdb=到達距離
|
||||||
|
display-frdb-tooltip=勢力到達距離
|
||||||
|
display-fwrsm=機械人速度
|
||||||
|
display-fwrsm-tooltip=勢力機械人速度
|
||||||
|
display-fwrbm=機械人電力
|
||||||
|
display-fwrbm-tooltip=勢力機械人電力
|
||||||
|
display-fwrsb=械人儲存
|
||||||
|
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選上來設定喜好
|
||||||
|
reset=重設
|
||||||
|
toggle=啟用喜好
|
||||||
|
move-up=向上
|
||||||
|
move-down=向下
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
[links]
|
|
||||||
discord=https://discord.explosivegaming.nl
|
|
||||||
website=https://www.explosivegaming.nl
|
|
||||||
status=https://status.explosivegaming.nl
|
|
||||||
github=https://github.com/explosivegaming/ExpCluster
|
|
||||||
patreon=https://www.patreon.com/ExpGaming
|
|
||||||
|
|
||||||
[info]
|
|
||||||
players-online=現在有 __1__ 人在線
|
|
||||||
total-map-time=地圖時間為 __1__
|
|
||||||
discord=加入社群: https://discord.explosivegaming.nl
|
|
||||||
website=訪問網站: https://www.explosivegaming.nl
|
|
||||||
status=伺服器狀態: https://status.explosivegaming.nl
|
|
||||||
github=增加功能或回報錯誤: https://github.com/explosivegaming/ExpCluster
|
|
||||||
patreon=支持我們: https://www.patreon.com/ExpGaming
|
|
||||||
custom-commands=這裹使用了自制指令,如 /tag 和 /me。可使用 /chelp 查看更多。
|
|
||||||
read-readme=確保你已閱讀相關資訊。按左上 i 圖標可再次查看。
|
|
||||||
softmod=這裹用了自設情境,是一種軟裝模組。
|
|
||||||
redmew=
|
|
||||||
lhd=列車必須是左則通行。這是本服務器長久以來的規則。
|
|
||||||
|
|
||||||
[warnings]
|
|
||||||
received=你已被 __1__ 警告了,現在共有 __2__ 個警告。 __3__
|
|
||||||
pre-kick=這是在被踢離之前的最後警告。
|
|
||||||
kick=你已因為被警告太多次而被請離,不過你之後還是可以回來的。
|
|
||||||
pre-pre-ban=按照你的行為,你有可能會被封禁。
|
|
||||||
pre-ban=這是在被封禁之前的最後警告。
|
|
||||||
ban=你已因為被警告太多次而被封禁; 可以到 __1__ 申訴.
|
|
||||||
script-warning=這是系統發出的自動警告 (__1__/__2__)
|
|
||||||
script-warning-removed=系統發出的自動警告已失效 (__1__/__2__)
|
|
||||||
script-warning-limit=__1__ 已被系統發出了一則自動警告。
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
[join-message]
|
|
||||||
description-add=設定 / 取得你的加入信息。
|
|
||||||
description-remove=移除你的加入信息。
|
|
||||||
arg-message=你的加入信息。
|
|
||||||
greet=[color=0,1,0] 歡迎來到 EXP 的服務器! 若果你喜歡本服務器可加入 DISCORD: __1__ [/color]
|
|
||||||
message-set=你的加入信息已更新。
|
|
||||||
message-get=你的加入信息為: __1__
|
|
||||||
message-cleared=你的加入信息已清除。
|
|
||||||
|
|
||||||
[quickbar]
|
|
||||||
saved=你的工具列已儲存。
|
|
||||||
|
|
||||||
[exp-required]
|
|
||||||
Warnings=警告
|
|
||||||
Warnings-tooltip=你所有收到的警告
|
|
||||||
Warnings-value-tooltip=你所有收到的警告
|
|
||||||
|
|
||||||
[exp-settings]
|
|
||||||
Colour=顏色
|
|
||||||
Colour-tooltip=你的人物顏色
|
|
||||||
Colour-value-tooltip=使用 /color 來更換你的人物顏色
|
|
||||||
JoinMessage=加入信息
|
|
||||||
JoinMessage-tooltip=你加入時所顯示的信息
|
|
||||||
JoinMessage-value-tooltip=使用 /join-message 來更換加入信息
|
|
||||||
QuickbarFilters=快捷欄
|
|
||||||
QuickbarFilters-tooltip=快捷欄的選項
|
|
||||||
QuickbarFilters-value-tooltip=使用 /save-quickbar 來更換快捷欄的選項
|
|
||||||
UsesAlt=細節模式
|
|
||||||
UsesAlt-tooltip=你加入時是否使用細節模式
|
|
||||||
UsesAlt-value-tooltip=按下 __CONTROL__show-info__ 來更改
|
|
||||||
UsesServerUps=服務器更新數 (SUPS)
|
|
||||||
UsesServerUps-tooltip=現在是否顯示服務器更新數 (SUPS)
|
|
||||||
UsesServerUps-value-tooltip=使用 /server-ups 來更換顯示
|
|
||||||
Tag=你的人物標籤
|
|
||||||
Tag-tooltip=你的人物標籤
|
|
||||||
Tag-value-tooltip=使用 /tag 來更換你的人物標籤
|
|
||||||
TagColor=人物標籤顏色
|
|
||||||
TagColor-tooltip=你的人物標籤顏色
|
|
||||||
TagColor-value-tooltip=使用 /tag-color 來更換你的人物標籤顏色
|
|
||||||
Bonus=人物附加屬性
|
|
||||||
Bonus-tooltip=你的人物附加屬性
|
|
||||||
Bonus-value-tooltip=使用 /bonus 來更改
|
|
||||||
HasEnabledDecon=快速拆除樹木
|
|
||||||
ToolbarState=工具箱
|
|
||||||
ToolbarState-tooltip=次序和喜愛項目
|
|
||||||
ToolbarState-value-tooltip=此值在離開時自動計算
|
|
||||||
|
|
||||||
[exp-statistics]
|
|
||||||
MapsPlayed=地圖遊玩次數
|
|
||||||
MapsPlayed-tooltip=你在本服務器遊玩過的地圖數
|
|
||||||
JoinCount=登入次數
|
|
||||||
JoinCount-tooltip=你在本服務器遊玩的次數
|
|
||||||
Playtime=遊玩時間
|
|
||||||
Playtime-tooltip=你在本服務器遊玩的時間
|
|
||||||
AfkTime=掛機時間
|
|
||||||
AfkTime-tooltip=你在本服務器掛機的時間
|
|
||||||
ChatMessages=傳送信息次數
|
|
||||||
ChatMessages-tooltip=你在本服務器發送信息的次數
|
|
||||||
CommandsUsed=使用指令次數
|
|
||||||
CommandsUsed-tooltip=你在本服務器使用指令的次數
|
|
||||||
RocketsLaunched=火箭發射次數
|
|
||||||
RocketsLaunched-tooltip=你在本服務器在線時所發射過火箭的次數
|
|
||||||
ResearchCompleted=研究次數
|
|
||||||
ResearchCompleted-tooltip=你在本服務器在線時完成研究的次數
|
|
||||||
MachinesBuilt=機器建造次數
|
|
||||||
MachinesBuilt-tooltip=你在本服務器建造過機器的次數
|
|
||||||
MachinesRemoved=機器拆除次數
|
|
||||||
MachinesRemoved-tooltip=你在本服務器拆除過機器的次數
|
|
||||||
TilesBuilt=地磚建造次數
|
|
||||||
TilesBuilt-tooltip=你在本服務器建造過地磚的次數
|
|
||||||
TilesRemoved=地磚拆除次數
|
|
||||||
TilesRemoved-tooltip=你在本服務器拆除過地磚的次數
|
|
||||||
TreesDestroyed=樹木拆除次數
|
|
||||||
TreesDestroyed-tooltip=你在本服務器拆除過樹木的次數
|
|
||||||
OreMined=挖礦次數
|
|
||||||
OreMined-tooltip=你在本服務器挖掘礦的次數
|
|
||||||
ItemsCrafted=物品合成數
|
|
||||||
ItemsCrafted-tooltip=你在本服務器合成過物品的次數
|
|
||||||
ItemsPickedUp=物品拾起數
|
|
||||||
ItemsPickedUp-tooltip=你在本服務器起過物品的次數
|
|
||||||
Kills=撃殺數
|
|
||||||
Kills-tooltip=你在本服務器殺過的敵人數量
|
|
||||||
Deaths=死亡次數
|
|
||||||
Deaths-tooltip=你在本服務器死過的數量
|
|
||||||
DamageDealt=傷害量
|
|
||||||
DamageDealt-tooltip=你在本伺服器對敵人所造成的傷害量
|
|
||||||
DistanceTravelled=移動距離
|
|
||||||
DistanceTravelled-tooltip=你在本服務器的總移動距離
|
|
||||||
CapsulesUsed=膠囊使用次數
|
|
||||||
CapsulesUsed-tooltip=你在本服務器使用過膠囊的次數
|
|
||||||
EntityRepaired=機器維修次數
|
|
||||||
EntityRepaired-tooltip=你在本服務器維修過機器的次數
|
|
||||||
DeconstructionPlannerUsed=拆除規劃器使用次數
|
|
||||||
DeconstructionPlannerUsed-tooltip=你在本服務器使用過拆除規劃器的次數
|
|
||||||
MapTagsMade=地圖標籤數量
|
|
||||||
MapTagsMade-tooltip=你在本服務器放過的地圖標籤數量
|
|
||||||
DamageDeathRatio=傷害死亡比
|
|
||||||
DamageDeathRatio-tooltip=傷害除以死亡
|
|
||||||
KillDeathRatio=撃殺死亡比
|
|
||||||
KillDeathRatio-tooltip=撃殺除以死亡
|
|
||||||
SessionTime=平均遊玩時間
|
|
||||||
SessionTime-tooltip=平均遊玩時間
|
|
||||||
BuildRatio=建築比
|
|
||||||
BuildRatio-tooltip=建築次數除以拆除次數
|
|
||||||
RocketPerHour=每小時火箭發射次數
|
|
||||||
RocketPerHour-tooltip=火箭發射次數除以遊玩時間
|
|
||||||
TreeKillPerMinute=每分鐘樹木拆除數
|
|
||||||
TreeKillPerMinute-tooltip=樹木拆除數除以遊玩時間
|
|
||||||
NetPlayTime=淨遊玩時間
|
|
||||||
NetPlayTime-tooltip=遊玩時間減掛機時間
|
|
||||||
AFKTimeRatio=掛機時間比
|
|
||||||
AFKTimeRatio-tooltip=掛機時間除以遊玩時間
|
|
||||||
Locale=語言
|
|
||||||
Locale-tooltip=用戶介面語言
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
[expcore-roles]
|
|
||||||
error-log-format-flag=[ERROR] roleFlag/__1__ :: __2__
|
|
||||||
error-log-format-assign=[ERROR] rolePromote/__1__ :: __2__
|
|
||||||
game-message-assign=__1__ 已被 __3__ 指派到身分組 __2__
|
|
||||||
game-message-unassign=__1__ 已被 __3__ 取消指派到身分組 __2__
|
|
||||||
reject-role=無效的身分組。
|
|
||||||
reject-player-role=用戶已有更高的身分組。
|
|
||||||
|
|
||||||
[expcore-data]
|
|
||||||
description-preference=允許寫入資料。
|
|
||||||
description-data=把用戶資料寫入電腦
|
|
||||||
arg-option="All", "Statistics", "Settings", "Required" 之中
|
|
||||||
set-preference=你的喜好已設定為 __1__ 。在你重新加入前將不會有任何改動。
|
|
||||||
get-preference=你的喜好為 __1__ 。使用 /set-preference 來更改,使用 /save-data 來獲得一份資料副本。
|
|
||||||
get-data=你的個人資料已寫入: factorio/script_output/expgaming_player_data.json
|
|
||||||
data-failed=無法載入你的個人資料,任何改動將不會保存。
|
|
||||||
data-restore=已載入你的個人資料,所有改動將會被保存。
|
|
||||||
preference=儲存喜好
|
|
||||||
preference-tooltip=那種資料將在退出時保存
|
|
||||||
preference-value-tooltip=使用 /set-preference 來更換儲存喜好
|
|
||||||
preference-All=所有資料都會被儲存
|
|
||||||
preference-Statistics=只有數據, 設定和資料會被儲存
|
|
||||||
preference-Settings=只有設定和資料會被儲存
|
|
||||||
preference-Required=只有需要的資料會被儲存
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
[player-list]
|
|
||||||
main-tooltip=用戶名單
|
|
||||||
open-action-bar=選項
|
|
||||||
close-action-bar=關閉選項
|
|
||||||
reason-confirm=確認原因
|
|
||||||
reason-entry=輸入原因
|
|
||||||
goto-player=傳送到用戶
|
|
||||||
bring-player=傳送用戶到自己
|
|
||||||
report-player=舉報用戶
|
|
||||||
warn-player=警告用戶
|
|
||||||
jail-player=監禁用戶
|
|
||||||
kick-player=踢除用戶
|
|
||||||
ban-player=封禁用戶
|
|
||||||
afk-time=__1__% 地圖時間\n上次移動: __2__
|
|
||||||
open-map=__1__ __2__\n__3__\n按下打開地圖
|
|
||||||
|
|
||||||
[rocket-info]
|
|
||||||
main-tooltip=火箭資訊
|
|
||||||
launch-tooltip=發射火箭
|
|
||||||
launch-tooltip-disabled=發射火箭 (未準備)
|
|
||||||
toggle-rocket-tooltip=取消自動發射
|
|
||||||
toggle-rocket-tooltip-disabled=啟用自動發射
|
|
||||||
toggle-section-tooltip=擴張欄
|
|
||||||
toggle-section-collapse-tooltip=收縮欄
|
|
||||||
section-caption-stats=數據
|
|
||||||
section-tooltip-stats=火箭發射數據
|
|
||||||
section-caption-milestones=里程碑
|
|
||||||
section-tooltip-milestones=里程碑達成次數
|
|
||||||
section-caption-progress=建造程度
|
|
||||||
section-tooltip-progress=火箭建造程度
|
|
||||||
progress-no-silos=你沒有火箭發射井
|
|
||||||
data-caption-first-launch=首次發射
|
|
||||||
data-tooltip-first-launch=首次發射時間
|
|
||||||
data-caption-last-launch=最後發射
|
|
||||||
data-tooltip-last-launch=最後發射時間
|
|
||||||
data-caption-fastest-launch=最快發射
|
|
||||||
data-tooltip-fastest-launch=最快發射時間
|
|
||||||
data-caption-total-rockets=總發射次數
|
|
||||||
data-tooltip-total-rockets=我方總發射次數
|
|
||||||
value-tooltip-total-rockets=__1__% 的火箭發射都在本地圖上。
|
|
||||||
data-caption-avg-launch=平均時間
|
|
||||||
data-tooltip-avg-launch=火箭發射平均時間
|
|
||||||
data-caption-avg-launch-n=平均時間 __1__
|
|
||||||
data-tooltip-avg-launch-n=__1__ 次火箭發射的平均時間
|
|
||||||
data-caption-milestone-n=里程碑 __1__
|
|
||||||
data-tooltip-milestone-n=__1__ 次火箭發射的時間
|
|
||||||
data-caption-milestone-next=N/A
|
|
||||||
data-tooltip-milestone-next=未達成
|
|
||||||
progress-x-pos=X __1__
|
|
||||||
progress-y-pos=Y __1__
|
|
||||||
progress-label-tooltip=在地圖上看
|
|
||||||
progress-launched=已發射
|
|
||||||
progress-caption=__1__ %
|
|
||||||
progress-tooltip=該火箭發射井發射了 __1__ 次
|
|
||||||
launch-failed=火箭發射失敗, 請過一會再試。
|
|
||||||
|
|
||||||
[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=修改傳送陣圖案
|
|
||||||
|
|
||||||
[readme]
|
|
||||||
main-tooltip=資訊
|
|
||||||
welcome-tab=歡迎
|
|
||||||
welcome-tooltip=歡迎來到 Explosive Gaming
|
|
||||||
welcome-general=歡迎來到 Explosive Gaming; 你在這裏的時候要遵守規則。 你可以在左上方的介面找到更多資訊。 地圖時間 __2__。\n下次地圖重設: __1__
|
|
||||||
welcome-roles=我們有自訂的身份組來保護其他用戶。 所以你有機會不能使用拆除規劃器或掉東西。 身份組給予你各種權限\n你有以下身份組: __1__
|
|
||||||
welcome-chat=你可以使用 「重音符/抑音符」 鍵 (ESC 鍵 下方) - 你也可以在選項中修改按鍵。\n你目前設定為 「__CONTROL__toggle-console__」
|
|
||||||
rules-tab=規則
|
|
||||||
rules-tooltip=服務器規則
|
|
||||||
rules-general=你在本服務器遊玩即等同同意以下規則。管理員有機會就相關事項提問。你可能會因為違反規則而被封禁,你可以在 Discord 中提出反對。
|
|
||||||
rules-1=開掛或濫用漏洞將不會被容忍。
|
|
||||||
rules-2=當你找到漏洞可以通知職員。
|
|
||||||
rules-3=請在聊天室中尊重其他人。
|
|
||||||
rules-4=請不要把所有物品全部取走。
|
|
||||||
rules-5=請不要濫發信息,大量鋪設地磚,或不為箱子設置上限,浪費資源。
|
|
||||||
rules-6=當你想大量拆除原有設施前請通知其他用戶。
|
|
||||||
rules-7=請不要無故使用機器人來大量鋪設或拆除地磚,濫用主動出貨箱。
|
|
||||||
rules-8=請不要隨意移動來開大量地圖,這有助減少地圖下載時間。
|
|
||||||
rules-9=請不要無故使用全圖喇叭。
|
|
||||||
rules-10=請不要無故轉動傳送帶方向,停用傳送帶,或停止生產。
|
|
||||||
rules-11=請不要用迴旋處。 火車站尾返程則是例外。
|
|
||||||
rules-12=請跟其他人使用相同的火車組成。常用的有 1-2-1,2-4-2,或 3-8-3,即1個動車組拖2個貨車組再拖不同方向的1個動車組。
|
|
||||||
rules-13=火車必須是左上右下 (LHD)。
|
|
||||||
rules-14=禁止要求身份組,發廣告,發惡意連結。
|
|
||||||
rules-15=使用常識, 舉報違規人員,管理員有最終決定權。
|
|
||||||
commands-tab=指令
|
|
||||||
commands-tooltip=你可用的指令
|
|
||||||
commands-general=這裏有自訂的指令。你可以在下邊找到你可用的指令及其功能介紹。你可以用 /search-help 來取得更多資訊。
|
|
||||||
servers-tab=伺服器
|
|
||||||
servers-tooltip=到其他伺服器及網站的連結
|
|
||||||
servers-general=這是其中一個伺服器。你可以在下方找到其他同樣是由我們運行的伺服器資料。
|
|
||||||
servers-factorio=異星工廠伺服器
|
|
||||||
servers-1=S1 公共
|
|
||||||
servers-d1=這個服務器每兩日重設。
|
|
||||||
servers-2=S2 -
|
|
||||||
servers-d2=這個服務器沒在營運。
|
|
||||||
servers-3=S3 周
|
|
||||||
servers-d3=這個服務器每週重設。
|
|
||||||
servers-4=S4 月
|
|
||||||
servers-d4=這個服務器每月重設。
|
|
||||||
servers-5=S5 模組
|
|
||||||
servers-d5=這個服務器運行模組,可在 Discord 中得到更多資訊。
|
|
||||||
servers-6=S6 Donator
|
|
||||||
servers-d6=這個服務器為支持者運行,可在 Discord 中得到更多資訊。
|
|
||||||
servers-7=S7 項目
|
|
||||||
servers-d7=這個服務器只在有項目期間運行。
|
|
||||||
servers-8=S8 T̷-̶s̶-̴:̷
|
|
||||||
servers-d8=N̵o̴ ̶o̷-̶e̵ ̴k̸n̷-̶w̵s̸ ̴w̷h̷a̶-̶ ̷h̴a̴p̷p̴e̷n̷s̸ ̷o̶n̴ ̷t̶h̴-̶s̶ ̷s̷e̶r̸v̸e̴r̷,̶ ̸i̸t̴ ̷m̶-̸g̴h̶t̷ ̸n̸-̶t̵ ̷e̴v̸e̸n̶t̷ ̵-̷x̴i̵s̶t̸。̸
|
|
||||||
servers-connect-Offline=該服務器目前沒有在運行
|
|
||||||
servers-connect-Current=你目前所在的伺服器
|
|
||||||
servers-connect-Version=該服務器運行不同的遊戲版本: __1__
|
|
||||||
servers-connect-Password=該服務器需要密碼
|
|
||||||
servers-connect-Modded=該服務器需要下載模組
|
|
||||||
servers-connect-Online=該服務器在線
|
|
||||||
servers-external=外部連結
|
|
||||||
servers-open-in-browser=可在你的瀏覽器中打開
|
|
||||||
backers-tab=支持者
|
|
||||||
backers-tooltip=支持者
|
|
||||||
backers-general=他們為服務器運行提供了不少幫助。
|
|
||||||
backers-management=系統管理員
|
|
||||||
backers-board=議員
|
|
||||||
backers-staff=職員
|
|
||||||
backers-backers=支持者
|
|
||||||
backers-active=活躍玩家
|
|
||||||
data-tab=資料
|
|
||||||
data-tooltip=所有已存資料
|
|
||||||
data-general=資料會自動存到各個服務器中。 你也可以用 /save-data 存一份副本。 也可用 /set-preference 來更改喜好。
|
|
||||||
data-settings=設定
|
|
||||||
data-statistics=數據
|
|
||||||
data-required=需要
|
|
||||||
data-misc=雜項
|
|
||||||
data-format=__1____2__
|
|
||||||
|
|
||||||
[bonus]
|
|
||||||
description=取得 / 設定 Bonus 量。
|
|
||||||
arg-amount=Bonus 數量, 0 來停用。
|
|
||||||
set=你的 Bonus 已設為 __1__。
|
|
||||||
get=你的 Bonus 為 __1__。
|
|
||||||
perm=你沒有足夠權限設多過 __1__。
|
|
||||||
display-fmms=挖掘速度
|
|
||||||
display-fmms-tooltip=勢力挖掘速度
|
|
||||||
display-frs=跑步速度
|
|
||||||
display-frs-tooltip=勢力跑步速度
|
|
||||||
display-fcs=合成速度
|
|
||||||
display-fcs-tooltip=勢力合成速度
|
|
||||||
display-fisb=儲存位
|
|
||||||
display-fisb-tooltip=勢力儲存位
|
|
||||||
display-fhb=生命
|
|
||||||
display-fhb-tooltip=勢力生命
|
|
||||||
display-frdb=到達距離
|
|
||||||
display-frdb-tooltip=勢力到達距離
|
|
||||||
display-fwrsm=機械人速度
|
|
||||||
display-fwrsm-tooltip=勢力機械人速度
|
|
||||||
display-fwrbm=機械人電力
|
|
||||||
display-fwrbm-tooltip=勢力機械人電力
|
|
||||||
display-fwrsb=械人儲存
|
|
||||||
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 功能。
|
|
||||||
enter=現在進入 vlayer 區域選擇
|
|
||||||
exit=已進入 vlayer 區域選擇
|
|
||||||
|
|
||||||
[toolbar]
|
|
||||||
main-caption=工具箱
|
|
||||||
main-tooltip=工具箱設定\n選上來設定喜好
|
|
||||||
reset=重設
|
|
||||||
toggle=啟用喜好
|
|
||||||
move-up=向上
|
|
||||||
move-down=向下
|
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clusterio": "*",
|
"clusterio": "*",
|
||||||
"exp_util": "*",
|
"exp_util": "*",
|
||||||
|
"exp_roles": "*",
|
||||||
"exp_gui": "*"
|
"exp_gui": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
--[[-- Control Module - Jail
|
|
||||||
- Adds a way to jail players.
|
|
||||||
@control Jail
|
|
||||||
@alias Jail
|
|
||||||
|
|
||||||
@usage
|
|
||||||
-- import the module from the control modules
|
|
||||||
local Jail = require("modules.exp_legacy.modules.control.jail") --- @dep modules.control.jail
|
|
||||||
|
|
||||||
-- This will move 'MrBiter' to the jail role and remove all other roles from them
|
|
||||||
-- the player name and reason are only so they can be included in the event for user feedback
|
|
||||||
Jail.jail_player('MrBiter', 'Cooldude2606', 'Likes biters too much')
|
|
||||||
|
|
||||||
-- This will give 'MrBiter' all his roles back and remove him from jail
|
|
||||||
-- again as above the player name is only used in the event for user feedback
|
|
||||||
Jail.unjail_player('MrBiter', 'Cooldude2606')
|
|
||||||
]]
|
|
||||||
|
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
|
||||||
|
|
||||||
local valid_player = function(p) return type(p) == "userdata" and p or game.get_player(p) end
|
|
||||||
local assign_roles = Roles.assign_player
|
|
||||||
local unassign_roles = Roles.unassign_player
|
|
||||||
local has_role = Roles.player_has_role
|
|
||||||
local get_roles = Roles.get_player_roles
|
|
||||||
|
|
||||||
local Jail = {
|
|
||||||
old_roles = {},
|
|
||||||
events = {
|
|
||||||
--- When a player is assigned to jail
|
|
||||||
-- @event on_player_jailed
|
|
||||||
-- @tparam number player_index the index of the player who was jailed
|
|
||||||
-- @tparam string by_player_name the name of the player who jailed the other player
|
|
||||||
-- @tparam string reason the reason that the player was jailed
|
|
||||||
on_player_jailed = script.generate_event_name(),
|
|
||||||
--- When a player is unassigned from jail
|
|
||||||
-- @event on_player_unjailed
|
|
||||||
-- @tparam number player_index the index of the player who was unjailed
|
|
||||||
-- @tparam string by_player_name the name of the player who unjailed the other player
|
|
||||||
on_player_unjailed = script.generate_event_name(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
--- Used to emit the jail related events
|
|
||||||
-- @tparam number event the name of the event that will be emited
|
|
||||||
-- @tparam LuaPlayer player the player who is being acted on
|
|
||||||
-- @tparam string by_player_name the player who is doing the action
|
|
||||||
-- @tparam string reason the reason for the action (jail)
|
|
||||||
local function event_emit(event, player, by_player_name, reason)
|
|
||||||
script.raise_event(event, {
|
|
||||||
name = event,
|
|
||||||
tick = game.tick,
|
|
||||||
player_index = player.index,
|
|
||||||
by_player_name = by_player_name,
|
|
||||||
reason = reason,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Jail.
|
|
||||||
-- Functions related to jail
|
|
||||||
-- @section jail-functions
|
|
||||||
|
|
||||||
--- Checks if the player is currently in jail
|
|
||||||
-- @tparam LuaPlayer player the player to check if they are in jail
|
|
||||||
-- @treturn boolean whether the player is currently in jail
|
|
||||||
function Jail.is_jailed(player)
|
|
||||||
return has_role(valid_player(player), "Jail")
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Moves a player to jail and removes all other roles
|
|
||||||
-- @tparam LuaPlayer player the player who will be jailed
|
|
||||||
-- @tparam string by_player_name the name of the player who is doing the jailing
|
|
||||||
-- @tparam[opt='Non given.'] string reason the reason that the player is being jailed
|
|
||||||
-- @treturn boolean wheather the user was jailed successfully
|
|
||||||
function Jail.jail_player(player, by_player_name, reason)
|
|
||||||
player = valid_player(player)
|
|
||||||
if not player then return end
|
|
||||||
if not by_player_name then return end
|
|
||||||
|
|
||||||
reason = reason or "Non given."
|
|
||||||
|
|
||||||
if has_role(player, "Jail") then return end
|
|
||||||
local roles = get_roles(player)
|
|
||||||
|
|
||||||
player.walking_state = { walking = false, direction = player.walking_state.direction }
|
|
||||||
player.riding_state = { acceleration = defines.riding.acceleration.nothing, direction = player.riding_state.direction }
|
|
||||||
player.mining_state = { mining = false }
|
|
||||||
player.shooting_state = { state = defines.shooting.not_shooting, position = player.shooting_state.position }
|
|
||||||
player.picking_state = false
|
|
||||||
player.repair_state = { repairing = false, position = player.repair_state.position }
|
|
||||||
|
|
||||||
unassign_roles(player, roles, by_player_name, nil, true)
|
|
||||||
assign_roles(player, "Jail", by_player_name, nil, true)
|
|
||||||
assign_roles(player, roles, by_player_name, nil, true)
|
|
||||||
|
|
||||||
event_emit(Jail.events.on_player_jailed, player, by_player_name, reason)
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Moves a player out of jail and restores all roles previously removed
|
|
||||||
-- @tparam LuaPlayer player the player that will be unjailed
|
|
||||||
-- @tparam string by_player_name the name of the player that is doing the unjail
|
|
||||||
-- @treturn boolean whether the player was unjailed successfully
|
|
||||||
function Jail.unjail_player(player, by_player_name)
|
|
||||||
player = valid_player(player)
|
|
||||||
if not player then return end
|
|
||||||
if not by_player_name then return end
|
|
||||||
|
|
||||||
if not has_role(player, "Jail") then return end
|
|
||||||
|
|
||||||
unassign_roles(player, "Jail", by_player_name, nil, true)
|
|
||||||
|
|
||||||
event_emit(Jail.events.on_player_unjailed, player, by_player_name)
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
return Jail
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
--[[-- Control Module - Protection
|
|
||||||
- Controls protected entities
|
|
||||||
@control Protection
|
|
||||||
@alias Protection
|
|
||||||
]]
|
|
||||||
|
|
||||||
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.protection") --- @dep config.protection
|
|
||||||
local EntityProtection = {
|
|
||||||
protected_entity_names = table.deep_copy(config.always_protected_names),
|
|
||||||
protected_entity_types = table.deep_copy(config.always_protected_types),
|
|
||||||
events = {
|
|
||||||
--- When a player mines a protected entity
|
|
||||||
-- @event on_player_mined_protected
|
|
||||||
-- @tparam number player_index the player index of the player who got mined the entity
|
|
||||||
-- @tparam LuaEntity entity the entity which was mined
|
|
||||||
on_player_mined_protected = script.generate_event_name(),
|
|
||||||
--- When a player repeatedly mines protected entities
|
|
||||||
-- @event on_repeat_violation
|
|
||||||
-- @tparam number player_index the player index of the player who got mined the entities
|
|
||||||
-- @tparam LuaEntity entity the last entity which was mined
|
|
||||||
on_repeat_violation = script.generate_event_name(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Convert config tables into lookup tables
|
|
||||||
for _, config_key in ipairs{ "always_protected_names", "always_protected_types", "always_trigger_repeat_names", "always_trigger_repeat_types" } do
|
|
||||||
local tbl = config[config_key]
|
|
||||||
for key, value in ipairs(tbl) do
|
|
||||||
tbl[key] = nil
|
|
||||||
tbl[value] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Require roles if a permission is assigned in the config
|
|
||||||
local Roles
|
|
||||||
if config.ignore_permission then
|
|
||||||
Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
|
|
||||||
end
|
|
||||||
|
|
||||||
----- Storage Variables -----
|
|
||||||
--- Variables stored in the global table
|
|
||||||
|
|
||||||
local protected_entities = {} -- All entities which are protected
|
|
||||||
local protected_areas = {} -- All areas which are protected
|
|
||||||
local repeats = {} -- Stores repeat removals by players
|
|
||||||
|
|
||||||
Storage.register({
|
|
||||||
protected_entities = protected_entities,
|
|
||||||
protected_areas = protected_areas,
|
|
||||||
repeats = repeats,
|
|
||||||
}, function(tbl)
|
|
||||||
protected_entities = tbl.protected_entities
|
|
||||||
protected_areas = tbl.protected_areas
|
|
||||||
repeats = tbl.repeats
|
|
||||||
end)
|
|
||||||
|
|
||||||
----- Local Functions -----
|
|
||||||
--- Functions used internally to search and add to the protected array
|
|
||||||
|
|
||||||
--- Get the key used in protected_entities
|
|
||||||
local function get_entity_key(entity)
|
|
||||||
return string.format("%i,%i", math.floor(entity.position.x), math.floor(entity.position.y))
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Get the key used in protected_areas
|
|
||||||
local function get_area_key(area)
|
|
||||||
return string.format("%i,%i", math.floor(area.left_top.x), math.floor(area.left_top.y))
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Check if an entity is always protected
|
|
||||||
local function check_always_protected(entity)
|
|
||||||
return config.always_protected_names[entity.name] or config.always_protected_types[entity.type] or false
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Check if an entity always triggers repeat protection
|
|
||||||
local function check_always_trigger_repeat(entity)
|
|
||||||
return config.always_trigger_repeat_names[entity.name] or config.always_trigger_repeat_types[entity.type] or false
|
|
||||||
end
|
|
||||||
|
|
||||||
----- Public Functions -----
|
|
||||||
--- Functions used to add and remove protected entities
|
|
||||||
|
|
||||||
--- Add an entity to the protected list
|
|
||||||
function EntityProtection.add_entity(entity)
|
|
||||||
local entities = protected_entities[entity.surface.index]
|
|
||||||
if not entities then
|
|
||||||
entities = {}
|
|
||||||
protected_entities[entity.surface.index] = entities
|
|
||||||
end
|
|
||||||
entities[get_entity_key(entity)] = entity
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Remove an entity from the protected list
|
|
||||||
function EntityProtection.remove_entity(entity)
|
|
||||||
local entities = protected_entities[entity.surface.index]
|
|
||||||
if not entities then return end
|
|
||||||
entities[get_entity_key(entity)] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Get all protected entities on a surface
|
|
||||||
function EntityProtection.get_entities(surface)
|
|
||||||
return protected_entities[surface.index] or {}
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Check if an entity is protected
|
|
||||||
function EntityProtection.is_entity_protected(entity)
|
|
||||||
if check_always_protected(entity) then return true end
|
|
||||||
local entities = protected_entities[entity.surface.index]
|
|
||||||
if not entities then return false end
|
|
||||||
return entities[get_entity_key(entity)] == entity
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Add an area to the protected list
|
|
||||||
function EntityProtection.add_area(surface, area)
|
|
||||||
local areas = protected_areas[surface.index]
|
|
||||||
if not areas then
|
|
||||||
areas = {}
|
|
||||||
protected_areas[surface.index] = areas
|
|
||||||
end
|
|
||||||
areas[get_area_key(area)] = area
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Remove an area from the protected list
|
|
||||||
function EntityProtection.remove_area(surface, area)
|
|
||||||
local areas = protected_areas[surface.index]
|
|
||||||
if not areas then return end
|
|
||||||
areas[get_area_key(area)] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Get all protected areas on a surface
|
|
||||||
function EntityProtection.get_areas(surface)
|
|
||||||
return protected_areas[surface.index] or {}
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Check if an entity is protected
|
|
||||||
function EntityProtection.is_position_protected(surface, position)
|
|
||||||
local areas = protected_areas[surface.index]
|
|
||||||
if not areas then return false end
|
|
||||||
for _, area in pairs(areas) do
|
|
||||||
if area.left_top.x <= position.x and area.left_top.y <= position.y
|
|
||||||
and area.right_bottom.x >= position.x and area.right_bottom.y >= position.y
|
|
||||||
then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
----- Events -----
|
|
||||||
--- All events registered by this module
|
|
||||||
|
|
||||||
--- Raise events for protected entities
|
|
||||||
Event.add(defines.events.on_pre_player_mined_item, function(event)
|
|
||||||
local entity = event.entity
|
|
||||||
local player = game.players[event.player_index]
|
|
||||||
-- Check if the player should be ignored
|
|
||||||
if config.ignore_admins and player.admin then return end
|
|
||||||
if entity.last_user == nil or entity.last_user.index == player.index then return end
|
|
||||||
if config.ignore_permission and Roles.player_allowed(player, config.ignore_permission) then return end
|
|
||||||
|
|
||||||
-- Check if the entity is protected
|
|
||||||
if EntityProtection.is_entity_protected(entity)
|
|
||||||
or EntityProtection.is_position_protected(entity.surface, entity.position)
|
|
||||||
then
|
|
||||||
-- Update repeats
|
|
||||||
local player_repeats = repeats[player.name]
|
|
||||||
if not player_repeats then
|
|
||||||
player_repeats = { last = game.tick, count = 0 }
|
|
||||||
repeats[player.name] = player_repeats
|
|
||||||
end
|
|
||||||
player_repeats.last = game.tick
|
|
||||||
player_repeats.count = player_repeats.count + 1
|
|
||||||
-- Send events
|
|
||||||
event.name = EntityProtection.events.on_player_mined_protected
|
|
||||||
script.raise_event(EntityProtection.events.on_player_mined_protected, event)
|
|
||||||
if check_always_trigger_repeat(entity) or player_repeats.count >= config.repeat_count then
|
|
||||||
player_repeats.count = 0 -- Reset to avoid spamming of events
|
|
||||||
event.name = EntityProtection.events.on_repeat_violation
|
|
||||||
script.raise_event(EntityProtection.events.on_repeat_violation, event)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
--- Remove old repeats
|
|
||||||
Event.on_nth_tick(config.refresh_rate, function()
|
|
||||||
local old = game.tick - config.repeat_lifetime
|
|
||||||
for player_name, player_repeats in pairs(repeats) do
|
|
||||||
if player_repeats.last <= old then
|
|
||||||
repeats[player_name] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
--- When an entity is removed remove it from the protection list
|
|
||||||
local function event_remove_entity(event)
|
|
||||||
EntityProtection.remove_entity(event.entity)
|
|
||||||
end
|
|
||||||
|
|
||||||
Event.add(defines.events.on_space_platform_pre_mined, event_remove_entity)
|
|
||||||
Event.add(defines.events.on_pre_player_mined_item, event_remove_entity)
|
|
||||||
Event.add(defines.events.on_robot_pre_mined, event_remove_entity)
|
|
||||||
Event.add(defines.events.on_entity_died, event_remove_entity)
|
|
||||||
Event.add(defines.events.script_raised_destroy, event_remove_entity)
|
|
||||||
|
|
||||||
return EntityProtection
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
--[[-- Control Module - Rockets
|
|
||||||
- Stores rocket stats for each force.
|
|
||||||
@control Rockets
|
|
||||||
@alias Rockets
|
|
||||||
|
|
||||||
@usage
|
|
||||||
-- import the module from the control modules
|
|
||||||
local Rockets = require("modules.exp_legacy.modules.control.rockets") --- @dep modules.control.rockets
|
|
||||||
|
|
||||||
-- Some basic information is stored for each silo that has been built
|
|
||||||
-- the data includes: the tick it was built, the rockets launched from it and more
|
|
||||||
Rockets.get_silo_data(rocket_silo_entity)
|
|
||||||
|
|
||||||
-- Some information is also stored for each force
|
|
||||||
Rockets.get_stats('player')
|
|
||||||
|
|
||||||
-- You can get the rocket data for all silos for a force by using get_silos
|
|
||||||
Rockets.get_silos('player')
|
|
||||||
|
|
||||||
-- You can get the launch time for a rocket, meaning what game tick the 50th rocket was launched
|
|
||||||
Rockets.get_rocket_time('player', 50)
|
|
||||||
|
|
||||||
-- The rolling average will work out the time to launch one rocket based on the last X rockets
|
|
||||||
Rockets.get_rolling_average('player', 10)
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
|
|
||||||
local Storage = require("modules/exp_util/storage")
|
|
||||||
local config = require("modules.exp_legacy.config.gui.rockets") --- @dep config.rockets
|
|
||||||
|
|
||||||
local largest_rolling_avg = 0
|
|
||||||
for _, avg_over in pairs(config.stats.rolling_avg) do
|
|
||||||
if avg_over > largest_rolling_avg then
|
|
||||||
largest_rolling_avg = avg_over
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local Rockets = {
|
|
||||||
times = {},
|
|
||||||
stats = {},
|
|
||||||
silos = {},
|
|
||||||
}
|
|
||||||
|
|
||||||
local rocket_times = Rockets.times
|
|
||||||
local rocket_stats = Rockets.stats
|
|
||||||
local rocket_silos = Rockets.silos
|
|
||||||
Storage.register({
|
|
||||||
rocket_times = rocket_times,
|
|
||||||
rocket_stats = rocket_stats,
|
|
||||||
rocket_silos = rocket_silos,
|
|
||||||
}, function(tbl)
|
|
||||||
Rockets.times = tbl.rocket_times
|
|
||||||
Rockets.stats = tbl.rocket_stats
|
|
||||||
Rockets.silos = tbl.rocket_silos
|
|
||||||
rocket_times = Rockets.times
|
|
||||||
rocket_stats = Rockets.stats
|
|
||||||
rocket_silos = Rockets.silos
|
|
||||||
end)
|
|
||||||
|
|
||||||
--- Gets the silo data for a given silo entity
|
|
||||||
--- @param silo LuaEntity Rocket silo entity
|
|
||||||
--- @return table # Data table for this silo, contains rockets launch, silo status, and its force
|
|
||||||
function Rockets.get_silo_data(silo)
|
|
||||||
return rocket_silos[silo.unit_number]
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets the silo entity from its silo name, reverse to get_silo_data
|
|
||||||
--- @param silo_name string Silo name that is stored in its data
|
|
||||||
--- @return LuaEntity # Rocket silo entity
|
|
||||||
function Rockets.get_silo_entity(silo_name)
|
|
||||||
return rocket_silos[tonumber(silo_name)].entity
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets the rocket stats for a force
|
|
||||||
--- @param force_name string Name of the force to get the stats for
|
|
||||||
--- @return table # Stats for the force
|
|
||||||
function Rockets.get_stats(force_name)
|
|
||||||
return rocket_stats[force_name] or {}
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets all the rocket silos that belong to a force
|
|
||||||
--- @param force_name string Name of the force to get the silos for
|
|
||||||
--- @return table # Array of silo data that all belong to this force
|
|
||||||
function Rockets.get_silos(force_name)
|
|
||||||
local rtn = {}
|
|
||||||
for _, silo_data in pairs(rocket_silos) do
|
|
||||||
if silo_data.force == force_name then
|
|
||||||
table.insert(rtn, silo_data)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return rtn
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets the launch time of a given rocket, due to cleaning not all counts are valid
|
|
||||||
--- @param force_name string Name of the force to get the count for
|
|
||||||
--- @param rocket_number number Number of the rocket to get the launch time for
|
|
||||||
--- @return number? # Game tick that the rocket was launched on
|
|
||||||
function Rockets.get_rocket_time(force_name, rocket_number)
|
|
||||||
return rocket_times[force_name] and rocket_times[force_name][rocket_number] or nil
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets the number of rockets that a force has launched
|
|
||||||
--- @param force_name string the name of the force to get the count for
|
|
||||||
--- @return number # Number of rockets that the force has launched
|
|
||||||
function Rockets.get_rocket_count(force_name)
|
|
||||||
local force = game.forces[force_name]
|
|
||||||
return force.rockets_launched
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets the total number of rockets launched by all forces
|
|
||||||
--- @return number # Total number of rockets launched this game
|
|
||||||
function Rockets.get_game_rocket_count()
|
|
||||||
local rtn = 0
|
|
||||||
for _, force in pairs(game.forces) do
|
|
||||||
rtn = rtn + force.rockets_launched
|
|
||||||
end
|
|
||||||
|
|
||||||
return rtn
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets the rolling average time to launch a rocket
|
|
||||||
--- @param force_name string Name of the force to get the average for
|
|
||||||
--- @param count number Distance to get the rolling average over
|
|
||||||
--- @return number # Number of ticks required to launch one rocket
|
|
||||||
function Rockets.get_rolling_average(force_name, count)
|
|
||||||
local force = game.forces[force_name]
|
|
||||||
local rocket_count = force.rockets_launched
|
|
||||||
if rocket_count == 0 then return 0 end
|
|
||||||
local last_launch_time = rocket_times[force_name][rocket_count]
|
|
||||||
local start_rocket_time = 0
|
|
||||||
if count < rocket_count then
|
|
||||||
start_rocket_time = rocket_times[force_name][rocket_count - count + 1]
|
|
||||||
rocket_count = count
|
|
||||||
end
|
|
||||||
return math.floor((last_launch_time - start_rocket_time) / rocket_count)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- When a launch is trigger it will wait for the silo to reset
|
|
||||||
--- @param event EventData.on_rocket_launch_ordered
|
|
||||||
Event.add(defines.events.on_rocket_launch_ordered, function(event)
|
|
||||||
local silo_data = Rockets.get_silo_data(event.rocket_silo)
|
|
||||||
assert(silo_data, "Rocket silo missing data: " .. tostring(event.rocket_silo))
|
|
||||||
silo_data.launched = silo_data.launched + 1
|
|
||||||
silo_data.awaiting_reset = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
--- Event used to update the stats and the hui when a rocket is launched
|
|
||||||
--- @param event EventData.on_cargo_pod_finished_ascending
|
|
||||||
Event.add(defines.events.on_cargo_pod_finished_ascending, function(event)
|
|
||||||
local force = event.cargo_pod.force
|
|
||||||
local force_name = force.name
|
|
||||||
local rockets_launched = force.rockets_launched
|
|
||||||
|
|
||||||
--- Handles updates to the rocket stats
|
|
||||||
local stats = rocket_stats[force_name]
|
|
||||||
if not stats then
|
|
||||||
rocket_stats[force_name] = {}
|
|
||||||
stats = rocket_stats[force_name]
|
|
||||||
end
|
|
||||||
|
|
||||||
if rockets_launched == 1 then
|
|
||||||
stats.first_launch = event.tick
|
|
||||||
stats.fastest_launch = event.tick
|
|
||||||
elseif event.tick - stats.last_launch < stats.fastest_launch then
|
|
||||||
stats.fastest_launch = event.tick - stats.last_launch
|
|
||||||
end
|
|
||||||
|
|
||||||
stats.last_launch = event.tick
|
|
||||||
|
|
||||||
--- Appends the new rocket into the array
|
|
||||||
if not rocket_times[force_name] then
|
|
||||||
rocket_times[force_name] = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
rocket_times[force_name][rockets_launched] = event.tick
|
|
||||||
|
|
||||||
local remove_rocket = rockets_launched - largest_rolling_avg
|
|
||||||
if remove_rocket > 0 and not table.contains(config.milestones, remove_rocket) then
|
|
||||||
rocket_times[force_name][remove_rocket] = nil
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
--- Adds a silo to the list when it is built
|
|
||||||
--- @param event EventData.on_built_entity | EventData.on_robot_built_entity
|
|
||||||
local function on_built(event)
|
|
||||||
local entity = event.entity
|
|
||||||
if entity.valid and entity.name == "rocket-silo" then
|
|
||||||
rocket_silos[entity.unit_number] = {
|
|
||||||
name = tostring(entity.unit_number),
|
|
||||||
force = entity.force.name,
|
|
||||||
entity = entity,
|
|
||||||
launched = 0,
|
|
||||||
awaiting_reset = false,
|
|
||||||
built = game.tick,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Event.add(defines.events.on_built_entity, on_built)
|
|
||||||
Event.add(defines.events.on_robot_built_entity, on_built)
|
|
||||||
|
|
||||||
return Rockets
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
local Storage = require("modules/exp_util/storage")
|
|
||||||
local Gui = require("modules/exp_gui")
|
|
||||||
|
|
||||||
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
|
|
||||||
|
|
||||||
----- Locals -----
|
|
||||||
local follow_label -- Gui constructor
|
|
||||||
local following = {}
|
|
||||||
local spectating = {}
|
|
||||||
local Public = {}
|
|
||||||
|
|
||||||
----- Storage data -----
|
|
||||||
Storage.register({
|
|
||||||
following = following,
|
|
||||||
spectating = spectating,
|
|
||||||
}, function(tbl)
|
|
||||||
following = tbl.following
|
|
||||||
spectating = tbl.spectating
|
|
||||||
end)
|
|
||||||
|
|
||||||
----- Public Functions -----
|
|
||||||
|
|
||||||
--- Test if a player is in spectator mode
|
|
||||||
-- @tparam LuaPlayer player The player to test the controller type of
|
|
||||||
-- @treturn boolean Returns true if the player is in spectator mode
|
|
||||||
function Public.is_spectating(player)
|
|
||||||
assert(player and player.valid, "Invalid player given to follower")
|
|
||||||
return player.controller_type == defines.controllers.spectator
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Puts a player into spectator mode while maintaining an association to their character
|
|
||||||
-- @tparam LuaPlayer player The player that will be placed into spectator mode
|
|
||||||
-- @treturn boolean Returns false if the player was already in spectator mode
|
|
||||||
function Public.start_spectate(player)
|
|
||||||
assert(player and player.valid, "Invalid player given to follower")
|
|
||||||
if spectating[player.index] or not player.character then return false end
|
|
||||||
local character = player.character
|
|
||||||
local opened = player.opened
|
|
||||||
player.set_controller{ type = defines.controllers.spectator }
|
|
||||||
player.associate_character(character)
|
|
||||||
spectating[player.index] = character
|
|
||||||
if opened then player.opened = opened end -- Maintain opened after controller change
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Return a player from spectator mode back to their character, if their character was killed then respawn them
|
|
||||||
-- @tparam LuaPlayer player The player that will leave spectator mode
|
|
||||||
function Public.stop_spectate(player)
|
|
||||||
assert(player and player.valid, "Invalid player given to follower")
|
|
||||||
local character = spectating[player.index]
|
|
||||||
spectating[player.index] = nil
|
|
||||||
if character and character.valid then
|
|
||||||
local opened = player.opened
|
|
||||||
player.teleport(character.position, character.surface)
|
|
||||||
player.set_controller{ type = defines.controllers.character, character = character }
|
|
||||||
if opened then player.opened = opened end -- Maintain opened after controller change
|
|
||||||
else
|
|
||||||
player.ticks_to_respawn = 300
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Test if a player is in follow mode
|
|
||||||
-- @tparam LuaPlayer player The player to test the follow mode of
|
|
||||||
-- @treturn boolean Returns true if the player is in follow mode
|
|
||||||
function Public.is_following(player)
|
|
||||||
assert(player and player.valid, "Invalid player given to follower")
|
|
||||||
return following[player.index] ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Puts a player into spectator mode and follows an entity as it moves
|
|
||||||
-- @tparam LuaPlayer player The player that will follow the entity
|
|
||||||
-- @tparam ?LuaPlayer|LuaEntity entity The player or entity that will be followed
|
|
||||||
function Public.start_follow(player, entity)
|
|
||||||
assert(player and player.valid, "Invalid player given to follower")
|
|
||||||
assert(entity and entity.valid, "Invalid entity given to follower")
|
|
||||||
local spectate = Public.start_spectate(player)
|
|
||||||
|
|
||||||
player.close_map()
|
|
||||||
follow_label(player.gui.screen, entity)
|
|
||||||
player.teleport(entity.position, entity.surface)
|
|
||||||
following[player.index] = { player, entity, entity.position, spectate }
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns camera control to the player, will return a player to their character if start_follow placed them into spectator mode
|
|
||||||
-- @tparam LuaPlayer player The player that will regain control of their camera
|
|
||||||
function Public.stop_follow(player)
|
|
||||||
assert(player and player.valid, "Invalid player given to follower")
|
|
||||||
if following[player.index] and following[player.index][4] and Public.is_spectating(player) then
|
|
||||||
Public.stop_spectate(player)
|
|
||||||
end
|
|
||||||
|
|
||||||
Gui.destroy_if_valid(player.gui.screen.follow_label)
|
|
||||||
following[player.index] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns camera control to all players, will return a player to their character if start_follow placed them into spectator mode
|
|
||||||
function Public.stop_all()
|
|
||||||
for key, data in pairs(following) do
|
|
||||||
Public.stop_follow(data[1])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
----- Gui -----
|
|
||||||
|
|
||||||
--- Label used to show that the player is following, also used to allow esc to stop following
|
|
||||||
follow_label = Gui.define("follow-label")
|
|
||||||
:draw(function(def, parent, target)
|
|
||||||
Gui.destroy_if_valid(parent.follow_label)
|
|
||||||
|
|
||||||
local label = parent.add{
|
|
||||||
type = "label",
|
|
||||||
name = "follow_label",
|
|
||||||
style = "frame_title",
|
|
||||||
caption = "Following " .. target.name .. ".\nClick here or press esc to stop following.",
|
|
||||||
}
|
|
||||||
|
|
||||||
local player = Gui.get_player(parent)
|
|
||||||
local res = player.display_resolution
|
|
||||||
label.location = { 0, res.height - 150 }
|
|
||||||
label.style.width = res.width
|
|
||||||
label.style.horizontal_align = "center"
|
|
||||||
player.opened = label
|
|
||||||
|
|
||||||
return label
|
|
||||||
end)
|
|
||||||
:on_click(function(def, player, element)
|
|
||||||
Public.stop_follow(player)
|
|
||||||
end)
|
|
||||||
:on_closed(function(def, player, element)
|
|
||||||
-- Don't call set_controller during on_close as it invalidates the controller
|
|
||||||
-- Setting an invalid position (as to not equal their current) will call stop_follow on the next tick
|
|
||||||
following[player.index][3] = {}
|
|
||||||
end)
|
|
||||||
|
|
||||||
----- Events -----
|
|
||||||
|
|
||||||
--- Updates the location of the player as well as doing some sanity checks
|
|
||||||
-- @tparam LuaPlayer player The player to update the position of
|
|
||||||
-- @tparam ?LuaPlayer|LuaEntity entity The player or entity being followed
|
|
||||||
local function update_player_location(player, entity, old_position)
|
|
||||||
if player.character or not entity.valid then
|
|
||||||
Public.stop_follow(player)
|
|
||||||
elseif player.position.x ~= old_position.x or player.position.y ~= old_position.y then
|
|
||||||
Public.stop_follow(player)
|
|
||||||
else
|
|
||||||
player.teleport(entity.position, entity.surface)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Updates the locations of all players currently following something
|
|
||||||
local function update_all()
|
|
||||||
for _, data in pairs(following) do
|
|
||||||
update_player_location(data[1], data[2], data[3])
|
|
||||||
data[3] = data[1].position
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Update the location of all players each tick
|
|
||||||
Event.add(defines.events.on_tick, update_all)
|
|
||||||
|
|
||||||
-- Check for player leaving
|
|
||||||
Event.add(defines.events.on_pre_player_left_game, function(event)
|
|
||||||
local player = game.players[event.player_index]
|
|
||||||
Public.stop_follow(player)
|
|
||||||
for _, data in pairs(following) do
|
|
||||||
if data[2] == player then
|
|
||||||
Public.stop_follow(data[1])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
----- Module Return -----
|
|
||||||
return Public
|
|
||||||
@@ -14,10 +14,10 @@ local mega = 1000000
|
|||||||
local vlayer = {}
|
local vlayer = {}
|
||||||
local vlayer_data = {
|
local vlayer_data = {
|
||||||
entity_interfaces = {
|
entity_interfaces = {
|
||||||
energy = {},
|
energy = {}, --- @type LuaEntity[]
|
||||||
circuit = {},
|
circuit = {}, --- @type LuaEntity[]
|
||||||
storage_input = {},
|
storage_input = {}, --- @type LuaEntity[]
|
||||||
storage_output = {},
|
storage_output = {}, --- @type LuaEntity[]
|
||||||
},
|
},
|
||||||
properties = {
|
properties = {
|
||||||
total_surface_area = 0,
|
total_surface_area = 0,
|
||||||
@@ -28,10 +28,10 @@ local vlayer_data = {
|
|||||||
capacity = 0,
|
capacity = 0,
|
||||||
},
|
},
|
||||||
storage = {
|
storage = {
|
||||||
items = {},
|
items = {}, --- @type table<string, number>
|
||||||
power_items = {},
|
power_items = {}, --- @type table<string, { value: number, count: number }>
|
||||||
energy = 0,
|
energy = 0,
|
||||||
unallocated = {},
|
unallocated = {}, --- @type table<string, number>
|
||||||
},
|
},
|
||||||
surface = table.deep_copy(config.surface),
|
surface = table.deep_copy(config.surface),
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ for name, properties in pairs(config.allowed_items) do
|
|||||||
|
|
||||||
if properties.power then
|
if properties.power then
|
||||||
vlayer_data.storage.power_items[name] = {
|
vlayer_data.storage.power_items[name] = {
|
||||||
value = properties.fuel_value * 1000000,
|
value = assert(properties.fuel_value) * 1000000,
|
||||||
count = 0,
|
count = 0,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@@ -349,7 +349,7 @@ function vlayer.create_input_interface(surface, position, circuit, last_user)
|
|||||||
end
|
end
|
||||||
|
|
||||||
interface.destructible = false
|
interface.destructible = false
|
||||||
interface.minable = false
|
interface.minable_flag = false
|
||||||
interface.operable = true
|
interface.operable = true
|
||||||
|
|
||||||
return interface
|
return interface
|
||||||
@@ -361,7 +361,7 @@ local function handle_input_interfaces()
|
|||||||
if not interface.valid then
|
if not interface.valid then
|
||||||
vlayer_data.entity_interfaces.storage_input[index] = nil
|
vlayer_data.entity_interfaces.storage_input[index] = nil
|
||||||
else
|
else
|
||||||
local inventory = interface.get_inventory(defines.inventory.chest)
|
local inventory = assert(interface.get_inventory(defines.inventory.chest))
|
||||||
|
|
||||||
for _, v in pairs(inventory.get_contents()) do
|
for _, v in pairs(inventory.get_contents()) do
|
||||||
if config.allowed_items[v.name] then
|
if config.allowed_items[v.name] then
|
||||||
@@ -384,8 +384,9 @@ local function handle_input_interfaces()
|
|||||||
|
|
||||||
if count_deduct and count_add then
|
if count_deduct and count_add then
|
||||||
if config.allowed_items[v.name].modded then
|
if config.allowed_items[v.name].modded then
|
||||||
|
local modded_item = assert(config.modded_items[v.name])
|
||||||
if config.modded_auto_downgrade then
|
if config.modded_auto_downgrade then
|
||||||
vlayer.insert_item(config.modded_items[v.name].base_game_equivalent, count_add * config.modded_items[v.name].multiplier)
|
vlayer.insert_item(modded_item.base_game_equivalent, count_add * modded_item.multiplier)
|
||||||
else
|
else
|
||||||
vlayer.insert_item(v.name, count_add)
|
vlayer.insert_item(v.name, count_add)
|
||||||
end
|
end
|
||||||
@@ -427,7 +428,7 @@ function vlayer.create_output_interface(surface, position, circuit, last_user)
|
|||||||
end
|
end
|
||||||
|
|
||||||
interface.destructible = false
|
interface.destructible = false
|
||||||
interface.minable = false
|
interface.minable_flag = false
|
||||||
interface.operable = true
|
interface.operable = true
|
||||||
|
|
||||||
return interface
|
return interface
|
||||||
@@ -439,14 +440,14 @@ local function handle_output_interfaces()
|
|||||||
if not interface.valid then
|
if not interface.valid then
|
||||||
vlayer_data.entity_interfaces.storage_output[index] = nil
|
vlayer_data.entity_interfaces.storage_output[index] = nil
|
||||||
else
|
else
|
||||||
local inventory = interface.get_inventory(defines.inventory.chest)
|
local inventory = assert(interface.get_inventory(defines.inventory.chest))
|
||||||
local inventory_request_sections = interface.get_logistic_sections().sections
|
local inventory_request_sections = assert(interface.get_logistic_sections()).sections
|
||||||
|
|
||||||
for i = 1, #inventory_request_sections do
|
for i = 1, #inventory_request_sections do
|
||||||
for _, v in pairs(inventory_request_sections[i].filters) do
|
for _, v in pairs(inventory_request_sections[i].filters) do
|
||||||
if v.value and config.allowed_items[v.value.name] then
|
if v.value and config.allowed_items[v.value.name] then
|
||||||
local current_amount = inventory.get_item_count(v.value.name)
|
local current_amount = inventory.get_item_count(v.value.name)
|
||||||
local request_amount = math.min(v.min - current_amount, vlayer_data.storage.items[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
|
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)
|
local removed_item_count = vlayer.remove_item(v.value.name, request_amount)
|
||||||
@@ -514,9 +515,6 @@ function vlayer.get_statistics()
|
|||||||
energy_sustained = vdp * gsm,
|
energy_sustained = vdp * gsm,
|
||||||
energy_capacity = vlayer_data.properties.capacity * mega,
|
energy_capacity = vlayer_data.properties.capacity * mega,
|
||||||
energy_storage = vlayer_data.storage.energy,
|
energy_storage = vlayer_data.storage.energy,
|
||||||
day_time = math.floor(vlayer_data.surface.daytime * vlayer_data.surface.ticks_per_day),
|
|
||||||
day_length = vlayer_data.surface.ticks_per_day,
|
|
||||||
tick = game.tick,
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -540,9 +538,6 @@ function vlayer.get_circuits()
|
|||||||
energy_sustained = "signal-S",
|
energy_sustained = "signal-S",
|
||||||
energy_capacity = "signal-C",
|
energy_capacity = "signal-C",
|
||||||
energy_storage = "signal-E",
|
energy_storage = "signal-E",
|
||||||
day_time = "signal-D",
|
|
||||||
day_length = "signal-L",
|
|
||||||
tick = "signal-T",
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -574,7 +569,7 @@ function vlayer.create_circuit_interface(surface, position, circuit, last_user)
|
|||||||
end
|
end
|
||||||
|
|
||||||
interface.destructible = false
|
interface.destructible = false
|
||||||
interface.minable = false
|
interface.minable_flag = false
|
||||||
interface.operable = true
|
interface.operable = true
|
||||||
|
|
||||||
return interface
|
return interface
|
||||||
@@ -588,11 +583,11 @@ local function handle_circuit_interfaces()
|
|||||||
if not interface.valid then
|
if not interface.valid then
|
||||||
vlayer_data.entity_interfaces.circuit[index] = nil
|
vlayer_data.entity_interfaces.circuit[index] = nil
|
||||||
else
|
else
|
||||||
local circuit_oc = interface.get_or_create_control_behavior()
|
local control = interface.get_or_create_control_behavior() --[[@as LuaConstantCombinatorControlBehavior]]
|
||||||
if circuit_oc.sections_count == 0 then
|
if control.sections_count == 0 then
|
||||||
circuit_oc.add_section()
|
control.add_section()
|
||||||
end
|
end
|
||||||
circuit_oc = circuit_oc.sections[1]
|
local circuit_oc = assert(control.sections[1])
|
||||||
local signal_index = 1
|
local signal_index = 1
|
||||||
local circuit = vlayer.get_circuits()
|
local circuit = vlayer.get_circuits()
|
||||||
|
|
||||||
@@ -623,7 +618,7 @@ local function handle_circuit_interfaces()
|
|||||||
|
|
||||||
-- Clear remaining signals to prevent outdated values being present (caused by count > 0 check)
|
-- Clear remaining signals to prevent outdated values being present (caused by count > 0 check)
|
||||||
for clear_index = signal_index, #circuit do
|
for clear_index = signal_index, #circuit do
|
||||||
if not circuit_oc.get_slot(clear_index).signal then
|
if not circuit_oc.get_slot(clear_index).value then
|
||||||
break -- There are no more signals to clear
|
break -- There are no more signals to clear
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -653,7 +648,7 @@ function vlayer.create_energy_interface(surface, position, last_user)
|
|||||||
end
|
end
|
||||||
|
|
||||||
interface.destructible = false
|
interface.destructible = false
|
||||||
interface.minable = false
|
interface.minable_flag = false
|
||||||
interface.operable = false
|
interface.operable = false
|
||||||
interface.electric_buffer_size = 0
|
interface.electric_buffer_size = 0
|
||||||
interface.power_production = 0
|
interface.power_production = 0
|
||||||
|
|||||||
@@ -1,331 +0,0 @@
|
|||||||
--[[-- Control Module - Warnings
|
|
||||||
- Adds a way to give and remove warnings to players.
|
|
||||||
@control Warnings
|
|
||||||
@alias Warnings
|
|
||||||
|
|
||||||
@usage
|
|
||||||
-- import the module from the control modules
|
|
||||||
local Warnings = require("modules.exp_legacy.modules.control.warnings") --- @dep modules.control.warnings
|
|
||||||
|
|
||||||
-- This will add a warning to the player
|
|
||||||
Warnings.add_warning('MrBiter', 'Cooldude2606', 'Killed too many biters')
|
|
||||||
|
|
||||||
-- This will remove a warning from a player, second name is just who is doing the action
|
|
||||||
Warnings.remove_warning('MrBiter', 'Cooldude2606')
|
|
||||||
|
|
||||||
-- Script warning as similar to normal warning but are designed to have no effect for a short amount of time
|
|
||||||
-- this is so it can be used for greifer protection without being too agressive
|
|
||||||
Warnings.add_script_warning('MrBiter', 'Killed too many biters')
|
|
||||||
|
|
||||||
-- Both normal and script warnings can also be cleared, this will remove all warnings
|
|
||||||
Warnings.clear_warnings('MrBiter', 'Cooldude2606')
|
|
||||||
]]
|
|
||||||
|
|
||||||
local Event = require("modules/exp_legacy/utils/event")
|
|
||||||
local Storage = require("modules/exp_util/storage")
|
|
||||||
local config = require("modules.exp_legacy.config.warnings")
|
|
||||||
|
|
||||||
local valid_player = function(p) return type(p) == "userdata" and p or game.get_player(p) end
|
|
||||||
|
|
||||||
--- Stores the quickbar filters for a player
|
|
||||||
local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data
|
|
||||||
local PlayerWarnings = PlayerData.Required:combine("Warnings")
|
|
||||||
PlayerWarnings:set_metadata{
|
|
||||||
stringify = function(value)
|
|
||||||
if not value then return "You have no warnings" end
|
|
||||||
local count = 0
|
|
||||||
for _ in pairs(value) do count = count + 1 end
|
|
||||||
|
|
||||||
return "You have " .. count .. " warnings"
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
local Warnings = {
|
|
||||||
user_warnings = PlayerWarnings,
|
|
||||||
user_script_warnings = {},
|
|
||||||
events = {
|
|
||||||
--- When a warning is added to a player
|
|
||||||
-- @event on_warning_added
|
|
||||||
-- @tparam number player_index the index of the player who recived the warning
|
|
||||||
-- @tparam string by_player_name the name of the player who gave the warning
|
|
||||||
-- @tparam string reason the reason that the player was given a warning
|
|
||||||
-- @tparam number warning_count the new number of warnings that the player has
|
|
||||||
on_warning_added = script.generate_event_name(),
|
|
||||||
--- When a warning is removed from a player
|
|
||||||
-- @event on_warning_removed
|
|
||||||
-- @tparam number player_index the index of the player who is having the warning removed
|
|
||||||
-- @tparam string warning_by_name the name of the player who gave the warning
|
|
||||||
-- @tparam string removed_by_name the name of the player who is removing the warning
|
|
||||||
-- @tparam number warning_count the new number of warnings that the player has
|
|
||||||
-- @tparam number batch_count the number of warnings removed in this batch, always one when not a batch
|
|
||||||
-- @tparam number batch the index of this event in a batch, always one when not a batch
|
|
||||||
on_warning_removed = script.generate_event_name(),
|
|
||||||
--- When a warning is added to a player, by the script
|
|
||||||
-- @event on_script_warning_added
|
|
||||||
-- @tparam number player_index the index of the player who recived the warning
|
|
||||||
-- @tparam string reason the reason that the player was given a warning
|
|
||||||
-- @tparam number warning_count the new number of warnings that the player has
|
|
||||||
on_script_warning_added = script.generate_event_name(),
|
|
||||||
--- When a warning is removed from a player, by the script
|
|
||||||
-- @event on_script_warning_removed
|
|
||||||
-- @tparam number player_index the index of the player who is having the warning removed
|
|
||||||
-- @tparam number warning_count the new number of warnings that the player has
|
|
||||||
on_script_warning_removed = script.generate_event_name(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
local user_script_warnings = Warnings.user_script_warnings
|
|
||||||
Storage.register(user_script_warnings, function(tbl)
|
|
||||||
Warnings.user_script_warnings = tbl
|
|
||||||
user_script_warnings = tbl
|
|
||||||
end)
|
|
||||||
|
|
||||||
--- Gets an array of warnings that the player has, always returns a list even if empty
|
|
||||||
-- @tparam LuaPlayer player the player to get the warning for
|
|
||||||
-- @treturn table an array of all the warnings on this player, contains tick, by_player_name and reason
|
|
||||||
function Warnings.get_warnings(player)
|
|
||||||
return PlayerWarnings:get(player.name, {})
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets the number of warnings that a player has on them
|
|
||||||
-- @tparam LuaPlayer player the player to count the warnings for
|
|
||||||
-- @treturn number the number of warnings that the player has
|
|
||||||
function Warnings.count_warnings(player)
|
|
||||||
local warnings = PlayerWarnings:get(player.name, {})
|
|
||||||
return #warnings
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Adds a warning to a player, when a warning is added a set action is done based on the number of warnings and the config file
|
|
||||||
-- @tparam LuaPlayer player the player to add a warning to
|
|
||||||
-- @tparam string by_player_name the name of the player who is doing the action
|
|
||||||
-- @tparam[opt='Non given.'] string reason the reason that the player is being warned
|
|
||||||
-- @treturn number the number of warnings that the player has
|
|
||||||
function Warnings.add_warning(player, by_player_name, reason)
|
|
||||||
player = valid_player(player)
|
|
||||||
if not player then return end
|
|
||||||
if not by_player_name then return end
|
|
||||||
|
|
||||||
reason = reason or "None given."
|
|
||||||
|
|
||||||
local warning_count
|
|
||||||
PlayerWarnings:update(player.name, function(_, warnings)
|
|
||||||
local warning = {
|
|
||||||
by_player_name = by_player_name,
|
|
||||||
reason = reason,
|
|
||||||
}
|
|
||||||
|
|
||||||
if not warnings then
|
|
||||||
warning_count = 1
|
|
||||||
return { warning }
|
|
||||||
else
|
|
||||||
table.insert(warnings, warning)
|
|
||||||
warning_count = #warnings
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
script.raise_event(Warnings.events.on_warning_added, {
|
|
||||||
name = Warnings.events.on_warning_added,
|
|
||||||
tick = game.tick,
|
|
||||||
player_index = player.index,
|
|
||||||
warning_count = warning_count,
|
|
||||||
by_player_name = by_player_name,
|
|
||||||
reason = reason,
|
|
||||||
})
|
|
||||||
|
|
||||||
local action = config.actions[warning_count]
|
|
||||||
if action then
|
|
||||||
local _type = type(action)
|
|
||||||
if _type == "function" then
|
|
||||||
action(player, by_player_name, warning_count)
|
|
||||||
elseif _type == "table" then
|
|
||||||
local current = table.deepcopy(action)
|
|
||||||
table.insert(current, 2, by_player_name)
|
|
||||||
table.insert(current, 3, warning_count)
|
|
||||||
player.print(current)
|
|
||||||
elseif type(action) == "string" then
|
|
||||||
player.print(action)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return warning_count
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Event trigger for removing a waring due to it being looped in clear warnings
|
|
||||||
-- @tparam LuaPlayer player the player who is having a warning removed
|
|
||||||
-- @tparam string warning_by_name the name of the player who made the warning
|
|
||||||
-- @tparam string removed_by_name the name of the player who is doing the action
|
|
||||||
-- @tparam number warning_count the number of warnings that the player how has
|
|
||||||
-- @tparam number batch the index of this event in a batch, always one when not a batch
|
|
||||||
-- @tparam number batch_count the number of reports removed in this batch, always one when not a batch
|
|
||||||
local function warning_removed_event(player, warning_by_name, removed_by_name, warning_count, batch, batch_count)
|
|
||||||
script.raise_event(Warnings.events.on_warning_removed, {
|
|
||||||
name = Warnings.events.on_warning_removed,
|
|
||||||
tick = game.tick,
|
|
||||||
player_index = player.index,
|
|
||||||
warning_count = warning_count,
|
|
||||||
warning_by_name = warning_by_name,
|
|
||||||
removed_by_name = removed_by_name,
|
|
||||||
batch_count = batch_count or 1,
|
|
||||||
batch = batch or 1,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Removes a warning from a player, always removes the earliest warning, fifo
|
|
||||||
-- @tparam LuaPlayer player the player to remove a warning from
|
|
||||||
-- @tparam string by_player_name the name of the player who is doing the action
|
|
||||||
-- @treturn number the number of warnings that the player has
|
|
||||||
function Warnings.remove_warning(player, by_player_name)
|
|
||||||
player = valid_player(player)
|
|
||||||
if not player then return end
|
|
||||||
if not by_player_name then return end
|
|
||||||
|
|
||||||
local warning, warning_count
|
|
||||||
PlayerWarnings:update(player.name, function(_, warnings)
|
|
||||||
if not warnings then return end
|
|
||||||
warning = table.remove(warnings, 1)
|
|
||||||
warning_count = #warnings
|
|
||||||
end)
|
|
||||||
|
|
||||||
if not warning then return end
|
|
||||||
warning_removed_event(player, warning.by_player_name, by_player_name, warning_count)
|
|
||||||
|
|
||||||
return warning_count
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Removes all warnings from a player, will trigger remove event for each warning
|
|
||||||
-- @tparam LuaPlayer player the player to clear the warnings from
|
|
||||||
-- @tparam string by_player_name the name of the player who is doing the action
|
|
||||||
-- @treturn boolean true when warnings were cleared succesfully
|
|
||||||
function Warnings.clear_warnings(player, by_player_name)
|
|
||||||
player = valid_player(player)
|
|
||||||
if not player then return end
|
|
||||||
if not by_player_name then return end
|
|
||||||
|
|
||||||
local warnings = PlayerWarnings:get(player)
|
|
||||||
if not warnings then return end
|
|
||||||
|
|
||||||
local warning_count = #warnings
|
|
||||||
for n, warning in pairs(warnings) do
|
|
||||||
warning_removed_event(player, warning.by_player_name, by_player_name, warning_count - n, n, warning_count)
|
|
||||||
end
|
|
||||||
|
|
||||||
PlayerWarnings:remove(player)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets an array of all the script warnings that a player has
|
|
||||||
-- @tparam LuaPlayer player the player to get the script warnings of
|
|
||||||
-- @treturn table a table of all the script warnings a player has, contains tick and reason
|
|
||||||
function Warnings.get_script_warnings(player)
|
|
||||||
return user_script_warnings[player.name] or {}
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets the number of script warnings that a player has on them
|
|
||||||
-- @tparam LuaPlayer player the player to count the script warnings of
|
|
||||||
-- @treturn number the number of script warnings that the player has
|
|
||||||
function Warnings.count_script_warnings(player)
|
|
||||||
local warnings = user_script_warnings[player.name] or {}
|
|
||||||
return #warnings
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Adds a script warning to a player, this may add a full warning if max script warnings is met
|
|
||||||
-- @tparam LuaPlayer player the player to add a script warning to
|
|
||||||
-- @tparam[opt='Non given.'] string reason the reason that the player is being warned
|
|
||||||
-- @treturn number the number of script warnings that the player has
|
|
||||||
function Warnings.add_script_warning(player, reason)
|
|
||||||
player = valid_player(player)
|
|
||||||
if not player then return end
|
|
||||||
|
|
||||||
reason = reason or "Non given."
|
|
||||||
|
|
||||||
local warnings = user_script_warnings[player.name]
|
|
||||||
if not warnings then
|
|
||||||
warnings = {}
|
|
||||||
user_script_warnings[player.name] = warnings
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(warnings, {
|
|
||||||
tick = game.tick,
|
|
||||||
reason = reason,
|
|
||||||
})
|
|
||||||
|
|
||||||
local warning_count = #warnings
|
|
||||||
|
|
||||||
script.raise_event(Warnings.events.on_script_warning_added, {
|
|
||||||
name = Warnings.events.on_script_warning_added,
|
|
||||||
tick = game.tick,
|
|
||||||
player_index = player.index,
|
|
||||||
warning_count = warning_count,
|
|
||||||
reason = reason,
|
|
||||||
})
|
|
||||||
|
|
||||||
if warning_count > config.script_warning_limit then
|
|
||||||
Warnings.add_warning(player, "<server>", reason)
|
|
||||||
end
|
|
||||||
|
|
||||||
return warning_count
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Script warning removed event trigger due to it being looped in clear script warnings
|
|
||||||
-- @tparam LuaPlayer player the player who is having a script warning removed
|
|
||||||
-- @tparam number warning_count the number of warnings that the player has
|
|
||||||
local function script_warning_removed_event(player, warning_count)
|
|
||||||
script.raise_event(Warnings.events.on_script_warning_removed, {
|
|
||||||
name = Warnings.events.on_script_warning_removed,
|
|
||||||
tick = game.tick,
|
|
||||||
player_index = player.index,
|
|
||||||
warning_count = warning_count,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Removes a script warning from a player
|
|
||||||
-- @tparam LuaPlayer player the player to remove a script warning from
|
|
||||||
-- @treturn number the number of script warnings that the player has
|
|
||||||
function Warnings.remove_script_warning(player)
|
|
||||||
player = valid_player(player)
|
|
||||||
if not player then return end
|
|
||||||
|
|
||||||
local warnings = user_script_warnings[player.name]
|
|
||||||
if not warnings then return end
|
|
||||||
|
|
||||||
table.remove(warnings, 1)
|
|
||||||
|
|
||||||
script_warning_removed_event(player)
|
|
||||||
|
|
||||||
return #warnings
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Removes all script warnings from a player, emits event for each warning removed
|
|
||||||
-- @tparam LuaPlayer player the player to clear the script warnings from
|
|
||||||
function Warnings.clear_script_warnings(player)
|
|
||||||
player = valid_player(player)
|
|
||||||
if not player then return end
|
|
||||||
|
|
||||||
local warnings = user_script_warnings[player.name]
|
|
||||||
if not warnings then return end
|
|
||||||
|
|
||||||
local warning_count = #warnings
|
|
||||||
for n, _ in pairs(warnings) do
|
|
||||||
script_warning_removed_event(player, warning_count - n)
|
|
||||||
end
|
|
||||||
|
|
||||||
user_script_warnings[player.name] = nil
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- script warnings are removed after a certain amount of time to make them even more lienient
|
|
||||||
local script_warning_cool_down = config.script_warning_cool_down * 3600
|
|
||||||
Event.on_nth_tick(script_warning_cool_down / 4, function()
|
|
||||||
local cutoff = game.tick - script_warning_cool_down
|
|
||||||
for player_name, script_warnings in pairs(user_script_warnings) do
|
|
||||||
if #script_warnings > 0 then
|
|
||||||
for _, warning in pairs(script_warnings) do
|
|
||||||
if warning.tick < cutoff then
|
|
||||||
Warnings.remove_script_warning(player_name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
return Warnings
|
|
||||||
@@ -182,7 +182,7 @@ function Warps.make_warp_area(warp_id)
|
|||||||
}
|
}
|
||||||
entity.destructible = false
|
entity.destructible = false
|
||||||
entity.health = 0
|
entity.health = 0
|
||||||
entity.minable = false
|
entity.minable_flag = false
|
||||||
entity.rotatable = false
|
entity.rotatable = false
|
||||||
|
|
||||||
-- Save reference of the last power pole
|
-- Save reference of the last power pole
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
--[[-- Commands Module - Bonus
|
|
||||||
- Adds a command that allows players to have increased stats
|
|
||||||
@data Bonus
|
|
||||||
]]
|
|
||||||
|
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
|
|
||||||
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
|
|
||||||
local config = require("modules.exp_legacy.config.bonus") --- @dep config.bonuses
|
|
||||||
local Commands = require("modules/exp_commands")
|
|
||||||
|
|
||||||
-- Stores the bonus for the player
|
|
||||||
local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data
|
|
||||||
local PlayerBonus = PlayerData.Settings:combine("Bonus")
|
|
||||||
PlayerBonus:set_default(0)
|
|
||||||
PlayerBonus:set_metadata{
|
|
||||||
permission = "command/bonus",
|
|
||||||
stringify = function(value)
|
|
||||||
if not value or value == 0 then
|
|
||||||
return "None set"
|
|
||||||
end
|
|
||||||
|
|
||||||
return value
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
--- Apply a bonus to a player
|
|
||||||
local function apply_bonus(player, stage)
|
|
||||||
if not player.character then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
for k, v in pairs(config.player_bonus) do
|
|
||||||
player[k] = v.value * stage / 10
|
|
||||||
|
|
||||||
if v.combined_bonus then
|
|
||||||
for i = 1, #v.combined_bonus, 1 do
|
|
||||||
player[v.combined_bonus[i]] = v.value * stage / 10
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- When store is updated apply new bonus to the player
|
|
||||||
PlayerBonus:on_update(function(player_name, player_bonus)
|
|
||||||
apply_bonus(game.players[player_name], player_bonus or 0)
|
|
||||||
end)
|
|
||||||
|
|
||||||
--- Changes the amount of bonus you receive
|
|
||||||
Commands.new("bonus", { "bonus.description" })
|
|
||||||
:optional("amount", { "bonus.arg-amount" }, Commands.types.integer_range(0, 10))
|
|
||||||
:register(function(player, amount)
|
|
||||||
--- @cast amount number?
|
|
||||||
if amount then
|
|
||||||
PlayerBonus:set(player, amount)
|
|
||||||
return Commands.status.success{ "bonus.set", amount }
|
|
||||||
else
|
|
||||||
return Commands.status.success{ "bonus.get", PlayerBonus:get(player) }
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
--- When a player respawns re-apply bonus
|
|
||||||
Event.add(defines.events.on_player_respawned, function(event)
|
|
||||||
local player = game.players[event.player_index]
|
|
||||||
apply_bonus(player, PlayerBonus:get(player))
|
|
||||||
end)
|
|
||||||
|
|
||||||
--- Remove bonus if a player no longer has access to the command
|
|
||||||
local function role_update(event)
|
|
||||||
local player = game.players[event.player_index]
|
|
||||||
if not Roles.player_allowed(player, "command/bonus") then
|
|
||||||
apply_bonus(player, 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- When a player dies allow them to have instant respawn
|
|
||||||
Event.add(defines.events.on_player_died, function(event)
|
|
||||||
local player = game.players[event.player_index]
|
|
||||||
|
|
||||||
if Roles.player_has_flag(player, "instant-respawn") then
|
|
||||||
player.ticks_to_respawn = 120
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
Event.add(defines.events.on_player_created, function(event)
|
|
||||||
if event.player_index ~= 1 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
for k, v in pairs(config.force_bonus) do
|
|
||||||
game.players[event.player_index].force[k] = v.value
|
|
||||||
end
|
|
||||||
|
|
||||||
for k, v in pairs(config.surface_bonus) do
|
|
||||||
game.players[event.player_index].surface[k] = v.value
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
Event.add(Roles.events.on_role_assigned, role_update)
|
|
||||||
Event.add(Roles.events.on_role_unassigned, role_update)
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user