Compare commits
508 Commits
ed-god-hai
...
welcoming-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e308eec703 | ||
|
|
d5e54ce744 | ||
|
|
c91a6c3f59 | ||
|
|
b66ee390a6 | ||
|
|
e125605f25 | ||
|
|
dd9aea718c | ||
|
|
48831380b4 | ||
|
|
16c4f8be53 | ||
|
|
879792be09 | ||
|
|
fa7aacc103 | ||
|
|
937851a72c | ||
|
|
504adb6738 | ||
|
|
b9d191c7e4 | ||
|
|
1573d1ee81 | ||
|
|
e2dec03cbb | ||
|
|
6e96943df5 | ||
|
|
256476b576 | ||
|
|
655cba7674 | ||
|
|
edc67dd280 | ||
|
|
05de87e9e9 | ||
|
|
e4bd418c45 | ||
|
|
d6943c762c | ||
|
|
6ac00528e9 | ||
|
|
5ae9df1f0b | ||
|
|
41433d1414 | ||
|
|
4d1094cd6b | ||
|
|
2e85ac0263 | ||
|
|
9d6e023bd0 | ||
|
|
987f00a493 | ||
|
|
8a479c4e5e | ||
|
|
011e90a5d1 | ||
|
|
abdd46c7d5 | ||
|
|
0c98ad8b38 | ||
|
|
89ea21bc31 | ||
|
|
f81f7a836e | ||
|
|
03e0d66083 | ||
|
|
fddbb010cf | ||
|
|
87be2dde27 | ||
|
|
2248e2ad5c | ||
|
|
6706a14163 | ||
|
|
baa48eb0e5 | ||
|
|
d3f70aee7e | ||
|
|
f337fde3d9 | ||
|
|
b9fad6cbd4 | ||
|
|
532f44eb3c | ||
|
|
e98df217e2 | ||
|
|
8f049f174d | ||
|
|
f9320aacd7 | ||
|
|
5c505958a7 | ||
|
|
db65e3241e | ||
|
|
56e0cb642a | ||
|
|
7c44038f63 | ||
|
|
2a524b4b4b | ||
|
|
735b4f5c7b | ||
|
|
4b13d876e8 | ||
|
|
afea64252b | ||
|
|
1e36a61339 | ||
|
|
6a6350a827 | ||
|
|
9271500fe4 | ||
|
|
f4adb0bdb3 | ||
|
|
f45794002b | ||
|
|
da2c9510b2 | ||
|
|
e4a5043d4e | ||
|
|
5bedf1761a | ||
|
|
bf17f85ced | ||
|
|
18cc899897 | ||
|
|
c8494dd5c9 | ||
|
|
f31a568ccc | ||
|
|
f838e6730d | ||
|
|
e066dd3abe | ||
|
|
9ff43f344e | ||
|
|
6d63e3c1f4 | ||
|
|
b7b3167302 | ||
|
|
615af0b116 | ||
|
|
9b28aedbc5 | ||
|
|
b97de9d603 | ||
|
|
87d757de45 | ||
|
|
fd2c59f4bb | ||
|
|
2f30fe3ceb | ||
|
|
462519b7da | ||
|
|
9ca6353dcd | ||
|
|
7270988961 | ||
|
|
1364bfd422 | ||
|
|
33970b0429 | ||
|
|
8dfa2e8f95 | ||
|
|
1732579c22 | ||
|
|
c82d531a8f | ||
|
|
098e594161 | ||
|
|
9148c21b2b | ||
|
|
c3d0b93ddf | ||
|
|
981eb42979 | ||
|
|
ecf590f223 | ||
|
|
290bffceec | ||
|
|
9243d65e6f | ||
|
|
72141abf8a | ||
|
|
ee2846613a | ||
|
|
0bcc5ace8c | ||
|
|
0d3be998a0 | ||
|
|
1b74de6dbb | ||
|
|
0096dedb18 | ||
|
|
2bc3561868 | ||
|
|
99e62b4a49 | ||
|
|
9502ca0d23 | ||
|
|
37c88debb5 | ||
|
|
ae07c5d841 | ||
|
|
67b76d81e5 | ||
|
|
c9f567956a | ||
|
|
b1af9ac89e | ||
|
|
4f23016c72 | ||
|
|
b2095a01cc | ||
|
|
b5a2508ebc | ||
|
|
44b775b308 | ||
|
|
2dbc3e7a4d | ||
|
|
4530749f1d | ||
|
|
7c9e344cd2 | ||
|
|
3e36b2c1f7 | ||
|
|
4f1b8ea65d | ||
|
|
b566eef796 | ||
|
|
fbf0679650 | ||
|
|
b325a80a61 | ||
|
|
c8f5b480cb | ||
|
|
35a619b1d5 | ||
|
|
f4c5253f9c | ||
|
|
4083b79217 | ||
|
|
9e1a150829 | ||
|
|
28d30571ca | ||
|
|
57e3f0494e | ||
|
|
8656a7e1f0 | ||
|
|
1859975ae6 | ||
|
|
29278ca98b | ||
|
|
9c5662d7f5 | ||
|
|
eaf9c009c1 | ||
|
|
5512e1a665 | ||
|
|
d4c8ddb0ac | ||
|
|
c42d0913cb | ||
|
|
3f2befca9e | ||
|
|
dac6c53aa1 | ||
|
|
b5f142b568 | ||
|
|
64faddf77a | ||
|
|
7773b77a91 | ||
|
|
f2562aad06 | ||
|
|
113e335ca8 | ||
|
|
42e3f7ce97 | ||
|
|
8da5776a4e | ||
|
|
923f147d9d | ||
|
|
17424e2eb6 | ||
|
|
559d22c86c | ||
|
|
38f3238b1d | ||
|
|
a9a6b98f99 | ||
|
|
c2963d47a0 | ||
|
|
71ae9257f7 | ||
|
|
dacf0d915c | ||
|
|
4027115e0c | ||
|
|
6856440084 | ||
|
|
3f62dfe0b5 | ||
|
|
dd6d3f8bc7 | ||
|
|
3d873c6600 | ||
|
|
52e6e9b28f | ||
|
|
1b26a98d7f | ||
|
|
97d0f14dae | ||
|
|
2b6c0e296d | ||
|
|
2dc7236a71 | ||
|
|
6e82ac9f81 | ||
|
|
9cba450a19 | ||
|
|
e72a086815 | ||
|
|
280ac8b3ed | ||
|
|
a5566b1bfa | ||
|
|
534f9e771f | ||
|
|
a8a73a611e | ||
|
|
4a234923ad | ||
|
|
8a3ae8e5ca | ||
|
|
d504357aab | ||
|
|
85a6ac90ba | ||
|
|
09901e6551 | ||
|
|
4d50a5a903 | ||
|
|
1535fe731a | ||
|
|
4415c2448d | ||
|
|
7174195208 | ||
|
|
8da4f9765e | ||
|
|
b780b74bbd | ||
|
|
ecf22daff7 | ||
|
|
5dd1cd4bdb | ||
|
|
43d08100b9 | ||
|
|
07a8a02522 | ||
|
|
f5b8b248f2 | ||
|
|
0d04f541d2 | ||
|
|
09c7c6b7b4 | ||
|
|
98730a5485 | ||
|
|
4009cea235 | ||
|
|
9292e3a43c | ||
|
|
2e7f01b99e | ||
|
|
65f393bb14 | ||
|
|
52a145f7f1 | ||
|
|
6351398bb6 | ||
|
|
a0c78e7b18 | ||
|
|
115492e9bd | ||
|
|
ba56fe8ebc | ||
|
|
887edf2d7b | ||
|
|
672c96ef6c | ||
|
|
e2097d473c | ||
|
|
d3ee9f8bc5 | ||
|
|
b9f56be959 | ||
|
|
003b667aa6 | ||
|
|
ed9bbd9fff | ||
|
|
4bb1a62f22 | ||
|
|
fc544d713b | ||
|
|
eb5610b745 | ||
|
|
791d7c8f98 | ||
|
|
7a539b3568 | ||
|
|
752477bff3 | ||
|
|
7909f87d00 | ||
|
|
b1c89d059c | ||
|
|
97c32d0d70 | ||
|
|
ef9c896972 | ||
|
|
988cb08760 | ||
|
|
6f1642eb9a | ||
|
|
a4eac94670 | ||
|
|
d0ce213b50 | ||
|
|
86aa82f2b6 | ||
|
|
916661944b | ||
|
|
8394753c30 | ||
|
|
150f58a6a5 | ||
|
|
1d4ff1b0d1 | ||
|
|
398d92a064 | ||
|
|
5b08838420 | ||
|
|
2516868bdc | ||
|
|
a951193dba | ||
|
|
4002b0a41c | ||
|
|
709b78080f | ||
|
|
d71fd8ef80 | ||
|
|
1867903c46 | ||
|
|
e76b81d291 | ||
|
|
097b93886b | ||
|
|
e27d4b9459 | ||
|
|
a5027e430c | ||
|
|
0eb30d522b | ||
|
|
196eb0327c | ||
|
|
cbcd1091d5 | ||
|
|
943d703685 | ||
|
|
d7c4bc0a5c | ||
|
|
e4eea533a5 | ||
|
|
0a166e2af0 | ||
|
|
c72fd448cd | ||
|
|
47dbbabe3e | ||
|
|
6c46fe5307 | ||
|
|
95055b43bf | ||
|
|
0c73b24da8 | ||
|
|
15a5cebdcd | ||
|
|
1367ae5703 | ||
|
|
dc4e8a2972 | ||
|
|
55f70f9d2c | ||
|
|
2b83d9bf89 | ||
|
|
08cd8d0543 | ||
|
|
e4864ac423 | ||
|
|
f8ccff1363 | ||
|
|
bf59896922 | ||
|
|
197ee78bfa | ||
|
|
03dc35ac92 | ||
|
|
29cbc4a190 | ||
|
|
1b34884823 | ||
|
|
d8ed9fe152 | ||
|
|
deb3ed3ed2 | ||
|
|
04b9088c85 | ||
|
|
a7f6ceec58 | ||
|
|
71f2cf4d54 | ||
|
|
4b9d9da074 | ||
|
|
eabd0b7f24 | ||
|
|
6ee3ae80a5 | ||
|
|
af36b3983c | ||
|
|
e0a8718f78 | ||
|
|
1a756a6574 | ||
|
|
6cd9c3e16d | ||
|
|
b378c5e455 | ||
|
|
411a89ce31 | ||
|
|
d5372db359 | ||
|
|
b878e80929 | ||
|
|
45593b1db7 | ||
|
|
36f6438c8e | ||
|
|
7f4672f963 | ||
|
|
8848d74ccc | ||
|
|
222331cb49 | ||
|
|
11cac44bc4 | ||
|
|
4576555e5b | ||
|
|
c7022ae7c5 | ||
|
|
2fe53c3722 | ||
|
|
5efcccc81f | ||
|
|
23d7cd0a29 | ||
|
|
5c5dc1207a | ||
|
|
b980c94d89 | ||
|
|
9ca4254e1a | ||
|
|
e7719518c0 | ||
|
|
fd59427cb5 | ||
|
|
50525a11be | ||
|
|
5d296d2dbb | ||
|
|
0ed1f84f45 | ||
|
|
22ecd58083 | ||
|
|
1945c7d7c6 | ||
|
|
33edf9692b | ||
|
|
bce9b4b05b | ||
|
|
1b2d3d6ab9 | ||
|
|
4a4d3d6a07 | ||
|
|
d4b7a6d582 | ||
|
|
66a02010f2 | ||
|
|
90dc674c96 | ||
|
|
0b1e25b68e | ||
|
|
96501f4966 | ||
|
|
48a4aa3929 | ||
|
|
3f8be60a8d | ||
|
|
e1762d91fa | ||
|
|
63ecf4241a | ||
|
|
f9a48463ba | ||
|
|
b3482fd737 | ||
|
|
e4b268088a | ||
|
|
212bf99688 | ||
|
|
8f81e55711 | ||
|
|
9005183e6e | ||
|
|
b7759cccbd | ||
|
|
28ff4eddf5 | ||
|
|
0d16bd5d2b | ||
|
|
ccc8837b69 | ||
|
|
d8ac1524ef | ||
|
|
45e5fbfcdf | ||
|
|
2ff59f153e | ||
|
|
02aec8fe54 | ||
|
|
44c8e05d1f | ||
|
|
d12e0b8ddf | ||
|
|
06ee81d1fe | ||
|
|
5db89ab7e9 | ||
|
|
b5728c228d | ||
|
|
3d898ed25a | ||
|
|
175f5e6c2f | ||
|
|
a384cbed89 | ||
|
|
4baaff2a23 | ||
|
|
3276deeca0 | ||
|
|
231847a36d | ||
|
|
0575a3afd6 | ||
|
|
a6d66e682f | ||
|
|
9c84acdd92 | ||
|
|
ab72f682ce | ||
|
|
81a1ae1623 | ||
|
|
6d99887746 | ||
|
|
4d98b9d621 | ||
|
|
314b64226c | ||
|
|
de98dbb400 | ||
|
|
38615b72e5 | ||
|
|
3b31b72ed0 | ||
|
|
4e37bcc40e | ||
|
|
12a3a67ca9 | ||
|
|
80e674a9ae | ||
|
|
a8d5e08d69 | ||
|
|
ca60f22c4d | ||
|
|
f4873e9c56 | ||
|
|
8d93eebceb | ||
|
|
e0d6944822 | ||
|
|
d1b714bcf8 | ||
|
|
c7d9a45dab | ||
|
|
2742429341 | ||
|
|
70fca49063 | ||
|
|
71ac80eb6c | ||
|
|
b8b6634c0f | ||
|
|
b1ddc1a20d | ||
|
|
75a7407e33 | ||
|
|
6689cae5a1 | ||
|
|
84792f88d6 | ||
|
|
38faab470b | ||
|
|
008e51a5f2 | ||
|
|
b5b92d5f3d | ||
|
|
469a4196e3 | ||
|
|
8e10ff1f08 | ||
|
|
03a8dee379 | ||
|
|
7a6b947e60 | ||
|
|
47c3dc65fa | ||
|
|
bcf3390c88 | ||
|
|
39a8bae2d2 | ||
|
|
ddc5758d00 | ||
|
|
66f89cece6 | ||
|
|
217845b7ae | ||
|
|
4ed99ca367 | ||
|
|
fc1c149820 | ||
|
|
f753bb9af4 | ||
|
|
7215ae2d5c | ||
|
|
2de3c58145 | ||
|
|
9d7cf27469 | ||
|
|
e57325b797 | ||
|
|
6f39ee8688 | ||
|
|
c64a6b03a3 | ||
|
|
52d7479c50 | ||
|
|
4735097385 | ||
|
|
1abd2d3a08 | ||
|
|
b1fabb5a1a | ||
|
|
3ccd9e4b6d | ||
|
|
9200a24dcf | ||
|
|
7806afb284 | ||
|
|
177a323fd4 | ||
|
|
0480e1554f | ||
|
|
7242a1d69d | ||
|
|
28174a5062 | ||
|
|
965bd1c5ac | ||
|
|
804addfff7 | ||
|
|
2412b4f634 | ||
|
|
bc083f9d49 | ||
|
|
315f5a21a9 | ||
|
|
a1487c21bb | ||
|
|
8d6cc7b2a0 | ||
|
|
d8b020ca54 | ||
|
|
6c4d1acb35 | ||
|
|
9f3ae33e77 | ||
|
|
4c9199dd59 | ||
|
|
2c40dde9bc | ||
|
|
f51b9bc86e | ||
|
|
db96cc7881 | ||
|
|
a695a527f0 | ||
|
|
31a45a427c | ||
|
|
0be41cebb9 | ||
|
|
67f2c1ed58 | ||
|
|
531f5619be | ||
|
|
cca537fb33 | ||
|
|
3fb80927cf | ||
|
|
00fec734f3 | ||
|
|
3281f408eb | ||
|
|
5c12c1bf08 | ||
|
|
fdb3082b90 | ||
|
|
e82bf065dc | ||
|
|
821baa0900 | ||
|
|
0a232908a4 | ||
|
|
6f23e10495 | ||
|
|
b57b534380 | ||
|
|
657899f8b5 | ||
|
|
3bf38a4d46 | ||
|
|
4c0ebb7f32 | ||
|
|
74e02022d6 | ||
|
|
27cfc0939c | ||
|
|
d3e1718da5 | ||
|
|
8785085be6 | ||
|
|
e8c13fe325 | ||
|
|
0d01bb8894 | ||
|
|
2d32e08bee | ||
|
|
346bd8dd8b | ||
|
|
e67c7a7409 | ||
|
|
5774b71b9b | ||
|
|
14c94968fc | ||
|
|
50f7846bb1 | ||
|
|
67b8f35a3c | ||
|
|
ff6c212335 | ||
|
|
f41c98b053 | ||
|
|
3f7a9cad7c | ||
|
|
8b7f175f84 | ||
|
|
69115870ea | ||
|
|
7a89fe5d33 | ||
|
|
ee690d9ee4 | ||
|
|
913894a041 | ||
|
|
f42c1bc933 | ||
|
|
78b2b361e8 | ||
|
|
184edfe71d | ||
|
|
7e7d12d437 | ||
|
|
36021f1702 | ||
|
|
8fc78f63d9 | ||
|
|
5b078e3978 | ||
|
|
6f1df2b62f | ||
|
|
469bb1a9ec | ||
|
|
ceff2bea00 | ||
|
|
02d3595faa | ||
|
|
ce7bb813d2 | ||
|
|
51754f09ce | ||
|
|
7ea586cc1c | ||
|
|
a8ebcac5c9 | ||
|
|
ba1504d0d6 | ||
|
|
d9e86b3e81 | ||
|
|
66e926843f | ||
|
|
4a937edd27 | ||
|
|
0563e0d67e | ||
|
|
a926c53979 | ||
|
|
bb0c4c66fa | ||
|
|
9c970d203d | ||
|
|
a54960eb81 | ||
|
|
5bdc93b102 | ||
|
|
deea33a36a | ||
|
|
10c868011e | ||
|
|
e8c812f90f | ||
|
|
212e942d21 | ||
|
|
5169ad4e8f | ||
|
|
e540f9cd50 | ||
|
|
aa05cbb49b | ||
|
|
1b20121114 | ||
|
|
6b84315928 | ||
|
|
30a6ebdb26 | ||
|
|
01e4029a11 | ||
|
|
8ea888d821 | ||
|
|
e60318e59f | ||
|
|
2a7c9ed267 | ||
|
|
7462f9a376 | ||
|
|
0c6db4db18 | ||
|
|
63a6ffaa52 | ||
|
|
4442d5e277 | ||
|
|
d8838e31d5 | ||
|
|
38d72434fb | ||
|
|
6b6ac9ac53 | ||
|
|
1047e32944 | ||
|
|
98c7010b16 | ||
|
|
ac7f99d46e | ||
|
|
213e88820a | ||
|
|
30bdcd58fb | ||
|
|
d364efb886 | ||
|
|
c781fde830 | ||
|
|
daccf98cca | ||
|
|
b788d15da7 | ||
|
|
ee0ce4b220 | ||
|
|
ecb6540c61 |
2
.github/workflows/benchmarks.yml
vendored
2
.github/workflows/benchmarks.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Run Benchmarks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Get Engine version
|
||||
|
||||
4
.github/workflows/build-docfx.yml
vendored
4
.github/workflows/build-docfx.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
docfx:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- name: Setup submodule
|
||||
run: |
|
||||
git submodule update --init --recursive
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
cd RobustToolbox/
|
||||
git submodule update --init --recursive
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
|
||||
4
.github/workflows/build-map-renderer.yml
vendored
4
.github/workflows/build-map-renderer.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Master
|
||||
uses: actions/checkout@v3.6.0
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Setup Submodule
|
||||
run: |
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
git submodule update --init --recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
|
||||
4
.github/workflows/build-test-debug.yml
vendored
4
.github/workflows/build-test-debug.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Master
|
||||
uses: actions/checkout@v3.6.0
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Setup Submodule
|
||||
run: |
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
git submodule update --init --recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
|
||||
2
.github/workflows/check-crlf.yml
vendored
2
.github/workflows/check-crlf.yml
vendored
@@ -10,6 +10,6 @@ jobs:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- name: Check for CRLF
|
||||
run: Tools/check_crlf.py
|
||||
|
||||
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@@ -16,11 +16,11 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install -y python3-paramiko python3-lxml
|
||||
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
|
||||
4
.github/workflows/rsi-diff.yml
vendored
4
.github/workflows/rsi-diff.yml
vendored
@@ -11,14 +11,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.6.0
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Get changed files
|
||||
id: files
|
||||
uses: Ana06/get-changed-files@v2.3.0
|
||||
with:
|
||||
format: 'space-delimited'
|
||||
filter: |
|
||||
filter: |
|
||||
**.rsi
|
||||
**.png
|
||||
|
||||
|
||||
4
.github/workflows/test-packaging.yml
vendored
4
.github/workflows/test-packaging.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Master
|
||||
uses: actions/checkout@v3.6.0
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Setup Submodule
|
||||
run: |
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
git submodule update --init --recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
|
||||
20
.github/workflows/update-credits.yml
vendored
20
.github/workflows/update-credits.yml
vendored
@@ -4,19 +4,19 @@ on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: 0 0 * * 0
|
||||
|
||||
|
||||
jobs:
|
||||
get_credits:
|
||||
runs-on: ubuntu-latest
|
||||
# Hey there fork dev! If you like to include your own contributors in this then you can probably just change this to your own repo
|
||||
# Do this in dump_github_contributors.ps1 too into your own repo
|
||||
if: github.repository == 'space-wizards/space-station-14'
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
ref: master
|
||||
|
||||
|
||||
- name: Get this week's Contributors
|
||||
shell: pwsh
|
||||
env:
|
||||
@@ -25,25 +25,25 @@ jobs:
|
||||
|
||||
# TODO
|
||||
#- name: Get this week's Patreons
|
||||
# run: Tools/script2dumppatreons > Resources/Credits/Patrons.yml
|
||||
|
||||
# run: Tools/script2dumppatreons > Resources/Credits/Patrons.yml
|
||||
|
||||
# MAKE SURE YOU ENABLED "Allow GitHub Actions to create and approve pull requests" IN YOUR ACTIONS, OTHERWISE IT WILL MOST LIKELY FAIL
|
||||
|
||||
|
||||
# For this you can use a pat token of an account with direct push access to the repo if you have protected branches.
|
||||
# For this you can use a pat token of an account with direct push access to the repo if you have protected branches.
|
||||
# Uncomment this and comment the other line if you do this.
|
||||
# https://github.com/stefanzweifel/git-auto-commit-action#push-to-protected-branches
|
||||
|
||||
|
||||
#- name: Commit new credit files
|
||||
# uses: stefanzweifel/git-auto-commit-action@v4
|
||||
# with:
|
||||
# commit_message: Update Credits
|
||||
# commit_author: PJBot <pieterjan.briers+bot@gmail.com>
|
||||
|
||||
|
||||
# This will make a PR
|
||||
- name: Set current date as env variable
|
||||
run: echo "NOW=$(date +'%Y-%m-%dT%H-%M-%S')" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
|
||||
2
.github/workflows/validate-rgas.yml
vendored
2
.github/workflows/validate-rgas.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
if: github.actor != 'PJBot' && github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- name: Setup Submodule
|
||||
run: git submodule update --init
|
||||
- name: Pull engine updates
|
||||
|
||||
2
.github/workflows/validate-rsis.yml
vendored
2
.github/workflows/validate-rsis.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
name: Validate RSIs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- name: Setup Submodule
|
||||
run: git submodule update --init
|
||||
- name: Pull engine updates
|
||||
|
||||
2
.github/workflows/validate_mapfiles.yml
vendored
2
.github/workflows/validate_mapfiles.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
if: github.actor != 'PJBot' && github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- name: Setup Submodule
|
||||
run: git submodule update --init
|
||||
- name: Pull engine updates
|
||||
|
||||
4
.github/workflows/yaml-linter.yml
vendored
4
.github/workflows/yaml-linter.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
if: github.actor != 'PJBot' && github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- name: Setup submodule
|
||||
run: |
|
||||
git submodule update --init --recursive
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
cd RobustToolbox/
|
||||
git submodule update --init --recursive
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Administration.Systems;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Mind;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
@@ -14,32 +17,54 @@ namespace Content.Client.Administration;
|
||||
|
||||
internal sealed class AdminNameOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
|
||||
private readonly AdminSystem _system;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly EntityLookupSystem _entityLookup;
|
||||
private readonly IUserInterfaceManager _userInterfaceManager;
|
||||
private readonly Font _font;
|
||||
private readonly Font _fontBold;
|
||||
private bool _overlayClassic;
|
||||
private bool _overlaySymbols;
|
||||
private bool _overlayPlaytime;
|
||||
private bool _overlayStartingJob;
|
||||
private float _ghostFadeDistance;
|
||||
private float _ghostHideDistance;
|
||||
private int _overlayStackMax;
|
||||
private float _overlayMergeDistance;
|
||||
|
||||
//TODO make this adjustable via GUI
|
||||
private readonly ProtoId<RoleTypePrototype>[] _filter =
|
||||
["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"];
|
||||
private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic");
|
||||
private readonly Color _antagColorClassic = Color.OrangeRed;
|
||||
|
||||
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager)
|
||||
public AdminNameOverlay(
|
||||
AdminSystem system,
|
||||
IEntityManager entityManager,
|
||||
IEyeManager eyeManager,
|
||||
IResourceCache resourceCache,
|
||||
EntityLookupSystem entityLookup,
|
||||
IUserInterfaceManager userInterfaceManager,
|
||||
IConfigurationManager config)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_system = system;
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
_entityLookup = entityLookup;
|
||||
_userInterfaceManager = userInterfaceManager;
|
||||
ZIndex = 200;
|
||||
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
// Setting these to a specific ttf would break the antag symbols
|
||||
_font = resourceCache.NotoStack();
|
||||
_fontBold = resourceCache.NotoStack(variation: "Bold");
|
||||
|
||||
config.OnValueChanged(CCVars.AdminOverlayClassic, (show) => { _overlayClassic = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlaySymbols, (show) => { _overlaySymbols = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayPlaytime, (show) => { _overlayPlaytime = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayStartingJob, (show) => { _overlayStartingJob = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayGhostHideDistance, (f) => { _ghostHideDistance = f; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayGhostFadeDistance, (f) => { _ghostFadeDistance = f; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayStackMax, (i) => { _overlayStackMax = i; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayMergeDistance, (f) => { _overlayMergeDistance = f; }, true);
|
||||
}
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
@@ -47,75 +72,147 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var viewport = args.WorldAABB;
|
||||
var colorDisconnected = Color.White;
|
||||
var uiScale = _userInterfaceManager.RootControl.UIScale;
|
||||
var lineoffset = new Vector2(0f, 14f) * uiScale;
|
||||
var drawnOverlays = new List<(Vector2,Vector2)>() ; // A saved list of the overlays already drawn
|
||||
|
||||
//TODO make this adjustable via GUI
|
||||
var classic = _config.GetCVar(CCVars.AdminOverlayClassic);
|
||||
var playTime = _config.GetCVar(CCVars.AdminOverlayPlaytime);
|
||||
var startingJob = _config.GetCVar(CCVars.AdminOverlayStartingJob);
|
||||
|
||||
foreach (var playerInfo in _system.PlayerList)
|
||||
// Get all player positions before drawing overlays, so they can be sorted before iteration
|
||||
var sortable = new List<(PlayerInfo, Box2, EntityUid, Vector2)>();
|
||||
foreach (var info in _system.PlayerList)
|
||||
{
|
||||
var entity = _entityManager.GetEntity(playerInfo.NetEntity);
|
||||
var entity = _entityManager.GetEntity(info.NetEntity);
|
||||
|
||||
// Otherwise the entity can not exist yet
|
||||
if (entity == null || !_entityManager.EntityExists(entity))
|
||||
{
|
||||
// If entity does not exist or is on a different map, skip
|
||||
if (entity == null
|
||||
|| !_entityManager.EntityExists(entity)
|
||||
|| _entityManager.GetComponent<TransformComponent>(entity.Value).MapID != args.MapId)
|
||||
continue;
|
||||
}
|
||||
|
||||
// if not on the same map, continue
|
||||
if (_entityManager.GetComponent<TransformComponent>(entity.Value).MapID != args.MapId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var aabb = _entityLookup.GetWorldAABB(entity.Value);
|
||||
|
||||
// if not on screen, continue
|
||||
// if not on screen, skip
|
||||
if (!aabb.Intersects(in viewport))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var uiScale = _userInterfaceManager.RootControl.UIScale;
|
||||
var lineoffset = new Vector2(0f, 14f) * uiScale;
|
||||
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
|
||||
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
|
||||
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
|
||||
// Get on-screen coordinates of player
|
||||
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center).Rounded();
|
||||
|
||||
sortable.Add((info, aabb, entity.Value, screenCoordinates));
|
||||
}
|
||||
|
||||
// Draw overlays for visible players, starting from the top of the screen
|
||||
foreach (var info in sortable.OrderBy(s => s.Item4.Y).ToList())
|
||||
{
|
||||
var playerInfo = info.Item1;
|
||||
var aabb = info.Item2;
|
||||
var entity = info.Item3;
|
||||
var screenCoordinatesCenter = info.Item4;
|
||||
//the center position is kept separately, for simpler position comparison later
|
||||
var centerOffset = new Vector2(28f, -18f) * uiScale;
|
||||
var screenCoordinates = screenCoordinatesCenter + centerOffset;
|
||||
var alpha = 1f;
|
||||
|
||||
//TODO make a smarter system where the starting offset can be modified by the predicted position and size of already-drawn overlays/stacks?
|
||||
var currentOffset = Vector2.Zero;
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
|
||||
// Ghosts near the cursor are made transparent/invisible
|
||||
// TODO would be "cheaper" if playerinfo already contained a ghost bool, this gets called every frame for every onscreen player!
|
||||
if (_entityManager.HasComponent<GhostComponent>(entity))
|
||||
{
|
||||
// We want the map positions here, so we don't have to worry about resolution and such shenanigans
|
||||
var mobPosition = aabb.Center;
|
||||
var mousePosition = _eyeManager
|
||||
.ScreenToMap(_userInterfaceManager.MousePositionScaled.Position * uiScale)
|
||||
.Position;
|
||||
var dist = Vector2.Distance(mobPosition, mousePosition);
|
||||
if (dist < _ghostHideDistance)
|
||||
continue;
|
||||
|
||||
alpha = Math.Clamp((dist - _ghostHideDistance) / (_ghostFadeDistance - _ghostHideDistance), 0f, 1f);
|
||||
colorDisconnected.A = alpha;
|
||||
}
|
||||
|
||||
// If the new overlay text block is within merge distance of any previous ones
|
||||
// merge them into a stack so they don't hide each other
|
||||
var stack = drawnOverlays.FindAll(x =>
|
||||
Vector2.Distance(_eyeManager.ScreenToMap(x.Item1).Position, aabb.Center) <= _overlayMergeDistance);
|
||||
if (stack.Count > 0)
|
||||
{
|
||||
screenCoordinates = stack.First().Item1 + centerOffset;
|
||||
// Replacing this overlay's coordinates for the later save with the stack root's coordinates
|
||||
// so that other overlays don't try to stack to these coordinates
|
||||
screenCoordinatesCenter = stack.First().Item1;
|
||||
|
||||
var i = 1;
|
||||
foreach (var s in stack)
|
||||
{
|
||||
// additional entries after maximum stack size is reached will be drawn over the last entry
|
||||
if (i <= _overlayStackMax - 1)
|
||||
currentOffset = lineoffset + s.Item2 ;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Character name
|
||||
var color = Color.Aquamarine;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.CharacterName, uiScale, playerInfo.Connected ? color : colorDisconnected);
|
||||
currentOffset += lineoffset;
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
|
||||
// Username
|
||||
color = Color.Yellow;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.Username, uiScale, playerInfo.Connected ? color : colorDisconnected);
|
||||
currentOffset += lineoffset;
|
||||
|
||||
if (!string.IsNullOrEmpty(playerInfo.PlaytimeString) && playTime)
|
||||
// Playtime
|
||||
if (!string.IsNullOrEmpty(playerInfo.PlaytimeString) && _overlayPlaytime)
|
||||
{
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.PlaytimeString, uiScale, playerInfo.Connected ? Color.Orange : Color.White);
|
||||
color = Color.Orange;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.PlaytimeString, uiScale, playerInfo.Connected ? color : colorDisconnected);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(playerInfo.StartingJob) && startingJob)
|
||||
// Job
|
||||
if (!string.IsNullOrEmpty(playerInfo.StartingJob) && _overlayStartingJob)
|
||||
{
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, Loc.GetString(playerInfo.StartingJob), uiScale, playerInfo.Connected ? Color.GreenYellow : Color.White);
|
||||
color = Color.GreenYellow;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, Loc.GetString(playerInfo.StartingJob), uiScale, playerInfo.Connected ? color : colorDisconnected);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
if (classic && playerInfo.Antag)
|
||||
// Classic Antag Label
|
||||
if (_overlayClassic && playerInfo.Antag)
|
||||
{
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, _antagLabelClassic, uiScale, Color.OrangeRed);
|
||||
var symbol = _overlaySymbols ? Loc.GetString("player-tab-antag-prefix") : string.Empty;
|
||||
var label = _overlaySymbols
|
||||
? Loc.GetString("player-tab-character-name-antag-symbol",
|
||||
("symbol", symbol),
|
||||
("name", _antagLabelClassic))
|
||||
: _antagLabelClassic;
|
||||
color = Color.OrangeRed;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
else if (!classic && _filter.Contains(playerInfo.RoleProto))
|
||||
// Role Type
|
||||
else if (!_overlayClassic && _filter.Contains(playerInfo.RoleProto))
|
||||
{
|
||||
var label = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
|
||||
var color = playerInfo.RoleProto.Color;
|
||||
var symbol = _overlaySymbols && playerInfo.Antag ? playerInfo.RoleProto.Symbol : string.Empty;
|
||||
var role = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
|
||||
var label = _overlaySymbols
|
||||
? Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", role))
|
||||
: role;
|
||||
color = playerInfo.RoleProto.Color;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, label, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
//Save the coordinates and size of the text block, for stack merge check
|
||||
drawnOverlays.Add((screenCoordinatesCenter, currentOffset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Content.Client.Administration.Systems
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
|
||||
private AdminNameOverlay _adminNameOverlay = default!;
|
||||
|
||||
@@ -22,7 +23,14 @@ namespace Content.Client.Administration.Systems
|
||||
|
||||
private void InitializeOverlay()
|
||||
{
|
||||
_adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup, _userInterfaceManager);
|
||||
_adminNameOverlay = new AdminNameOverlay(
|
||||
this,
|
||||
EntityManager,
|
||||
_eyeManager,
|
||||
_resourceCache,
|
||||
_entityLookup,
|
||||
_userInterfaceManager,
|
||||
_configurationManager);
|
||||
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
<PanelContainer StyleClasses="BackgroundDark">
|
||||
<SplitContainer Orientation="Vertical">
|
||||
<SplitContainer Orientation="Vertical" ResizeMode="NotResizable">
|
||||
<SplitContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
<cc:PlayerListControl Access="Public" Name="ChannelSelector" HorizontalExpand="True" SizeFlagsStretchRatio="2" />
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="2">
|
||||
<BoxContainer Access="Public" Name="BwoinkArea" VerticalExpand="True" />
|
||||
</BoxContainer>
|
||||
</SplitContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal" SetHeight="30" >
|
||||
<CheckBox Name="AdminOnly" Access="Public" Text="{Loc 'admin-ahelp-admin-only'}" ToolTip="{Loc 'admin-ahelp-admin-only-tooltip'}" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<CheckBox Name="PlaySound" Access="Public" Text="{Loc 'admin-bwoink-play-sound'}" Pressed="True" />
|
||||
|
||||
@@ -2,11 +2,13 @@ using System.Linq;
|
||||
using Content.Client.Administration.Systems;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
@@ -16,6 +18,7 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||
public sealed partial class PlayerTab : Control
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
|
||||
private const string ArrowUp = "↑";
|
||||
@@ -41,6 +44,10 @@ public sealed partial class PlayerTab : Control
|
||||
_adminSystem.OverlayEnabled += OverlayEnabled;
|
||||
_adminSystem.OverlayDisabled += OverlayDisabled;
|
||||
|
||||
_config.OnValueChanged(CCVars.AdminPlayerlistSeparateSymbols, PlayerListSettingsChanged);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerlistHighlightedCharacterColor, PlayerListSettingsChanged);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerlistRoleTypeColor, PlayerListSettingsChanged);
|
||||
|
||||
OverlayButton.OnPressed += OverlayButtonPressed;
|
||||
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
|
||||
|
||||
@@ -106,6 +113,11 @@ public sealed partial class PlayerTab : Control
|
||||
|
||||
#region ListContainer
|
||||
|
||||
private void PlayerListSettingsChanged(bool _)
|
||||
{
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
|
||||
{
|
||||
_players = players;
|
||||
@@ -196,8 +208,7 @@ public sealed partial class PlayerTab : Control
|
||||
Header.Username => Compare(x.Username, y.Username),
|
||||
Header.Character => Compare(x.CharacterName, y.CharacterName),
|
||||
Header.Job => Compare(x.StartingJob, y.StartingJob),
|
||||
Header.Antagonist => x.Antag.CompareTo(y.Antag),
|
||||
Header.RoleType => Compare(x.RoleProto.Name , y.RoleProto.Name),
|
||||
Header.RoleType => y.SortWeight - x.SortWeight,
|
||||
Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
|
||||
_ => 1
|
||||
};
|
||||
|
||||
@@ -19,11 +19,6 @@
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<Label Name="AntagonistLabel"
|
||||
SizeFlagsStretchRatio="1"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<Label Name="RoleTypeLabel"
|
||||
SizeFlagsStretchRatio="2"
|
||||
HorizontalExpand="True"
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||
|
||||
@@ -14,17 +16,25 @@ public sealed partial class PlayerTabEntry : PanelContainer
|
||||
public PlayerTabEntry(PlayerInfo player, StyleBoxFlat styleBoxFlat)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
var config = IoCManager.Resolve<IConfigurationManager>();
|
||||
|
||||
UsernameLabel.Text = player.Username;
|
||||
if (!player.Connected)
|
||||
UsernameLabel.StyleClasses.Add("Disabled");
|
||||
JobLabel.Text = player.StartingJob;
|
||||
CharacterLabel.Text = player.CharacterName;
|
||||
var separateAntagSymbols = config.GetCVar(CCVars.AdminPlayerlistSeparateSymbols);
|
||||
var genericAntagSymbol = player.Antag ? Loc.GetString("player-tab-antag-prefix") : string.Empty;
|
||||
var roleSymbol = player.Antag ? player.RoleProto.Symbol : string.Empty;
|
||||
var symbol = separateAntagSymbols ? roleSymbol : genericAntagSymbol;
|
||||
CharacterLabel.Text = Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", player.CharacterName));
|
||||
|
||||
if (player.Antag && config.GetCVar(CCVars.AdminPlayerlistHighlightedCharacterColor))
|
||||
CharacterLabel.FontColorOverride = player.RoleProto.Color;
|
||||
if (player.IdentityName != player.CharacterName)
|
||||
CharacterLabel.Text += $" [{player.IdentityName}]";
|
||||
AntagonistLabel.Text = Loc.GetString(player.Antag ? "player-tab-is-antag-yes" : "player-tab-is-antag-no");
|
||||
RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
|
||||
RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
|
||||
if (config.GetCVar(CCVars.AdminPlayerlistRoleTypeColor))
|
||||
RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
|
||||
BackgroundColorPanel.PanelOverride = styleBoxFlat;
|
||||
OverallPlaytimeLabel.Text = player.PlaytimeString;
|
||||
PlayerEntity = player.NetEntity;
|
||||
|
||||
@@ -25,13 +25,6 @@
|
||||
Text="{Loc player-tab-job}"
|
||||
MouseFilter="Pass"/>
|
||||
<cc:VSeparator/>
|
||||
<Label Name="AntagonistLabel"
|
||||
SizeFlagsStretchRatio="1"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc player-tab-antagonist}"
|
||||
MouseFilter="Pass"/>
|
||||
<cc:VSeparator/>
|
||||
<Label Name="RoleTypeLabel"
|
||||
SizeFlagsStretchRatio="2"
|
||||
HorizontalExpand="True"
|
||||
|
||||
@@ -18,7 +18,6 @@ public sealed partial class PlayerTabHeader : Control
|
||||
UsernameLabel.OnKeyBindDown += UsernameClicked;
|
||||
CharacterLabel.OnKeyBindDown += CharacterClicked;
|
||||
JobLabel.OnKeyBindDown += JobClicked;
|
||||
AntagonistLabel.OnKeyBindDown += AntagonistClicked;
|
||||
RoleTypeLabel.OnKeyBindDown += RoleTypeClicked;
|
||||
PlaytimeLabel.OnKeyBindDown += PlaytimeClicked;
|
||||
}
|
||||
@@ -30,7 +29,6 @@ public sealed partial class PlayerTabHeader : Control
|
||||
Header.Username => UsernameLabel,
|
||||
Header.Character => CharacterLabel,
|
||||
Header.Job => JobLabel,
|
||||
Header.Antagonist => AntagonistLabel,
|
||||
Header.RoleType => RoleTypeLabel,
|
||||
Header.Playtime => PlaytimeLabel,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
|
||||
@@ -42,7 +40,6 @@ public sealed partial class PlayerTabHeader : Control
|
||||
UsernameLabel.Text = Loc.GetString("player-tab-username");
|
||||
CharacterLabel.Text = Loc.GetString("player-tab-character");
|
||||
JobLabel.Text = Loc.GetString("player-tab-job");
|
||||
AntagonistLabel.Text = Loc.GetString("player-tab-antagonist");
|
||||
RoleTypeLabel.Text = Loc.GetString("player-tab-roletype");
|
||||
PlaytimeLabel.Text = Loc.GetString("player-tab-playtime");
|
||||
}
|
||||
@@ -73,11 +70,6 @@ public sealed partial class PlayerTabHeader : Control
|
||||
HeaderClicked(args, Header.Job);
|
||||
}
|
||||
|
||||
private void AntagonistClicked(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
HeaderClicked(args, Header.Antagonist);
|
||||
}
|
||||
|
||||
private void RoleTypeClicked(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
HeaderClicked(args, Header.RoleType);
|
||||
@@ -97,7 +89,6 @@ public sealed partial class PlayerTabHeader : Control
|
||||
UsernameLabel.OnKeyBindDown -= UsernameClicked;
|
||||
CharacterLabel.OnKeyBindDown -= CharacterClicked;
|
||||
JobLabel.OnKeyBindDown -= JobClicked;
|
||||
AntagonistLabel.OnKeyBindDown -= AntagonistClicked;
|
||||
RoleTypeLabel.OnKeyBindDown -= RoleTypeClicked;
|
||||
PlaytimeLabel.OnKeyBindDown -= PlaytimeClicked;
|
||||
}
|
||||
@@ -108,7 +99,6 @@ public sealed partial class PlayerTabHeader : Control
|
||||
Username,
|
||||
Character,
|
||||
Job,
|
||||
Antagonist,
|
||||
RoleType,
|
||||
Playtime
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
if (args.Current is not AlertComponentState cast)
|
||||
return;
|
||||
|
||||
alerts.Comp.Alerts = cast.Alerts;
|
||||
alerts.Comp.Alerts = new(cast.Alerts);
|
||||
|
||||
UpdateHud(alerts);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
public sealed class NanoTaskCartridgeSystem : SharedNanoTaskCartridgeSystem;
|
||||
@@ -0,0 +1,32 @@
|
||||
<Control xmlns="https://spacestation14.io" xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
<BoxContainer Name="MainContainer"
|
||||
Orientation="Horizontal"
|
||||
SetWidth="250">
|
||||
<Button Name="MainButton"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
StyleClasses="ButtonSquare"
|
||||
Margin="-1 0 0 0">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="-5 0 0 0">
|
||||
<Label Name="TaskLabel"
|
||||
StyleClasses="LabelSubText" />
|
||||
<Label Name="TaskForLabel"
|
||||
StyleClasses="LabelSubText"
|
||||
Margin="0 -5 0 0" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Button>
|
||||
<Button Name="DoneButton"
|
||||
VerticalExpand="True"
|
||||
Text="{Loc 'nano-task-ui-done'}">
|
||||
<Button.StyleClasses>
|
||||
<system:String>ButtonSmall</system:String>
|
||||
<system:String>OpenLeft</system:String>
|
||||
</Button.StyleClasses>
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
@@ -0,0 +1,33 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Maths;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single control for a single NanoTask item
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NanoTaskItemControl : Control
|
||||
{
|
||||
public Action<int>? OnMainPressed;
|
||||
public Action<int>? OnDonePressed;
|
||||
|
||||
public NanoTaskItemControl(NanoTaskItemAndId item)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
TaskLabel.Text = item.Data.Description;
|
||||
TaskLabel.FontColorOverride = Color.White;
|
||||
TaskForLabel.Text = item.Data.TaskIsFor;
|
||||
|
||||
MainButton.OnPressed += _ => OnMainPressed?.Invoke(item.Id);
|
||||
DoneButton.OnPressed += _ => OnDonePressed?.Invoke(item.Id);
|
||||
|
||||
MainButton.Disabled = item.Data.IsTaskDone;
|
||||
DoneButton.Text = item.Data.IsTaskDone ? Loc.GetString("nano-task-ui-revert-done") : Loc.GetString("nano-task-ui-done");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc nano-task-ui-item-title}"
|
||||
MinSize="300 300">
|
||||
<PanelContainer StyleClasses="AngleRect">
|
||||
<BoxContainer Orientation="Vertical" Margin="4">
|
||||
<!-- Task Description Input -->
|
||||
<BoxContainer Orientation="Vertical" Margin="0 4">
|
||||
<Label Text="{Loc nano-task-ui-description-label}"
|
||||
StyleClasses="LabelHeading" />
|
||||
<PanelContainer StyleClasses="ButtonSquare">
|
||||
<LineEdit Name="DescriptionInput"
|
||||
PlaceHolder="{Loc nano-task-ui-description-placeholder}" />
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Task Requester Input -->
|
||||
<BoxContainer Orientation="Vertical" Margin="0 4">
|
||||
<Label Text="{Loc nano-task-ui-requester-label}"
|
||||
StyleClasses="LabelHeading" />
|
||||
<PanelContainer StyleClasses="ButtonSquare">
|
||||
<LineEdit Name="RequesterInput"
|
||||
PlaceHolder="{Loc nano-task-ui-requester-placeholder}" />
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Severity Buttons -->
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0 8 0 0">
|
||||
<Button Name="LowButton"
|
||||
Text="{Loc nano-task-ui-priority-low}"
|
||||
StyleClasses="OpenRight"
|
||||
MinSize="60 0" />
|
||||
<Button Name="MediumButton"
|
||||
Text="{Loc nano-task-ui-priority-medium}"
|
||||
StyleClasses="ButtonSquare"
|
||||
MinSize="60 0" />
|
||||
<Button Name="HighButton"
|
||||
Text="{Loc nano-task-ui-priority-high}"
|
||||
StyleClasses="OpenLeft"
|
||||
MinSize="60 0" />
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Verb Buttons -->
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 8 0 0">
|
||||
<Button Name="CancelButton"
|
||||
Text="{Loc nano-task-ui-cancel}"
|
||||
StyleClasses="OpenRight"
|
||||
MinSize="60 0" />
|
||||
<Button Name="DeleteButton"
|
||||
Text="{Loc nano-task-ui-delete}"
|
||||
StyleClasses="ButtonSquare"
|
||||
MinSize="60 0" />
|
||||
<Button Name="PrintButton"
|
||||
Text="{Loc nano-task-ui-print}"
|
||||
StyleClasses="ButtonSquare"
|
||||
MinSize="60 0" />
|
||||
<Button Name="SaveButton"
|
||||
Text="{Loc nano-task-ui-save}"
|
||||
StyleClasses="OpenLeft"
|
||||
MinSize="60 0" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Linq;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
/// <summary>
|
||||
/// Popup displayed to edit a NanoTask item
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NanoTaskItemPopup : DefaultWindow
|
||||
{
|
||||
private readonly ButtonGroup _priorityGroup = new();
|
||||
private int? _editingTaskId = null;
|
||||
|
||||
public Action<int, NanoTaskItem>? TaskSaved;
|
||||
public Action<int>? TaskDeleted;
|
||||
public Action<NanoTaskItem>? TaskCreated;
|
||||
public Action<NanoTaskItem>? TaskPrinted;
|
||||
|
||||
private NanoTaskItem MakeItem()
|
||||
{
|
||||
return new(
|
||||
description: DescriptionInput.Text,
|
||||
taskIsFor: RequesterInput.Text,
|
||||
isTaskDone: false,
|
||||
priority: _priorityGroup.Pressed switch {
|
||||
var item when item == LowButton => NanoTaskPriority.Low,
|
||||
var item when item == MediumButton => NanoTaskPriority.Medium,
|
||||
var item when item == HighButton => NanoTaskPriority.High,
|
||||
_ => NanoTaskPriority.Medium,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public NanoTaskItemPopup()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
LowButton.Group = _priorityGroup;
|
||||
MediumButton.Group = _priorityGroup;
|
||||
HighButton.Group = _priorityGroup;
|
||||
|
||||
CancelButton.OnPressed += _ => Close();
|
||||
DeleteButton.OnPressed += _ =>
|
||||
{
|
||||
if (_editingTaskId is int id)
|
||||
{
|
||||
TaskDeleted?.Invoke(id);
|
||||
}
|
||||
};
|
||||
PrintButton.OnPressed += _ =>
|
||||
{
|
||||
TaskPrinted?.Invoke(MakeItem());
|
||||
};
|
||||
SaveButton.OnPressed += _ =>
|
||||
{
|
||||
if (_editingTaskId is int id)
|
||||
{
|
||||
TaskSaved?.Invoke(id, MakeItem());
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskCreated?.Invoke(MakeItem());
|
||||
}
|
||||
};
|
||||
|
||||
DescriptionInput.OnTextChanged += args =>
|
||||
{
|
||||
if (args.Text.Length > NanoTaskItem.MaximumStringLength)
|
||||
DescriptionInput.Text = args.Text[..NanoTaskItem.MaximumStringLength];
|
||||
};
|
||||
RequesterInput.OnTextChanged += args =>
|
||||
{
|
||||
if (args.Text.Length > NanoTaskItem.MaximumStringLength)
|
||||
RequesterInput.Text = args.Text[..NanoTaskItem.MaximumStringLength];
|
||||
};
|
||||
}
|
||||
|
||||
public void SetEditingTaskId(int? id)
|
||||
{
|
||||
_editingTaskId = id;
|
||||
DeleteButton.Visible = id is not null;
|
||||
}
|
||||
|
||||
public void ResetInputs(NanoTaskItem? item)
|
||||
{
|
||||
if (item is NanoTaskItem task)
|
||||
{
|
||||
var button = task.Priority switch {
|
||||
NanoTaskPriority.High => HighButton,
|
||||
NanoTaskPriority.Medium => MediumButton,
|
||||
NanoTaskPriority.Low => LowButton,
|
||||
};
|
||||
button.Pressed = true;
|
||||
DescriptionInput.Text = task.Description;
|
||||
RequesterInput.Text = task.TaskIsFor;
|
||||
}
|
||||
else
|
||||
{
|
||||
MediumButton.Pressed = true;
|
||||
DescriptionInput.Text = "";
|
||||
RequesterInput.Text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Content.Client/CartridgeLoader/Cartridges/NanoTaskUi.cs
Normal file
82
Content.Client/CartridgeLoader/Cartridges/NanoTaskUi.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface.Fragments;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
/// <summary>
|
||||
/// UI fragment responsible for displaying NanoTask controls in a PDA and coordinating with the NanoTaskCartridgeSystem for state
|
||||
/// </summary>
|
||||
public sealed partial class NanoTaskUi : UIFragment
|
||||
{
|
||||
private NanoTaskUiFragment? _fragment;
|
||||
private NanoTaskItemPopup? _popup;
|
||||
|
||||
public override Control GetUIFragmentRoot()
|
||||
{
|
||||
return _fragment!;
|
||||
}
|
||||
|
||||
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
|
||||
{
|
||||
_fragment = new NanoTaskUiFragment();
|
||||
_popup = new NanoTaskItemPopup();
|
||||
_fragment.NewTask += () =>
|
||||
{
|
||||
_popup.ResetInputs(null);
|
||||
_popup.SetEditingTaskId(null);
|
||||
_popup.OpenCentered();
|
||||
};
|
||||
_fragment.OpenTask += id =>
|
||||
{
|
||||
if (_fragment.Tasks.Find(task => task.Id == id) is not NanoTaskItemAndId task)
|
||||
return;
|
||||
|
||||
_popup.ResetInputs(task.Data);
|
||||
_popup.SetEditingTaskId(task.Id);
|
||||
_popup.OpenCentered();
|
||||
};
|
||||
_fragment.ToggleTaskCompletion += id =>
|
||||
{
|
||||
if (_fragment.Tasks.Find(task => task.Id == id) is not NanoTaskItemAndId task)
|
||||
return;
|
||||
|
||||
userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskUpdateTask(new(id, new(
|
||||
description: task.Data.Description,
|
||||
taskIsFor: task.Data.TaskIsFor,
|
||||
isTaskDone: !task.Data.IsTaskDone,
|
||||
priority: task.Data.Priority
|
||||
))))));
|
||||
};
|
||||
_popup.TaskSaved += (id, data) =>
|
||||
{
|
||||
userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskUpdateTask(new(id, data)))));
|
||||
_popup.Close();
|
||||
};
|
||||
_popup.TaskDeleted += id =>
|
||||
{
|
||||
userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskDeleteTask(id))));
|
||||
_popup.Close();
|
||||
};
|
||||
_popup.TaskCreated += data =>
|
||||
{
|
||||
userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskAddTask(data))));
|
||||
_popup.Close();
|
||||
};
|
||||
_popup.TaskPrinted += data =>
|
||||
{
|
||||
userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskPrintTask(data))));
|
||||
};
|
||||
}
|
||||
|
||||
public override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
if (state is not NanoTaskUiState nanoTaskState)
|
||||
return;
|
||||
|
||||
_fragment?.UpdateState(nanoTaskState.Tasks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<cartridges:NanoTaskUiFragment xmlns:cartridges="clr-namespace:Content.Client.CartridgeLoader.Cartridges"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns="https://spacestation14.io" Margin="1 0 2 0">
|
||||
|
||||
<PanelContainer StyleClasses="BackgroundDark"></PanelContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical" Margin="8" SeparationOverride="8">
|
||||
<!-- Heading for High Priority Items -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<PanelContainer SetWidth="7" Margin="0 0 8 0">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#e93d58"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<Label Name="HighPriority" StyleClasses="LabelHeading"/>
|
||||
</BoxContainer>
|
||||
<!-- Location for High Priority Items -->
|
||||
<GridContainer Name="HighContainer"
|
||||
HorizontalExpand="True"
|
||||
Access="Public"
|
||||
Columns="2" />
|
||||
|
||||
<!-- Heading for Medium Priority Items -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<PanelContainer SetWidth="7" Margin="0 0 8 0">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#ef973c"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<Label Name="MediumPriority" StyleClasses="LabelHeading"/>
|
||||
</BoxContainer>
|
||||
<!-- Location for Medium Priority Items -->
|
||||
<GridContainer Name="MediumContainer"
|
||||
HorizontalExpand="True"
|
||||
Access="Public"
|
||||
Columns="2" />
|
||||
|
||||
<!-- Location for Low Priority Items -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<PanelContainer SetWidth="7" Margin="0 0 8 0">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#3dd425"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<Label Name="LowPriority" StyleClasses="LabelHeading"/>
|
||||
</BoxContainer>
|
||||
<!-- Location for Low Priority Items -->
|
||||
<GridContainer Name="LowContainer"
|
||||
HorizontalExpand="True"
|
||||
Access="Public"
|
||||
Columns="2" />
|
||||
|
||||
<Button Name="NewTaskButton" Text="{Loc 'nano-task-ui-new-task'}" HorizontalAlignment="Right"/>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</cartridges:NanoTaskUiFragment>
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Linq;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
/// <summary>
|
||||
/// Class displaying the main UI of NanoTask
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NanoTaskUiFragment : BoxContainer
|
||||
{
|
||||
public Action<int>? OpenTask;
|
||||
public Action<int>? ToggleTaskCompletion;
|
||||
public Action? NewTask;
|
||||
public List<NanoTaskItemAndId> Tasks = new();
|
||||
|
||||
public NanoTaskUiFragment()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
HorizontalExpand = true;
|
||||
VerticalExpand = true;
|
||||
NewTaskButton.OnPressed += _ => NewTask?.Invoke();
|
||||
}
|
||||
public void UpdateState(List<NanoTaskItemAndId> tasks)
|
||||
{
|
||||
Tasks = tasks;
|
||||
HighContainer.RemoveAllChildren();
|
||||
MediumContainer.RemoveAllChildren();
|
||||
LowContainer.RemoveAllChildren();
|
||||
|
||||
HighPriority.Text = Loc.GetString("nano-task-ui-heading-high-priority-tasks", ("amount", tasks.Count(task => task.Data.Priority == NanoTaskPriority.High)));
|
||||
MediumPriority.Text = Loc.GetString("nano-task-ui-heading-medium-priority-tasks", ("amount", tasks.Count(task => task.Data.Priority == NanoTaskPriority.Medium)));
|
||||
LowPriority.Text = Loc.GetString("nano-task-ui-heading-low-priority-tasks", ("amount", tasks.Count(task => task.Data.Priority == NanoTaskPriority.Low)));
|
||||
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
var container = task.Data.Priority switch {
|
||||
NanoTaskPriority.High => HighContainer,
|
||||
NanoTaskPriority.Medium => MediumContainer,
|
||||
NanoTaskPriority.Low => LowContainer,
|
||||
};
|
||||
var control = new NanoTaskItemControl(task);
|
||||
container.AddChild(control);
|
||||
control.OnMainPressed += id => OpenTask?.Invoke(id);
|
||||
control.OnDonePressed += id => ToggleTaskCompletion?.Invoke(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,13 +131,13 @@ public sealed partial class ChangelogTab : Control
|
||||
Margin = new Thickness(6, 0, 0, 0),
|
||||
};
|
||||
authorLabel.SetMessage(
|
||||
FormattedMessage.FromMarkupOrThrow(Loc.GetString("changelog-author-changed", ("author", author))));
|
||||
FormattedMessage.FromMarkupOrThrow(Loc.GetString("changelog-author-changed", ("author", FormattedMessage.EscapeText(author)))));
|
||||
ChangelogBody.AddChild(authorLabel);
|
||||
|
||||
foreach (var change in groupedEntry.SelectMany(c => c.Changes))
|
||||
{
|
||||
var text = new RichTextLabel();
|
||||
text.SetMessage(FormattedMessage.FromMarkupOrThrow(change.Message));
|
||||
text.SetMessage(FormattedMessage.FromUnformatted(change.Message));
|
||||
ChangelogBody.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
|
||||
@@ -169,7 +169,7 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
|
||||
var state = $"equipped-{correctedSlot}";
|
||||
|
||||
if (clothing.EquippedPrefix != null)
|
||||
if (!string.IsNullOrEmpty(clothing.EquippedPrefix))
|
||||
state = $"{clothing.EquippedPrefix}-equipped-{correctedSlot}";
|
||||
|
||||
if (clothing.EquippedState != null)
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CriminalRecords;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Security;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Client.AutoGenerated;
|
||||
@@ -32,7 +33,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
public readonly EntityUid Console;
|
||||
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
[ValidatePrototypeId<LocalizedDatasetPrototype>]
|
||||
private const string ReasonPlaceholders = "CriminalRecordsWantedReasonPlaceholders";
|
||||
|
||||
public Action<uint?>? OnKeySelected;
|
||||
@@ -333,8 +334,8 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
var field = "reason";
|
||||
var title = Loc.GetString("criminal-records-status-" + status.ToString().ToLower());
|
||||
var placeholders = _proto.Index<DatasetPrototype>(ReasonPlaceholders);
|
||||
var placeholder = Loc.GetString("criminal-records-console-reason-placeholder", ("placeholder", _random.Pick(placeholders.Values))); // just funny it doesn't actually get used
|
||||
var placeholders = _proto.Index<LocalizedDatasetPrototype>(ReasonPlaceholders);
|
||||
var placeholder = Loc.GetString("criminal-records-console-reason-placeholder", ("placeholder", _random.Pick(placeholders))); // just funny it doesn't actually get used
|
||||
var prompt = Loc.GetString("criminal-records-console-reason");
|
||||
var entry = new QuickDialogEntry(field, QuickDialogEntryType.LongText, prompt, placeholder);
|
||||
var entries = new List<QuickDialogEntry>() { entry };
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.Verbs;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -40,7 +41,17 @@ public sealed class ExamineButton : ContainerButton
|
||||
Disabled = true;
|
||||
}
|
||||
|
||||
ToolTip = verb.Message ?? verb.Text;
|
||||
TooltipSupplier = sender =>
|
||||
{
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(FormattedMessage.FromMarkupOrThrow(verb.Message ?? verb.Text));
|
||||
|
||||
var tooltip = new Tooltip();
|
||||
tooltip.GetChild(0).Children.Clear();
|
||||
tooltip.GetChild(0).Children.Add(label);
|
||||
|
||||
return tooltip;
|
||||
};
|
||||
|
||||
Icon = new TextureRect
|
||||
{
|
||||
|
||||
@@ -298,10 +298,28 @@ namespace Content.Client.Examine
|
||||
{
|
||||
Name = "ExamineButtonsHBox",
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalAlignment = Control.HAlignment.Right,
|
||||
HorizontalAlignment = Control.HAlignment.Stretch,
|
||||
VerticalAlignment = Control.VAlignment.Bottom,
|
||||
};
|
||||
|
||||
var hoverExamineBox = new BoxContainer
|
||||
{
|
||||
Name = "HoverExamineHBox",
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalAlignment = Control.HAlignment.Left,
|
||||
VerticalAlignment = Control.VAlignment.Center,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
|
||||
var clickExamineBox = new BoxContainer
|
||||
{
|
||||
Name = "ClickExamineHBox",
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalAlignment = Control.HAlignment.Right,
|
||||
VerticalAlignment = Control.VAlignment.Center,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
|
||||
// Examine button time
|
||||
foreach (var verb in verbs)
|
||||
{
|
||||
@@ -316,8 +334,15 @@ namespace Content.Client.Examine
|
||||
|
||||
var button = new ExamineButton(examine);
|
||||
|
||||
button.OnPressed += VerbButtonPressed;
|
||||
buttonsHBox.AddChild(button);
|
||||
if (examine.HoverVerb)
|
||||
{
|
||||
hoverExamineBox.AddChild(button);
|
||||
}
|
||||
else
|
||||
{
|
||||
button.OnPressed += VerbButtonPressed;
|
||||
clickExamineBox.AddChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
var vbox = _examineTooltipOpen?.GetChild(0).GetChild(0);
|
||||
@@ -334,6 +359,8 @@ namespace Content.Client.Examine
|
||||
{
|
||||
vbox.Children.Remove(hbox.First());
|
||||
}
|
||||
buttonsHBox.AddChild(hoverExamineBox);
|
||||
buttonsHBox.AddChild(clickExamineBox);
|
||||
vbox.AddChild(buttonsHBox);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,24 +29,25 @@
|
||||
VerticalAlignment="Center"
|
||||
Access="Public"
|
||||
Visible="False" />
|
||||
<!-- CP14 random reactions begin -->
|
||||
<BoxContainer Name="RandomVariations"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center">
|
||||
<controls:SplitBar MinHeight="10">
|
||||
</controls:SplitBar>
|
||||
<Label Name="RandomVariationsLabel"
|
||||
Text="{Loc 'cp14-guidebook-random-variations-title'}"
|
||||
Visible="False"
|
||||
HorizontalAlignment="Center"
|
||||
FontColorOverride="#1e6651" />
|
||||
<controls:SplitBar MinHeight="10">
|
||||
</controls:SplitBar>
|
||||
</BoxContainer>
|
||||
<!-- CP14 random reactions end -->
|
||||
</BoxContainer>
|
||||
<!-- CP14 random reactions begin -->
|
||||
<BoxContainer Name="RandomVariations"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center">
|
||||
<controls:SplitBar MinHeight="10">
|
||||
</controls:SplitBar>
|
||||
<Label Name="RandomVariationsLabel"
|
||||
Text="{Loc 'cp14-guidebook-random-variations-title'}"
|
||||
Visible="False"
|
||||
HorizontalAlignment="Center"
|
||||
FontColorOverride="#1e6651" />
|
||||
<controls:SplitBar MinHeight="10">
|
||||
</controls:SplitBar>
|
||||
</BoxContainer>
|
||||
<!-- CP14 random reactions end -->
|
||||
|
||||
</BoxContainer>
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" />
|
||||
</BoxContainer>
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -48,7 +49,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
}
|
||||
|
||||
private static bool IsHidden(HumanoidAppearanceComponent humanoid, HumanoidVisualLayers layer)
|
||||
=> humanoid.HiddenLayers.Contains(layer) || humanoid.PermanentlyHidden.Contains(layer);
|
||||
=> humanoid.HiddenLayers.ContainsKey(layer) || humanoid.PermanentlyHidden.Contains(layer);
|
||||
|
||||
private void UpdateLayers(HumanoidAppearanceComponent component, SpriteComponent sprite)
|
||||
{
|
||||
@@ -57,7 +58,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
|
||||
// add default species layers
|
||||
var speciesProto = _prototypeManager.Index(component.Species);
|
||||
var baseSprites = _prototypeManager.Index<HumanoidSpeciesBaseSpritesPrototype>(speciesProto.SpriteSet);
|
||||
var baseSprites = _prototypeManager.Index(speciesProto.SpriteSet);
|
||||
foreach (var (key, id) in baseSprites.Sprites)
|
||||
{
|
||||
oldLayers.Remove(key);
|
||||
@@ -214,7 +215,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
|
||||
humanoid.MarkingSet = markings;
|
||||
humanoid.PermanentlyHidden = new HashSet<HumanoidVisualLayers>();
|
||||
humanoid.HiddenLayers = new HashSet<HumanoidVisualLayers>();
|
||||
humanoid.HiddenLayers = new Dictionary<HumanoidVisualLayers, SlotFlags>();
|
||||
humanoid.CustomBaseLayers = customBaseLayers;
|
||||
humanoid.Sex = profile.Sex;
|
||||
humanoid.Gender = profile.Gender;
|
||||
@@ -402,23 +403,21 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetLayerVisibility(
|
||||
EntityUid uid,
|
||||
HumanoidAppearanceComponent humanoid,
|
||||
public override void SetLayerVisibility(
|
||||
Entity<HumanoidAppearanceComponent> ent,
|
||||
HumanoidVisualLayers layer,
|
||||
bool visible,
|
||||
bool permanent,
|
||||
SlotFlags? slot,
|
||||
ref bool dirty)
|
||||
{
|
||||
base.SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty);
|
||||
base.SetLayerVisibility(ent, layer, visible, slot, ref dirty);
|
||||
|
||||
var sprite = Comp<SpriteComponent>(uid);
|
||||
var sprite = Comp<SpriteComponent>(ent);
|
||||
if (!sprite.LayerMapTryGet(layer, out var index))
|
||||
{
|
||||
if (!visible)
|
||||
return;
|
||||
else
|
||||
index = sprite.LayerMapReserveBlank(layer);
|
||||
index = sprite.LayerMapReserveBlank(layer);
|
||||
}
|
||||
|
||||
var spriteLayer = sprite[index];
|
||||
@@ -428,13 +427,14 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
spriteLayer.Visible = visible;
|
||||
|
||||
// I fucking hate this. I'll get around to refactoring sprite layers eventually I swear
|
||||
// Just a week away...
|
||||
|
||||
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
|
||||
foreach (var markingList in ent.Comp.MarkingSet.Markings.Values)
|
||||
{
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
if (_markingManager.TryGetMarking(marking, out var markingPrototype) && markingPrototype.BodyPart == layer)
|
||||
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite);
|
||||
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, ent, sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,9 @@ namespace Content.Client.Input
|
||||
human.AddFunction(ContentKeyFunctions.Arcade1);
|
||||
human.AddFunction(ContentKeyFunctions.Arcade2);
|
||||
human.AddFunction(ContentKeyFunctions.Arcade3);
|
||||
//CP14 Keys
|
||||
human.AddFunction(ContentKeyFunctions.CP14OpenSkillMenu);
|
||||
//CP14 Keys end
|
||||
|
||||
// actions should be common (for ghosts, mobs, etc)
|
||||
common.AddFunction(ContentKeyFunctions.OpenActionsMenu);
|
||||
@@ -123,10 +126,6 @@ namespace Content.Client.Input
|
||||
common.AddFunction(ContentKeyFunctions.OpenDecalSpawnWindow);
|
||||
common.AddFunction(ContentKeyFunctions.OpenAdminMenu);
|
||||
common.AddFunction(ContentKeyFunctions.OpenGuidebook);
|
||||
|
||||
//CP14 Keys
|
||||
human.AddFunction(ContentKeyFunctions.CP14OpenKnowledgeMenu);
|
||||
//CP14 Keys end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,21 +68,13 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
{
|
||||
ServerListButton.Visible = false;
|
||||
}
|
||||
|
||||
AmountLineEdit.SetText(latheComponent.DefaultProductionAmount.ToString());
|
||||
}
|
||||
|
||||
MaterialsList.SetOwner(Entity);
|
||||
}
|
||||
|
||||
protected override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
|
||||
if (_entityManager.TryGetComponent<LatheComponent>(Entity, out var latheComp))
|
||||
{
|
||||
AmountLineEdit.SetText(latheComp.DefaultProductionAmount.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the list of all the recipes
|
||||
/// </summary>
|
||||
|
||||
@@ -4,7 +4,20 @@
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
|
||||
<BoxContainer Orientation="Vertical" Margin="8">
|
||||
<Label Text="{Loc 'ui-options-admin-player-panel'}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="PlayerlistSeparateSymbolsCheckBox" Text="{Loc 'ui-options-admin-playerlist-separate-symbols'}" />
|
||||
<CheckBox Name="PlayerlistCharacterColorCheckBox" Text="{Loc 'ui-options-admin-playerlist-character-color'}" />
|
||||
<CheckBox Name="PlayerlistRoleTypeColorCheckBox" Text="{Loc 'ui-options-admin-playerlist-roletype-color'}" />
|
||||
<Label Text="{Loc 'ui-options-admin-overlay-title'}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="EnableClassicOverlayCheckBox" Text="{Loc 'ui-options-enable-classic-overlay'}" />
|
||||
<CheckBox Name="EnableOverlaySymbolsCheckBox" Text="{Loc 'ui-options-enable-overlay-symbols'}" />
|
||||
<CheckBox Name="EnableOverlayPlaytimeCheckBox" Text="{Loc 'ui-options-enable-overlay-playtime'}" />
|
||||
<CheckBox Name="EnableOverlayStartingJobCheckBox" Text="{Loc 'ui-options-enable-overlay-starting-job'}" />
|
||||
<ui:OptionSlider Name="OverlayMergeDistanceSlider" Title="{Loc 'ui-options-overlay-merge-distance'}"/>
|
||||
<ui:OptionSlider Name="OverlayGhostFadeSlider" Title="{Loc 'ui-options-overlay-ghost-fade-distance'}"/>
|
||||
<ui:OptionSlider Name="OverlayGhostHideSlider" Title="{Loc 'ui-options-overlay-ghost-hide-distance'}"/>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<ui:OptionsTabControlRow Name="Control" Access="Public" />
|
||||
|
||||
@@ -8,13 +8,45 @@ namespace Content.Client.Options.UI.Tabs;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminOptionsTab : Control
|
||||
{
|
||||
private const float OverlayMergeMin = 0.05f;
|
||||
private const float OverlayMergeMax = 0.95f;
|
||||
private const int OverlayGhostFadeMin = 0;
|
||||
private const int OverlayGhostFadeMax = 10;
|
||||
private const int OverlayGhostHideMin = 0;
|
||||
private const int OverlayGhostHideMax = 5;
|
||||
|
||||
public AdminOptionsTab()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Control.AddOptionCheckBox(CCVars.AdminPlayerlistSeparateSymbols, PlayerlistSeparateSymbolsCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AdminPlayerlistHighlightedCharacterColor, PlayerlistCharacterColorCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AdminPlayerlistRoleTypeColor, PlayerlistRoleTypeColorCheckBox);
|
||||
|
||||
Control.AddOptionCheckBox(CCVars.AdminOverlayClassic, EnableClassicOverlayCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AdminOverlaySymbols, EnableOverlaySymbolsCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AdminOverlayPlaytime, EnableOverlayPlaytimeCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AdminOverlayStartingJob, EnableOverlayStartingJobCheckBox);
|
||||
|
||||
Control.Initialize();
|
||||
|
||||
Control.AddOptionPercentSlider(
|
||||
CCVars.AdminOverlayMergeDistance,
|
||||
OverlayMergeDistanceSlider,
|
||||
OverlayMergeMin,
|
||||
OverlayMergeMax);
|
||||
|
||||
Control.AddOptionSlider(
|
||||
CCVars.AdminOverlayGhostFadeDistance,
|
||||
OverlayGhostFadeSlider,
|
||||
OverlayGhostFadeMin,
|
||||
OverlayGhostFadeMax);
|
||||
|
||||
Control.AddOptionSlider(
|
||||
CCVars.AdminOverlayGhostHideDistance,
|
||||
OverlayGhostHideSlider,
|
||||
OverlayGhostHideMin,
|
||||
OverlayGhostHideMax);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
|
||||
//CP14
|
||||
AddHeader("ui-options-header-cp14");
|
||||
AddButton(ContentKeyFunctions.CP14OpenKnowledgeMenu);
|
||||
AddButton(ContentKeyFunctions.CP14OpenSkillMenu);
|
||||
//CP14 end
|
||||
|
||||
foreach (var control in _keyControls.Values)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Parallax;
|
||||
using Content.Client.Weather;
|
||||
using Content.Shared._CP14.DayCycle.Components;
|
||||
using Content.Shared._CP14.CloudShadow;
|
||||
using Content.Shared.Salvage;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -62,7 +62,7 @@ public sealed partial class StencilOverlay : Overlay
|
||||
{
|
||||
foreach (var (proto, weather) in comp.Weather)
|
||||
{
|
||||
if (!_protoManager.TryIndex<WeatherPrototype>(proto, out var weatherProto))
|
||||
if (!_protoManager.TryIndex(proto, out var weatherProto))
|
||||
continue;
|
||||
|
||||
var alpha = _weather.GetPercent(weather, mapUid);
|
||||
|
||||
@@ -11,13 +11,21 @@ public sealed class PaperVisualizerSystem : VisualizerSystem<PaperVisualsCompone
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (AppearanceSystem.TryGetData<PaperStatus>(uid, PaperVisuals.Status , out var writingStatus, args.Component))
|
||||
if (AppearanceSystem.TryGetData<PaperStatus>(uid, PaperVisuals.Status, out var writingStatus, args.Component))
|
||||
args.Sprite.LayerSetVisible(PaperVisualLayers.Writing, writingStatus == PaperStatus.Written);
|
||||
|
||||
if (AppearanceSystem.TryGetData<string>(uid, PaperVisuals.Stamp, out var stampState, args.Component))
|
||||
{
|
||||
args.Sprite.LayerSetState(PaperVisualLayers.Stamp, stampState);
|
||||
args.Sprite.LayerSetVisible(PaperVisualLayers.Stamp, true);
|
||||
if (stampState != string.Empty)
|
||||
{
|
||||
args.Sprite.LayerSetState(PaperVisualLayers.Stamp, stampState);
|
||||
args.Sprite.LayerSetVisible(PaperVisualLayers.Stamp, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Sprite.LayerSetVisible(PaperVisualLayers.Stamp, false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Content.Client.Parallax.Data;
|
||||
/// <summary>
|
||||
/// Prototype data for a parallax.
|
||||
/// </summary>
|
||||
[Prototype("parallax")]
|
||||
[Prototype]
|
||||
public sealed partial class ParallaxPrototype : IPrototype
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using Content.Client.Parallax.Data;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -17,27 +19,50 @@ public sealed class ParallaxControl : Control
|
||||
[Dependency] private readonly IParallaxManager _parallaxManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
private string _parallaxPrototype = "FastSpace";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] public Vector2 Offset { get; set; }
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float SpeedX { get; set; } = 0.0f;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float SpeedY { get; set; } = 0.0f;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float ScaleX { get; set; } = 1.0f;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float ScaleY { get; set; } = 1.0f;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public string ParallaxPrototype
|
||||
{
|
||||
get => _parallaxPrototype;
|
||||
set
|
||||
{
|
||||
_parallaxPrototype = value;
|
||||
_parallaxManager.LoadParallaxByName(value);
|
||||
}
|
||||
}
|
||||
|
||||
public ParallaxControl()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
Offset = new Vector2(_random.Next(0, 1000), _random.Next(0, 1000));
|
||||
|
||||
RectClipContent = true;
|
||||
_parallaxManager.LoadParallaxByName("FastSpace");
|
||||
_parallaxManager.LoadParallaxByName(_parallaxPrototype);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
foreach (var layer in _parallaxManager.GetParallaxLayers("FastSpace"))
|
||||
var currentTime = (float) _timing.RealTime.TotalSeconds;
|
||||
var offset = Offset + new Vector2(currentTime * SpeedX, currentTime * SpeedY);
|
||||
|
||||
foreach (var layer in _parallaxManager.GetParallaxLayers(_parallaxPrototype))
|
||||
{
|
||||
var tex = layer.Texture;
|
||||
var texSize = (tex.Size.X * (int) Size.X, tex.Size.Y * (int) Size.X) * layer.Config.Scale.Floored() / 1920;
|
||||
var texSize = new Vector2i(
|
||||
(int)(tex.Size.X * Size.X * layer.Config.Scale.X / 1920 * ScaleX),
|
||||
(int)(tex.Size.Y * Size.X * layer.Config.Scale.Y / 1920 * ScaleY)
|
||||
);
|
||||
var ourSize = PixelSize;
|
||||
|
||||
var currentTime = (float) _timing.RealTime.TotalSeconds;
|
||||
var offset = Offset + new Vector2(currentTime * 100f, currentTime * 0f);
|
||||
//Protection from division by zero.
|
||||
texSize.X = Math.Max(texSize.X, 1);
|
||||
texSize.Y = Math.Max(texSize.Y, 1);
|
||||
|
||||
if (layer.Config.Tiled)
|
||||
{
|
||||
|
||||
@@ -237,6 +237,12 @@ namespace Content.Client.Popups
|
||||
PopupEntity(message, uid, recipient.Value, type);
|
||||
}
|
||||
|
||||
public override void PopupPredicted(string? message, EntityUid uid, EntityUid? recipient, Filter filter, bool recordReplay, PopupType type = PopupType.Small)
|
||||
{
|
||||
if (recipient != null && _timing.IsFirstTimePredicted)
|
||||
PopupEntity(message, uid, recipient.Value, type);
|
||||
}
|
||||
|
||||
public override void PopupPredicted(string? recipientMessage, string? othersMessage, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small)
|
||||
{
|
||||
if (recipient != null && _timing.IsFirstTimePredicted)
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using Content.Shared.DrawDepth;
|
||||
using Content.Client.UserInterface.Systems.Sandbox;
|
||||
using Content.Shared.SubFloor;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.SubFloor;
|
||||
|
||||
public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
|
||||
private bool _showAll;
|
||||
|
||||
@@ -18,8 +21,13 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
||||
{
|
||||
if (_showAll == value) return;
|
||||
_showAll = value;
|
||||
_ui.GetUIController<SandboxUIController>().SetToggleSubfloors(value);
|
||||
|
||||
UpdateAll();
|
||||
var ev = new ShowSubfloorRequestEvent()
|
||||
{
|
||||
Value = value,
|
||||
};
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +36,20 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SubFloorHideComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
||||
SubscribeNetworkEvent<ShowSubfloorRequestEvent>(OnRequestReceived);
|
||||
SubscribeLocalEvent<LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(LocalPlayerDetachedEvent ev)
|
||||
{
|
||||
// Vismask resets so need to reset this.
|
||||
ShowAll = false;
|
||||
}
|
||||
|
||||
private void OnRequestReceived(ShowSubfloorRequestEvent ev)
|
||||
{
|
||||
// When client receives request Queue an update on all vis.
|
||||
UpdateAll();
|
||||
}
|
||||
|
||||
private void OnAppearanceChanged(EntityUid uid, SubFloorHideComponent component, ref AppearanceChangeEvent args)
|
||||
|
||||
@@ -14,6 +14,7 @@ public sealed partial class GhostGui : UIWidget
|
||||
public event Action? RequestWarpsPressed;
|
||||
public event Action? ReturnToBodyPressed;
|
||||
public event Action? GhostRolesPressed;
|
||||
private int _prevNumberRoles;
|
||||
|
||||
public GhostGui()
|
||||
{
|
||||
@@ -26,6 +27,7 @@ public sealed partial class GhostGui : UIWidget
|
||||
GhostWarpButton.OnPressed += _ => RequestWarpsPressed?.Invoke();
|
||||
ReturnToBodyButton.OnPressed += _ => ReturnToBodyPressed?.Invoke();
|
||||
GhostRolesButton.OnPressed += _ => GhostRolesPressed?.Invoke();
|
||||
GhostRolesButton.OnPressed += _ => GhostRolesButton.StyleClasses.Remove(StyleBase.ButtonCaution);
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
@@ -41,14 +43,13 @@ public sealed partial class GhostGui : UIWidget
|
||||
if (roles != null)
|
||||
{
|
||||
GhostRolesButton.Text = Loc.GetString("ghost-gui-ghost-roles-button", ("count", roles));
|
||||
if (roles > 0)
|
||||
|
||||
if (roles > _prevNumberRoles)
|
||||
{
|
||||
GhostRolesButton.StyleClasses.Add(StyleBase.ButtonCaution);
|
||||
}
|
||||
else
|
||||
{
|
||||
GhostRolesButton.StyleClasses.Remove(StyleBase.ButtonCaution);
|
||||
}
|
||||
|
||||
_prevNumberRoles = (int)roles;
|
||||
}
|
||||
|
||||
TargetWindow.Populate();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client._CP14.UserInterface.Systems.Skill;
|
||||
using Content.Client.UserInterface.Systems.Actions;
|
||||
using Content.Client.UserInterface.Systems.Admin;
|
||||
using Content.Client.UserInterface.Systems.Bwoink;
|
||||
@@ -24,7 +25,7 @@ public sealed class GameTopMenuBarUIController : UIController
|
||||
[Dependency] private readonly SandboxUIController _sandbox = default!;
|
||||
[Dependency] private readonly GuidebookUIController _guidebook = default!;
|
||||
[Dependency] private readonly EmotesUIController _emotes = default!;
|
||||
[Dependency] private readonly CP14KnowledgeUIController _knowledge = default!; //CP14
|
||||
[Dependency] private readonly CP14SkillUIController _skill = default!; //CP14
|
||||
|
||||
private GameTopMenuBar? GameTopMenuBar => UIManager.GetActiveUIWidgetOrNull<GameTopMenuBar>();
|
||||
|
||||
@@ -48,7 +49,7 @@ public sealed class GameTopMenuBarUIController : UIController
|
||||
_action.UnloadButton();
|
||||
_sandbox.UnloadButton();
|
||||
_emotes.UnloadButton();
|
||||
_knowledge.UnloadButton(); //CP14
|
||||
_skill.UnloadButton(); //CP14
|
||||
}
|
||||
|
||||
public void LoadButtons()
|
||||
@@ -62,6 +63,6 @@ public sealed class GameTopMenuBarUIController : UIController
|
||||
_action.LoadButton();
|
||||
_sandbox.LoadButton();
|
||||
_emotes.LoadButton();
|
||||
_knowledge.LoadButton(); //CP14
|
||||
_skill.LoadButton(); //CP14
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +35,11 @@
|
||||
/>
|
||||
<!-- CP14 UI part -->
|
||||
<ui:MenuButton
|
||||
Name="CP14KnowledgeButton"
|
||||
Name="CP14SkillButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/Interface/students-cap.svg.192dpi.png'}"
|
||||
ToolTip="{Loc 'cp14-game-hud-open-knowledge-menu-button-tooltip'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.CP14OpenKnowledgeMenu}"
|
||||
ToolTip="{Loc 'cp14-game-hud-open-skill-menu-button-tooltip'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.CP14OpenSkillMenu}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
|
||||
@@ -39,7 +39,6 @@ public sealed class SandboxUIController : UIController, IOnStateChanged<Gameplay
|
||||
[UISystemDependency] private readonly DebugPhysicsSystem _debugPhysics = default!;
|
||||
[UISystemDependency] private readonly MarkerSystem _marker = default!;
|
||||
[UISystemDependency] private readonly SandboxSystem _sandbox = default!;
|
||||
[UISystemDependency] private readonly SubFloorHideSystem _subfloorHide = default!;
|
||||
|
||||
private SandboxWindow? _window;
|
||||
|
||||
@@ -117,10 +116,11 @@ public sealed class SandboxUIController : UIController, IOnStateChanged<Gameplay
|
||||
|
||||
_window.OnOpen += () => { SandboxButton!.Pressed = true; };
|
||||
_window.OnClose += () => { SandboxButton!.Pressed = false; };
|
||||
|
||||
// TODO: These need moving to opened so at least if they're not synced properly on open they work.
|
||||
_window.ToggleLightButton.Pressed = !_light.Enabled;
|
||||
_window.ToggleFovButton.Pressed = !_eye.CurrentEye.DrawFov;
|
||||
_window.ToggleShadowsButton.Pressed = !_light.DrawShadows;
|
||||
_window.ToggleSubfloorButton.Pressed = _subfloorHide.ShowAll;
|
||||
_window.ShowMarkersButton.Pressed = _marker.MarkersVisible;
|
||||
_window.ShowBbButton.Pressed = (_debugPhysics.Flags & PhysicsDebugFlags.Shapes) != 0x0;
|
||||
|
||||
@@ -219,4 +219,16 @@ public sealed class SandboxUIController : UIController, IOnStateChanged<Gameplay
|
||||
_window.Close();
|
||||
}
|
||||
}
|
||||
|
||||
#region Buttons
|
||||
|
||||
public void SetToggleSubfloors(bool value)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
_window.ToggleSubfloorButton.Pressed = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Content.Client.SubFloor;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -7,8 +8,18 @@ namespace Content.Client.UserInterface.Systems.Sandbox.Windows;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SandboxWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = null!;
|
||||
|
||||
public SandboxWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
// Make sure state is up to date.
|
||||
ToggleSubfloorButton.Pressed = _entManager.System<SubFloorHideSystem>().ShowAll;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ public sealed class StorageWindow : BaseWindow
|
||||
private ValueList<EntityUid> _contained = new();
|
||||
private ValueList<EntityUid> _toRemove = new();
|
||||
|
||||
// Manually store this because you can't have a 0x0 GridContainer but we still need to add child controls for 1x1 containers.
|
||||
private Vector2i _pieceGridSize;
|
||||
|
||||
private TextureButton? _backButton;
|
||||
|
||||
private bool _isDirty;
|
||||
@@ -408,11 +411,14 @@ public sealed class StorageWindow : BaseWindow
|
||||
_contained.Clear();
|
||||
_contained.AddRange(storageComp.Container.ContainedEntities.Reverse());
|
||||
|
||||
var width = boundingGrid.Width + 1;
|
||||
var height = boundingGrid.Height + 1;
|
||||
|
||||
// Build the grid representation
|
||||
if (_pieceGrid.Rows - 1 != boundingGrid.Height || _pieceGrid.Columns - 1 != boundingGrid.Width)
|
||||
if (_pieceGrid.Rows != _pieceGridSize.Y || _pieceGrid.Columns != _pieceGridSize.X)
|
||||
{
|
||||
_pieceGrid.Rows = boundingGrid.Height + 1;
|
||||
_pieceGrid.Columns = boundingGrid.Width + 1;
|
||||
_pieceGrid.Rows = height;
|
||||
_pieceGrid.Columns = width;
|
||||
_controlGrid.Clear();
|
||||
|
||||
for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++)
|
||||
@@ -430,6 +436,7 @@ public sealed class StorageWindow : BaseWindow
|
||||
}
|
||||
}
|
||||
|
||||
_pieceGridSize = new(width, height);
|
||||
_toRemove.Clear();
|
||||
|
||||
// Remove entities no longer relevant / Update existing ones
|
||||
|
||||
@@ -3,10 +3,12 @@ using Content.Client.ContextMenu.UI;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Verbs.UI
|
||||
{
|
||||
@@ -27,7 +29,17 @@ namespace Content.Client.Verbs.UI
|
||||
|
||||
public VerbMenuElement(Verb verb) : base(verb.Text)
|
||||
{
|
||||
ToolTip = verb.Message;
|
||||
TooltipSupplier = sender =>
|
||||
{
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(FormattedMessage.FromMarkupOrThrow(verb.Message ?? verb.Text));
|
||||
|
||||
var tooltip = new Tooltip();
|
||||
tooltip.GetChild(0).Children.Clear();
|
||||
tooltip.GetChild(0).Children.Add(label);
|
||||
|
||||
return tooltip;
|
||||
};
|
||||
Disabled = verb.Disabled;
|
||||
Verb = verb;
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace Content.Client.Verbs
|
||||
{
|
||||
// maybe send an informative pop-up message.
|
||||
if (!string.IsNullOrWhiteSpace(verb.Message))
|
||||
_popupSystem.PopupEntity(verb.Message, user);
|
||||
_popupSystem.PopupEntity(FormattedMessage.RemoveMarkupOrThrow(verb.Message), user);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
using Content.Shared.DisplacementMap;
|
||||
|
||||
namespace Content.Client._CP14.Displacement;
|
||||
|
||||
/// <summary>
|
||||
/// Simply apply displacements to sprite layers
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class CP14SimpleDisplacementMapComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public Dictionary<string, DisplacementData> Displacements = new();
|
||||
|
||||
public readonly HashSet<string> RevealedLayers = new();
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using Content.Client.DisplacementMap;
|
||||
using Content.Shared.Item;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client._CP14.Displacement;
|
||||
|
||||
public sealed class CP14SimpleDisplacementMapSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DisplacementMapSystem _displacement = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CP14SimpleDisplacementMapComponent, VisualsChangedEvent>(OnVisualChanged);
|
||||
}
|
||||
|
||||
private void OnVisualChanged(Entity<CP14SimpleDisplacementMapComponent> ent, ref VisualsChangedEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
foreach (var key in ent.Comp.RevealedLayers)
|
||||
{
|
||||
sprite.RemoveLayer(key);
|
||||
}
|
||||
ent.Comp.RevealedLayers.Clear();
|
||||
|
||||
foreach (var (key, dData) in ent.Comp.Displacements)
|
||||
{
|
||||
if (!sprite.LayerMapTryGet(key, out var index))
|
||||
continue;
|
||||
|
||||
_displacement.TryAddDisplacement(dData, sprite, index, key, ent.Comp.RevealedLayers);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared._CP14.Farming;
|
||||
using Content.Shared._CP14.Farming.Components;
|
||||
using Content.Shared.Rounding;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
@@ -32,7 +33,7 @@ public sealed class ClientCP14FarmingSystem : CP14SharedFarmingSystem
|
||||
if (!TryComp<SpriteComponent>(visuals, out var sprite))
|
||||
return;
|
||||
|
||||
if (!TryComp<CP14PlantComponent>(visuals, out var plant))
|
||||
if (!PlantQuery.TryComp(visuals, out var plant))
|
||||
return;
|
||||
|
||||
var growthState = ContentHelpers.RoundToNearestLevels(plant.GrowthLevel, 1, visuals.Comp.GrowthSteps);
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
using Content.Shared._CP14.Knowledge;
|
||||
using Content.Shared._CP14.Knowledge.Events;
|
||||
using Content.Shared._CP14.Knowledge.Prototypes;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._CP14.Knowledge;
|
||||
|
||||
public sealed class ClientCP14KnowledgeSystem : SharedCP14KnowledgeSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
|
||||
public event Action<KnowledgeData>? OnKnowledgeUpdate;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<CP14KnowledgeInfoEvent>(OnCharacterKnowledgeEvent);
|
||||
}
|
||||
|
||||
public void RequestKnowledgeInfo()
|
||||
{
|
||||
var entity = _players.LocalEntity;
|
||||
if (entity is null)
|
||||
return;
|
||||
|
||||
RaiseNetworkEvent(new CP14RequestKnowledgeInfoEvent(GetNetEntity(entity.Value)));
|
||||
}
|
||||
|
||||
private void OnCharacterKnowledgeEvent(CP14KnowledgeInfoEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
var entity = GetEntity(msg.NetEntity);
|
||||
var data = new KnowledgeData(entity, msg.AllKnowledge);
|
||||
|
||||
OnKnowledgeUpdate?.Invoke(data);
|
||||
}
|
||||
|
||||
public readonly record struct KnowledgeData(
|
||||
EntityUid Entity,
|
||||
HashSet<ProtoId<CP14KnowledgePrototype>> AllKnowledge
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using Content.Shared._CP14.NightVision;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client._CP14.NightVision;
|
||||
|
||||
public sealed class CP14ClientNightVisionSystem : CP14SharedNightVisionSystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CP14NightVisionComponent, CP14ToggleNightVisionEvent>(OnToggleNightVision);
|
||||
SubscribeLocalEvent<CP14NightVisionComponent, PlayerDetachedEvent>(OnPlayerDetached);
|
||||
}
|
||||
|
||||
protected override void OnRemove(Entity<CP14NightVisionComponent> ent, ref ComponentRemove args)
|
||||
{
|
||||
base.OnRemove(ent, ref args);
|
||||
|
||||
NightVisionOff(ent);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(Entity<CP14NightVisionComponent> ent, ref PlayerDetachedEvent args)
|
||||
{
|
||||
NightVisionOff(ent);
|
||||
}
|
||||
|
||||
private void OnToggleNightVision(Entity<CP14NightVisionComponent> ent, ref CP14ToggleNightVisionEvent args)
|
||||
{
|
||||
NightVisionToggle(ent);
|
||||
}
|
||||
|
||||
private void NightVisionOn(Entity<CP14NightVisionComponent> ent)
|
||||
{
|
||||
if (_playerManager.LocalSession?.AttachedEntity != ent)
|
||||
return;
|
||||
|
||||
var nightVisionLight = Spawn(ent.Comp.LightPrototype, Transform(ent).Coordinates);
|
||||
_transform.SetParent(nightVisionLight, ent);
|
||||
_transform.SetWorldRotation(nightVisionLight, _transform.GetWorldRotation(ent));
|
||||
ent.Comp.LocalLightEntity = nightVisionLight;
|
||||
}
|
||||
|
||||
private void NightVisionOff(Entity<CP14NightVisionComponent> ent)
|
||||
{
|
||||
QueueDel(ent.Comp.LocalLightEntity);
|
||||
ent.Comp.LocalLightEntity = null;
|
||||
}
|
||||
|
||||
private void NightVisionToggle(Entity<CP14NightVisionComponent> ent)
|
||||
{
|
||||
if (ent.Comp.LocalLightEntity == null)
|
||||
{
|
||||
NightVisionOn(ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
NightVisionOff(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared._CP14.DayCycle.Components;
|
||||
using Content.Shared._CP14.CloudShadow;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
|
||||
57
Content.Client/_CP14/Skill/CP14ClientSkillSystem.cs
Normal file
57
Content.Client/_CP14/Skill/CP14ClientSkillSystem.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Content.Shared._CP14.Skill;
|
||||
using Content.Shared._CP14.Skill.Components;
|
||||
using Content.Shared._CP14.Skill.Prototypes;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._CP14.Skill;
|
||||
|
||||
public sealed partial class CP14ClientSkillSystem : CP14SharedSkillSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
public event Action<EntityUid>? OnSkillUpdate;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CP14SkillStorageComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
|
||||
}
|
||||
|
||||
private void OnAfterAutoHandleState(Entity<CP14SkillStorageComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (ent != _playerManager.LocalEntity)
|
||||
return;
|
||||
|
||||
OnSkillUpdate?.Invoke(ent.Owner);
|
||||
}
|
||||
|
||||
public void RequestSkillData()
|
||||
{
|
||||
var localPlayer = _playerManager.LocalEntity;
|
||||
|
||||
if (!HasComp<CP14SkillStorageComponent>(localPlayer))
|
||||
return;
|
||||
|
||||
OnSkillUpdate?.Invoke(localPlayer.Value);
|
||||
}
|
||||
|
||||
public void RequestLearnSkill(EntityUid? target, CP14SkillPrototype? skill)
|
||||
{
|
||||
if (skill == null || target == null)
|
||||
return;
|
||||
|
||||
var netEv = new CP14TryLearnSkillMessage(GetNetEntity(target.Value), skill.ID);
|
||||
RaiseNetworkEvent(netEv);
|
||||
|
||||
if (_proto.TryIndex(skill.Tree, out var indexedTree))
|
||||
{
|
||||
_audio.PlayGlobal(indexedTree.LearnSound, target.Value, AudioParams.Default.WithVolume(6f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<Control xmlns="https://spacestation14.io" HorizontalExpand="True">
|
||||
<BoxContainer Name="MainContainer"
|
||||
Orientation="Horizontal"
|
||||
HorizontalExpand="True">
|
||||
<Button Name="MainButton"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
StyleClasses="OpenRight"
|
||||
Margin="0 0 -1 0">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="-5 0 0 0">
|
||||
<Label Name="SkillTreeLabel" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Button>
|
||||
<PanelContainer Name="ColorPanel"
|
||||
VerticalExpand="True"
|
||||
SetWidth="7"
|
||||
Margin="0 1 0 0" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
@@ -0,0 +1,22 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._CP14.Skill.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CP14SkillTreeButtonControl : Control
|
||||
{
|
||||
public event Action? OnPressed;
|
||||
|
||||
public CP14SkillTreeButtonControl(Color color, string label)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ColorPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = color };
|
||||
SkillTreeLabel.Text = label;
|
||||
|
||||
MainButton.OnPressed += args => OnPressed?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<ui:CP14SkillTreeGraphControl
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client._CP14.Skill.Ui"
|
||||
xmlns:ui="clr-namespace:Content.Client._CP14.Skill.Ui"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
MouseFilter="Stop"/>
|
||||
227
Content.Client/_CP14/Skill/Ui/CP14SkillTreeGraphControl.xaml.cs
Normal file
227
Content.Client/_CP14/Skill/Ui/CP14SkillTreeGraphControl.xaml.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared._CP14.Skill;
|
||||
using Content.Shared._CP14.Skill.Components;
|
||||
using Content.Shared._CP14.Skill.Prototypes;
|
||||
using Content.Shared._CP14.Skill.Restrictions;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._CP14.Skill.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CP14SkillTreeGraphControl : BoxContainer
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
private readonly CP14SharedSkillSystem _skillSystem;
|
||||
|
||||
private Entity<CP14SkillStorageComponent>? _player;
|
||||
|
||||
private IEnumerable<CP14SkillPrototype> _allSkills;
|
||||
|
||||
private CP14SkillPrototype? _hoveredNode;
|
||||
private CP14SkillPrototype? _selectedNode;
|
||||
public CP14SkillTreePrototype? Tree;
|
||||
|
||||
private bool dragging = false;
|
||||
private Vector2 _previousMousePosition = Vector2.Zero;
|
||||
private Vector2 _globalOffset = new (60,60);
|
||||
|
||||
private const float GridSize = 25f;
|
||||
|
||||
public event Action<CP14SkillPrototype?>? OnNodeSelected;
|
||||
public event Action<Vector2>? OnOffsetChanged;
|
||||
|
||||
public CP14SkillTreeGraphControl()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
_skillSystem = _entManager.System<CP14SharedSkillSystem>();
|
||||
|
||||
_allSkills = _proto.EnumeratePrototypes<CP14SkillPrototype>();
|
||||
_proto.PrototypesReloaded += _ => _allSkills = _proto.EnumeratePrototypes<CP14SkillPrototype>().OrderBy(skill => _skillSystem.GetSkillName(skill)).ToList();
|
||||
OnOffsetChanged?.Invoke(_globalOffset);
|
||||
}
|
||||
|
||||
public void UpdateState(Entity<CP14SkillStorageComponent>? player)
|
||||
{
|
||||
_player = player;
|
||||
OnOffsetChanged?.Invoke(_globalOffset);
|
||||
}
|
||||
|
||||
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindDown(args);
|
||||
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (args.Function == EngineKeyFunctions.UIClick)
|
||||
{
|
||||
dragging = true;
|
||||
|
||||
if (_hoveredNode == null)
|
||||
return;
|
||||
|
||||
OnNodeSelected?.Invoke(_hoveredNode);
|
||||
UserInterfaceManager.ClickSound();
|
||||
_selectedNode = _hoveredNode;
|
||||
}
|
||||
|
||||
if (args.Function == EngineKeyFunctions.UIRightClick)
|
||||
{
|
||||
_globalOffset = new Vector2(60, 60); // Reset offset on right click
|
||||
OnOffsetChanged?.Invoke(_globalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindUp(args);
|
||||
|
||||
if (args.Handled || args.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
OnNodeSelected?.Invoke(null);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
_hoveredNode = null;
|
||||
if (_player == null || Tree == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cursor = (UserInterfaceManager.MousePositionScaled.Position * UIScale) - GlobalPixelPosition;
|
||||
|
||||
if (dragging)
|
||||
{
|
||||
var delta = cursor - _previousMousePosition;
|
||||
_globalOffset += delta;
|
||||
OnOffsetChanged?.Invoke(_globalOffset);
|
||||
}
|
||||
|
||||
_previousMousePosition = cursor;
|
||||
|
||||
var playerSkills = _player.Value.Comp.LearnedSkills;
|
||||
|
||||
//Draw connection lines
|
||||
foreach (var skill in _allSkills)
|
||||
{
|
||||
if (skill.Tree != Tree)
|
||||
continue;
|
||||
|
||||
var fromPos = skill.SkillUiPosition * GridSize * UIScale + _globalOffset;
|
||||
|
||||
foreach (var req in skill.Restrictions)
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case NeedPrerequisite prerequisite:
|
||||
if (!_proto.TryIndex(prerequisite.Prerequisite, out var prerequisiteSkill))
|
||||
continue;
|
||||
|
||||
if (prerequisiteSkill.Tree != Tree)
|
||||
continue;
|
||||
|
||||
var learned = playerSkills.Contains(skill.ID);
|
||||
|
||||
var toPos = prerequisiteSkill.SkillUiPosition * GridSize * UIScale + _globalOffset;
|
||||
var color = learned ? Color.White : Color.FromSrgb(new Color(0.7f, 0.7f, 0.7f));
|
||||
handle.DrawLine(fromPos, toPos, color);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Draw skill icons over lines
|
||||
foreach (var skill in _allSkills)
|
||||
{
|
||||
if (skill.Tree != Tree)
|
||||
continue;
|
||||
|
||||
//TODO: Not optimized, recalculates every frame. Needs to be calculated on state update and cached.
|
||||
var canBeLearned = _skillSystem.CanLearnSkill(_player.Value, skill, _player.Value.Comp);
|
||||
var pos = skill.SkillUiPosition * GridSize * UIScale + _globalOffset;
|
||||
|
||||
// Base skill icon
|
||||
var baseTexture = skill.Icon.Frame0();
|
||||
var baseSize = new Vector2(baseTexture.Width, baseTexture.Height) * 2;
|
||||
var baseQuad = new UIBox2(pos - baseSize / 2, pos + baseSize / 2);
|
||||
|
||||
var hovered = (cursor - pos).LengthSquared() <= (baseSize.X / 2) * (baseSize.X / 2);
|
||||
|
||||
// Frame
|
||||
var frameTexture = Tree.FrameIcon.Frame0();
|
||||
var frameSize = new Vector2(frameTexture.Width, frameTexture.Height) * 2;
|
||||
var frameQuad = new UIBox2(pos - frameSize / 2, pos + frameSize / 2);
|
||||
handle.DrawTextureRect(frameTexture, frameQuad, canBeLearned ? Color.White : Color.FromSrgb(new Color(0.7f, 0.7f, 0.7f)));
|
||||
|
||||
// Selected Skill
|
||||
if (_selectedNode == skill)
|
||||
{
|
||||
var selectedTexture = Tree.SelectedIcon.Frame0();
|
||||
var selectedSize = new Vector2(selectedTexture.Width, selectedTexture.Height) * 2;
|
||||
var selectedQuad = new UIBox2(pos - selectedSize / 2, pos + selectedSize / 2);
|
||||
handle.DrawTextureRect(selectedTexture, selectedQuad, Color.White);
|
||||
}
|
||||
|
||||
// Hovered Skill
|
||||
if (hovered)
|
||||
{
|
||||
_hoveredNode = skill;
|
||||
var hoveredTexture = Tree.HoveredIcon.Frame0();
|
||||
var hoveredSize = new Vector2(hoveredTexture.Width, hoveredTexture.Height) * 2;
|
||||
var hoveredQuad = new UIBox2(pos - hoveredSize / 2, pos + hoveredSize / 2);
|
||||
handle.DrawTextureRect(hoveredTexture, hoveredQuad, Color.White);
|
||||
}
|
||||
|
||||
var learned = playerSkills.Contains(skill.ID);
|
||||
var allowedToLearn = _skillSystem.AllowedToLearn(_player.Value, skill, _player.Value.Comp);
|
||||
|
||||
// Learned Skill
|
||||
if (learned)
|
||||
{
|
||||
var learnedTexture = Tree.LearnedIcon.Frame0();
|
||||
var learnedSize = new Vector2(learnedTexture.Width, learnedTexture.Height) * 2;
|
||||
var learnedQuad = new UIBox2(pos - learnedSize / 2, pos + learnedSize / 2);
|
||||
handle.DrawTextureRect(learnedTexture, learnedQuad, Color.White);
|
||||
}
|
||||
else if (canBeLearned)
|
||||
{
|
||||
var availableTexture = Tree.AvailableIcon.Frame0();
|
||||
var availableSize = new Vector2(availableTexture.Width, availableTexture.Height) * 2;
|
||||
var availableQuad = new UIBox2(pos - availableSize / 2, pos + availableSize / 2);
|
||||
handle.DrawTextureRect(availableTexture, availableQuad, Color.White);
|
||||
}
|
||||
|
||||
var iconColor = allowedToLearn switch
|
||||
{
|
||||
true when !learned => Color.FromSrgb(new Color(0.7f, 0.7f, 0.7f)),
|
||||
false when !learned => Color.FromSrgb(new Color(0f, 0f, 0f)),
|
||||
_ => Color.White
|
||||
};
|
||||
|
||||
handle.DrawTextureRect(baseTexture, baseQuad, iconColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
using Content.Client._CP14.Knowledge;
|
||||
using Content.Client._CP14.UserInterface.Systems.Knowledge.Windows;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Input;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Character;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class CP14KnowledgeUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>,
|
||||
IOnSystemChanged<ClientCP14KnowledgeSystem>
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[UISystemDependency] private readonly ClientCP14KnowledgeSystem _knowledge = default!;
|
||||
|
||||
private CP14KnowledgeWindow? _window;
|
||||
|
||||
private MenuButton? KnowledgeButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.CP14KnowledgeButton;
|
||||
|
||||
public void OnStateEntered(GameplayState state)
|
||||
{
|
||||
DebugTools.Assert(_window == null);
|
||||
|
||||
_window = UIManager.CreateWindow<CP14KnowledgeWindow>();
|
||||
LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop);
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.CP14OpenKnowledgeMenu,
|
||||
InputCmdHandler.FromDelegate(_ => ToggleWindow()))
|
||||
.Register<CP14KnowledgeUIController>();
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
{
|
||||
if (_window != null)
|
||||
{
|
||||
_window.Dispose();
|
||||
_window = null;
|
||||
}
|
||||
|
||||
CommandBinds.Unregister<CP14KnowledgeUIController>();
|
||||
}
|
||||
|
||||
public void OnSystemLoaded(ClientCP14KnowledgeSystem system)
|
||||
{
|
||||
system.OnKnowledgeUpdate += KnowledgeUpdated;
|
||||
_player.LocalPlayerDetached += CharacterDetached;
|
||||
}
|
||||
|
||||
public void OnSystemUnloaded(ClientCP14KnowledgeSystem system)
|
||||
{
|
||||
system.OnKnowledgeUpdate -= KnowledgeUpdated;
|
||||
_player.LocalPlayerDetached -= CharacterDetached;
|
||||
}
|
||||
|
||||
public void UnloadButton()
|
||||
{
|
||||
if (KnowledgeButton is null)
|
||||
return;
|
||||
|
||||
KnowledgeButton.OnPressed -= KnowledgeButtonPressed;
|
||||
}
|
||||
|
||||
public void LoadButton()
|
||||
{
|
||||
if (KnowledgeButton is null)
|
||||
return;
|
||||
|
||||
KnowledgeButton.OnPressed += KnowledgeButtonPressed;
|
||||
|
||||
if (_window is null)
|
||||
return;
|
||||
|
||||
_window.OnClose += DeactivateButton;
|
||||
_window.OnOpen += ActivateButton;
|
||||
}
|
||||
|
||||
private void DeactivateButton()
|
||||
{
|
||||
KnowledgeButton!.Pressed = false;
|
||||
}
|
||||
|
||||
private void ActivateButton()
|
||||
{
|
||||
KnowledgeButton!.Pressed = true;
|
||||
}
|
||||
|
||||
private void KnowledgeUpdated(ClientCP14KnowledgeSystem.KnowledgeData data)
|
||||
{
|
||||
if (_window is null)
|
||||
return;
|
||||
|
||||
_window.KnowledgeContent.RemoveAllChildren();
|
||||
|
||||
var (entity, allKnowledge) = data;
|
||||
|
||||
foreach (var knowledge in allKnowledge)
|
||||
{
|
||||
if (!_proto.TryIndex(knowledge, out var indexedKnowledge))
|
||||
continue;
|
||||
|
||||
var knowledgeButton = new Button()
|
||||
{
|
||||
Access = AccessLevel.Public,
|
||||
Text = Loc.GetString(indexedKnowledge.Name),
|
||||
ToolTip = Loc.GetString(indexedKnowledge.Desc),
|
||||
TextAlign = Label.AlignMode.Center,
|
||||
};
|
||||
|
||||
_window.KnowledgeContent.AddChild(knowledgeButton);
|
||||
}
|
||||
}
|
||||
|
||||
private void CharacterDetached(EntityUid uid)
|
||||
{
|
||||
CloseWindow();
|
||||
}
|
||||
|
||||
private void KnowledgeButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
ToggleWindow();
|
||||
}
|
||||
|
||||
private void CloseWindow()
|
||||
{
|
||||
_window?.Close();
|
||||
}
|
||||
|
||||
private void ToggleWindow()
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
if (KnowledgeButton != null)
|
||||
{
|
||||
KnowledgeButton.SetClickPressed(!_window.IsOpen);
|
||||
}
|
||||
|
||||
if (_window.IsOpen)
|
||||
{
|
||||
CloseWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
_knowledge.RequestKnowledgeInfo();
|
||||
_window.Open();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<windows:CP14KnowledgeWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:windows="clr-namespace:Content.Client._CP14.UserInterface.Systems.Knowledge.Windows"
|
||||
Title="{Loc 'cp14-knowledge-info-title'}"
|
||||
MinWidth="400"
|
||||
MinHeight="200">
|
||||
<ScrollContainer>
|
||||
<BoxContainer Name="KnowledgeContent" Margin="5" Access="Public" Orientation="Vertical">
|
||||
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</windows:CP14KnowledgeWindow>
|
||||
@@ -0,0 +1,266 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Content.Client._CP14.Skill;
|
||||
using Content.Client._CP14.Skill.Ui;
|
||||
using Content.Client._CP14.UserInterface.Systems.Skill.Window;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared._CP14.Skill.Components;
|
||||
using Content.Shared._CP14.Skill.Prototypes;
|
||||
using Content.Shared.Input;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client._CP14.UserInterface.Systems.Skill;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class CP14SkillUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>,
|
||||
IOnSystemChanged<CP14ClientSkillSystem>
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[UISystemDependency] private readonly CP14ClientSkillSystem _skill = default!;
|
||||
|
||||
private CP14SkillWindow? _window;
|
||||
private CP14SkillPrototype? _selectedSkill;
|
||||
|
||||
private MenuButton? SkillButton => UIManager.GetActiveUIWidgetOrNull<Client.UserInterface.Systems.MenuBar.Widgets.GameTopMenuBar>()?.CP14SkillButton;
|
||||
|
||||
public void OnStateEntered(GameplayState state)
|
||||
{
|
||||
DebugTools.Assert(_window == null);
|
||||
|
||||
_window = UIManager.CreateWindow<CP14SkillWindow>();
|
||||
LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop);
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.CP14OpenSkillMenu,
|
||||
InputCmdHandler.FromDelegate(_ => ToggleWindow()))
|
||||
.Register<CP14SkillUIController>();
|
||||
|
||||
_window.LearnButton.OnPressed += _ => _skill.RequestLearnSkill(_player.LocalEntity, _selectedSkill);
|
||||
_window.GraphControl.OnNodeSelected += SelectNode;
|
||||
_window.GraphControl.OnOffsetChanged += offset =>
|
||||
{
|
||||
_window.ParallaxBackground.Offset = -offset * 0.25f + new Vector2(1000,1000); //hardcoding is bad
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
{
|
||||
if (_window != null)
|
||||
{
|
||||
_window.GraphControl.OnNodeSelected -= SelectNode;
|
||||
|
||||
_window.Dispose();
|
||||
_window = null;
|
||||
}
|
||||
|
||||
CommandBinds.Unregister<CP14SkillUIController>();
|
||||
}
|
||||
|
||||
public void OnSystemLoaded(CP14ClientSkillSystem system)
|
||||
{
|
||||
system.OnSkillUpdate += UpdateState;
|
||||
_player.LocalPlayerDetached += CharacterDetached;
|
||||
}
|
||||
|
||||
public void OnSystemUnloaded(CP14ClientSkillSystem system)
|
||||
{
|
||||
system.OnSkillUpdate -= UpdateState;
|
||||
_player.LocalPlayerDetached -= CharacterDetached;
|
||||
}
|
||||
|
||||
public void UnloadButton()
|
||||
{
|
||||
if (SkillButton is null)
|
||||
return;
|
||||
|
||||
SkillButton.OnPressed -= SkillButtonPressed;
|
||||
}
|
||||
|
||||
public void LoadButton()
|
||||
{
|
||||
if (SkillButton is null)
|
||||
return;
|
||||
|
||||
SkillButton.OnPressed += SkillButtonPressed;
|
||||
|
||||
if (_window is null)
|
||||
return;
|
||||
|
||||
_window.OnClose += DeactivateButton;
|
||||
_window.OnOpen += ActivateButton;
|
||||
}
|
||||
|
||||
private void DeactivateButton()
|
||||
{
|
||||
SkillButton!.Pressed = false;
|
||||
}
|
||||
|
||||
private void ActivateButton()
|
||||
{
|
||||
SkillButton!.Pressed = true;
|
||||
}
|
||||
|
||||
private void SelectNode(CP14SkillPrototype? skill)
|
||||
{
|
||||
if (_window is null)
|
||||
return;
|
||||
|
||||
if (_player.LocalEntity == null)
|
||||
return;
|
||||
|
||||
_selectedSkill = skill;
|
||||
|
||||
if (skill == null)
|
||||
{
|
||||
_window.SkillName.Text = string.Empty;
|
||||
_window.SkillDescription.Text = string.Empty;
|
||||
_window.SkillView.Texture = null;
|
||||
_window.LearnButton.Disabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_window.SkillName.Text = _skill.GetSkillName(skill);
|
||||
_window.SkillDescription.SetMessage(GetSkillDescription(skill));
|
||||
_window.SkillView.Texture = skill.Icon.Frame0();
|
||||
_window.LearnButton.Disabled = !_skill.CanLearnSkill(_player.LocalEntity.Value, skill);
|
||||
_window.SkillCost.Text = skill.LearnCost.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private FormattedMessage GetSkillDescription(CP14SkillPrototype skill)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
|
||||
if (_player.LocalEntity == null)
|
||||
return msg;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
//Description
|
||||
sb.Append(_skill.GetSkillDescription(skill) + "\n \n");
|
||||
|
||||
//Restrictions
|
||||
foreach (var req in skill.Restrictions)
|
||||
{
|
||||
var color = req.Check(_entManager, _player.LocalEntity.Value) ? "green" : "red";
|
||||
|
||||
sb.Append($"- [color={color}]{req.GetDescription(_entManager, _proto)}[/color]\n");
|
||||
}
|
||||
|
||||
msg.TryAddMarkup(sb.ToString(), out _);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
private void UpdateState(EntityUid player)
|
||||
{
|
||||
if (_window is null)
|
||||
return;
|
||||
|
||||
if (!EntityManager.TryGetComponent<CP14SkillStorageComponent>(player, out var storage))
|
||||
return;
|
||||
|
||||
_window.GraphControl.UpdateState((player, storage));
|
||||
|
||||
// Reselect for update state
|
||||
SelectNode(_selectedSkill);
|
||||
|
||||
//If tree not selected, select the first one
|
||||
if (_window.GraphControl.Tree == null && storage.Progress.Count > 0)
|
||||
{
|
||||
var firstTree = storage.Progress.First().Key;
|
||||
|
||||
if (_proto.TryIndex(firstTree, out var indexedTree))
|
||||
{
|
||||
SelectTree(indexedTree, storage); // Set the first tree from the player's progress
|
||||
}
|
||||
}
|
||||
|
||||
// Update the experience points for the selected tree
|
||||
var playerProgress = storage.Progress;
|
||||
if (_window.GraphControl.Tree is not null && playerProgress.TryGetValue(_window.GraphControl.Tree, out var skillpoint))
|
||||
{
|
||||
_window.ExpPointLabel.Text = skillpoint.ToString();
|
||||
}
|
||||
|
||||
_window.LevelLabel.Text = $"{storage.SkillsSumExperience}/{storage.ExperienceMaxCap}";
|
||||
|
||||
_window.TreeTabsContainer.RemoveAllChildren();
|
||||
foreach (var (tree, progress) in storage.Progress)
|
||||
{
|
||||
if (!_proto.TryIndex(tree, out var indexedTree))
|
||||
continue;
|
||||
|
||||
var treeButton2 = new CP14SkillTreeButtonControl(indexedTree.Color, Loc.GetString(indexedTree.Name));
|
||||
treeButton2.ToolTip = Loc.GetString(indexedTree.Desc ?? string.Empty);
|
||||
treeButton2.OnPressed += () =>
|
||||
{
|
||||
SelectTree(indexedTree, storage);
|
||||
};
|
||||
|
||||
_window.TreeTabsContainer.AddChild(treeButton2);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectTree(CP14SkillTreePrototype tree, CP14SkillStorageComponent storage)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
_window.GraphControl.Tree = tree;
|
||||
_window.ParallaxBackground.ParallaxPrototype = tree.Parallax;
|
||||
_window.TreeName.Text = Loc.GetString(tree.Name);
|
||||
|
||||
var playerProgress = storage.Progress;
|
||||
_window.ExpPointLabel.Text = playerProgress.TryGetValue(tree, out var skillpoint) ? skillpoint.ToString() : "0";
|
||||
}
|
||||
|
||||
private void CharacterDetached(EntityUid uid)
|
||||
{
|
||||
CloseWindow();
|
||||
}
|
||||
|
||||
private void SkillButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
ToggleWindow();
|
||||
}
|
||||
|
||||
private void CloseWindow()
|
||||
{
|
||||
_window?.Close();
|
||||
}
|
||||
|
||||
private void ToggleWindow()
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
if (SkillButton != null)
|
||||
{
|
||||
SkillButton.SetClickPressed(!_window.IsOpen);
|
||||
}
|
||||
|
||||
if (_window.IsOpen)
|
||||
{
|
||||
CloseWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
_skill.RequestSkillData();
|
||||
_window.Open();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<window:CP14SkillWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:window="clr-namespace:Content.Client._CP14.UserInterface.Systems.Skill.Window"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:parallax="clr-namespace:Content.Client.Parallax"
|
||||
xmlns:ui1="clr-namespace:Content.Client._CP14.Skill.Ui"
|
||||
Title="{Loc 'cp14-skill-info-title'}"
|
||||
MinSize="700 350"
|
||||
SetSize="980 550">
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
|
||||
|
||||
<!-- Selected Skill -->
|
||||
<BoxContainer Margin="10 10 10 10" MaxWidth="240" SetWidth="240" Orientation="Vertical"
|
||||
HorizontalExpand="False" VerticalExpand="True">
|
||||
<!-- Skill View -->
|
||||
<PanelContainer Name="BackPanel" HorizontalAlignment="Center">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxTexture Modulate="#1B1B1E" PatchMarginBottom="10" PatchMarginLeft="10"
|
||||
PatchMarginRight="10" PatchMarginTop="10" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer HorizontalExpand="True" VerticalExpand="True" >
|
||||
<TextureRect Stretch="Scale" Name="SkillView" SetSize="64 64" HorizontalAlignment="Center" VerticalAlignment="Center" MinSize="64 64"
|
||||
HorizontalExpand="True" VerticalExpand="True" Access="Public"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
<customControls:HSeparator StyleClasses="HighDivider" Margin="0 15 0 10" />
|
||||
<!-- Skill Data -->
|
||||
<BoxContainer Name="NodeViewContainer" Orientation="Vertical" VerticalExpand="True">
|
||||
<ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="False" VerticalExpand="True">
|
||||
<BoxContainer Name="InfoContainer" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer HorizontalExpand="True">
|
||||
<Label Name="SkillName" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center"
|
||||
HorizontalExpand="True" HorizontalAlignment="Center" />
|
||||
</BoxContainer>
|
||||
<!-- Skill Cost -->
|
||||
<BoxContainer HorizontalExpand="True">
|
||||
<RichTextLabel HorizontalExpand="True" Access="Public" Text="{Loc 'cp14-skill-menu-learncost'}" Margin="0 10 0 10" />
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Left" TextureScale="2, 2" TexturePath="/Textures/_CP14/Interface/Skills/skillpoint.png"/>
|
||||
<RichTextLabel Name="SkillCost" Access="Public" Text="0"/>
|
||||
</BoxContainer>
|
||||
<!-- Skill Description -->
|
||||
<BoxContainer HorizontalExpand="True">
|
||||
<RichTextLabel Name="SkillDescription" HorizontalExpand="True" Access="Public"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<Control MinHeight="5"/>
|
||||
<!-- Skill Buttons -->
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Button Name="LearnButton" Text="{Loc 'cp14-skill-menu-learn-button'}" StyleClasses="OpenRight" HorizontalExpand="True" MinHeight="35" Access="Public"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<customControls:VSeparator StyleClasses="LowDivider" />
|
||||
|
||||
<!-- Skills Tree -->
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer HorizontalExpand="True">
|
||||
<Label Name="TreeName" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center"
|
||||
HorizontalExpand="True" HorizontalAlignment="Center" />
|
||||
</BoxContainer>
|
||||
<PanelContainer Margin="10 10 10 10" HorizontalExpand="True" VerticalExpand="True" RectClipContent="True">
|
||||
<parallax:ParallaxControl Name="ParallaxBackground" ScaleX="4" ScaleY="4" Access="Public" SpeedX="10" SpeedY="5"/>
|
||||
<BoxContainer Margin="10 10 10 10" Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
|
||||
<ui1:CP14SkillTreeGraphControl Name="GraphControl" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Access="Public"/>
|
||||
</BoxContainer>
|
||||
<!-- Tree Tabs -->
|
||||
<BoxContainer Margin="10 10 10 10" VerticalAlignment="Top" HorizontalAlignment="Right" Orientation="Vertical" VerticalExpand="True">
|
||||
<BoxContainer Name="TreeTabsContainer" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Access="Public"/>
|
||||
</BoxContainer>
|
||||
<!-- Experience Data -->
|
||||
<BoxContainer Margin="10 10 10 10" VerticalAlignment="Bottom" HorizontalAlignment="Center" Orientation="Horizontal" VerticalExpand="True">
|
||||
<RichTextLabel VerticalAlignment="Center" HorizontalAlignment="Left" Text="{Loc 'cp14-skill-menu-skillpoints'}" StyleClasses="LabelKeyText"/>
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2, 2" TexturePath="/Textures/_CP14/Interface/Skills/skillpoint.png"/>
|
||||
<RichTextLabel Margin="0 0 20 0" Name="ExpPointLabel" VerticalAlignment="Center" HorizontalAlignment="Left" Text="0" StyleClasses="LabelKeyText" Access="Public"/>
|
||||
|
||||
<RichTextLabel VerticalAlignment="Center" HorizontalAlignment="Left" Text="{Loc 'cp14-skill-menu-level'}" StyleClasses="LabelKeyText"/>
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2, 2" TexturePath="/Textures/_CP14/Interface/Skills/skillpoint.png"/>
|
||||
<RichTextLabel Margin="0 0 0 0" Name="LevelLabel" VerticalAlignment="Center" HorizontalAlignment="Left" Text="0" StyleClasses="LabelKeyText" Access="Public"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</window:CP14SkillWindow>
|
||||
@@ -2,12 +2,12 @@ using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._CP14.UserInterface.Systems.Knowledge.Windows;
|
||||
namespace Content.Client._CP14.UserInterface.Systems.Skill.Window;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CP14KnowledgeWindow : DefaultWindow
|
||||
public sealed partial class CP14SkillWindow : DefaultWindow
|
||||
{
|
||||
public CP14KnowledgeWindow()
|
||||
public CP14SkillWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.Prototypes;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.DeviceLinking;
|
||||
|
||||
public sealed class DeviceLinkingTest
|
||||
{
|
||||
private const string PortTesterProtoId = "DeviceLinkingSinkPortTester";
|
||||
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = $@"
|
||||
- type: entity
|
||||
id: {PortTesterProtoId}
|
||||
components:
|
||||
- type: DeviceLinkSource
|
||||
ports:
|
||||
- Output
|
||||
";
|
||||
|
||||
/// <summary>
|
||||
/// Spawns every entity that has a <see cref="DeviceLinkSinkComponent"/>
|
||||
/// and sends a signal to every port to make sure nothing causes an error.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task AllDeviceLinkSinksWorkTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
var compFact = server.ResolveDependency<IComponentFactory>();
|
||||
var mapMan = server.ResolveDependency<IMapManager>();
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var deviceLinkSys = server.System<DeviceLinkSystem>();
|
||||
|
||||
var prototypes = server.ProtoMan.EnumeratePrototypes<EntityPrototype>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var proto in prototypes)
|
||||
{
|
||||
if (proto.Abstract || pair.IsTestPrototype(proto))
|
||||
continue;
|
||||
|
||||
if (!proto.TryGetComponent<DeviceLinkSinkComponent>(out var protoSinkComp, compFact))
|
||||
continue;
|
||||
|
||||
foreach (var port in protoSinkComp.Ports)
|
||||
{
|
||||
// Create a map for each entity/port combo so they can't interfere
|
||||
mapSys.CreateMap(out var mapId);
|
||||
var grid = mapMan.CreateGridEntity(mapId);
|
||||
mapSys.SetTile(grid.Owner, grid.Comp, Vector2i.Zero, new Tile(1));
|
||||
var coord = new EntityCoordinates(grid.Owner, 0, 0);
|
||||
|
||||
// Spawn the sink entity
|
||||
var sinkEnt = server.EntMan.SpawnEntity(proto.ID, coord);
|
||||
// Get the actual sink component, since the one we got from the prototype doesn't have its owner set up
|
||||
Assert.That(server.EntMan.TryGetComponent<DeviceLinkSinkComponent>(sinkEnt, out var sinkComp),
|
||||
$"{proto.ID} does not have a DeviceLinkSinkComponent!");
|
||||
|
||||
// Spawn the tester
|
||||
var sourceEnt = server.EntMan.SpawnEntity(PortTesterProtoId, coord);
|
||||
Assert.That(server.EntMan.TryGetComponent<DeviceLinkSourceComponent>(sourceEnt, out var sourceComp),
|
||||
$"Tester prototype does not have a DeviceLinkSourceComponent!");
|
||||
|
||||
// Create a link from the tester's output to the target port on the sink
|
||||
deviceLinkSys.SaveLinks(null,
|
||||
sourceEnt,
|
||||
sinkEnt,
|
||||
[("Output", port.Id)],
|
||||
sourceComp,
|
||||
sinkComp);
|
||||
|
||||
// Send a signal to the port
|
||||
Assert.DoesNotThrow(() => { deviceLinkSys.InvokePort(sourceEnt, "Output", null, sourceComp); },
|
||||
$"Exception thrown while triggering port {port.Id} of sink device {proto.ID}");
|
||||
|
||||
mapSys.DeleteMap(mapId);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
91
Content.IntegrationTests/Tests/Embedding/EmbedTest.cs
Normal file
91
Content.IntegrationTests/Tests/Embedding/EmbedTest.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Shared.Projectiles;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Embedding;
|
||||
|
||||
public sealed class EmbedTest : InteractionTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Embeddable entity that will be thrown at the target.
|
||||
/// </summary>
|
||||
private const string EmbeddableProtoId = "SurvivalKnife";
|
||||
|
||||
/// <summary>
|
||||
/// Target entity that the thrown item will embed into.
|
||||
/// </summary>
|
||||
private const string TargetProtoId = "AirlockGlass";
|
||||
|
||||
/// <summary>
|
||||
/// Embeds an entity with a <see cref="EmbeddableProjectileComponent"/> into a target,
|
||||
/// then disconnects the client. Intended to reveal any clientside issues that might
|
||||
/// occur due to reparenting during cleanup.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestDisconnectWhileEmbedded()
|
||||
{
|
||||
// Spawn the target we're going to throw at
|
||||
await SpawnTarget(TargetProtoId);
|
||||
|
||||
// Give the player the embeddable to throw
|
||||
var projectile = await PlaceInHands(EmbeddableProtoId);
|
||||
Assert.That(TryComp<EmbeddableProjectileComponent>(projectile, out var embedComp),
|
||||
$"{EmbeddableProtoId} does not have EmbeddableProjectileComponent");
|
||||
// Make sure the projectile isn't already embedded into anything
|
||||
Assert.That(embedComp.EmbeddedIntoUid, Is.Null,
|
||||
$"Projectile already embedded into {SEntMan.ToPrettyString(embedComp.EmbeddedIntoUid)}");
|
||||
|
||||
// Have the player throw the embeddable at the target
|
||||
await ThrowItem();
|
||||
|
||||
// Wait a moment for the item to hit and embed
|
||||
await RunSeconds(0.5f);
|
||||
|
||||
// Make sure the projectile is embedded into the target
|
||||
Assert.That(embedComp.EmbeddedIntoUid, Is.EqualTo(ToServer(Target)),
|
||||
"Projectile not embedded into target");
|
||||
|
||||
// Disconnect the client
|
||||
var cNetMgr = Client.ResolveDependency<IClientNetManager>();
|
||||
await Client.WaitPost(Client.EntMan.FlushEntities);
|
||||
await Pair.RunTicksSync(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Embeds an entity with a <see cref="EmbeddableProjectileComponent"/> into a target,
|
||||
/// then deletes the target and makes sure the embeddable is not deleted.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestEmbedDetach()
|
||||
{
|
||||
// Spawn the target we're going to throw at
|
||||
await SpawnTarget(TargetProtoId);
|
||||
|
||||
// Give the player the embeddable to throw
|
||||
var projectile = await PlaceInHands(EmbeddableProtoId);
|
||||
Assert.That(TryComp<EmbeddableProjectileComponent>(projectile, out var embedComp),
|
||||
$"{EmbeddableProtoId} does not have EmbeddableProjectileComponent");
|
||||
// Make sure the projectile isn't already embedded into anything
|
||||
Assert.That(embedComp.EmbeddedIntoUid, Is.Null,
|
||||
$"Projectile already embedded into {SEntMan.ToPrettyString(embedComp.EmbeddedIntoUid)}");
|
||||
|
||||
// Have the player throw the embeddable at the target
|
||||
await ThrowItem();
|
||||
|
||||
// Wait a moment for the item to hit and embed
|
||||
await RunSeconds(0.5f);
|
||||
|
||||
// Make sure the projectile is embedded into the target
|
||||
Assert.That(embedComp.EmbeddedIntoUid, Is.EqualTo(ToServer(Target)),
|
||||
"Projectile not embedded into target");
|
||||
|
||||
// Delete the target
|
||||
await Delete(Target.Value);
|
||||
|
||||
await RunTicks(1);
|
||||
|
||||
// Make sure the embeddable wasn't deleted with the target
|
||||
AssertExists(projectile);
|
||||
await AssertEntityLookup(EmbeddableProtoId);
|
||||
}
|
||||
}
|
||||
@@ -155,7 +155,7 @@ public sealed class NukeOpsTest
|
||||
// The game rule exists, and all the stations/shuttles/maps are properly initialized
|
||||
var rule = entMan.AllComponents<NukeopsRuleComponent>().Single();
|
||||
var ruleComp = rule.Component;
|
||||
var gridsRule = entMan.AllComponents<RuleGridsComponent>().Single().Component;
|
||||
var gridsRule = entMan.GetComponent<RuleGridsComponent>(rule.Uid);
|
||||
foreach (var grid in gridsRule.MapGrids)
|
||||
{
|
||||
Assert.That(entMan.EntityExists(grid));
|
||||
|
||||
@@ -19,16 +19,19 @@ public sealed class LocalizedDatasetPrototypeTest
|
||||
|
||||
var protos = protoMan.EnumeratePrototypes<LocalizedDatasetPrototype>().OrderBy(p => p.ID);
|
||||
|
||||
// Check each prototype
|
||||
foreach (var proto in protos)
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// Check each value in the prototype
|
||||
foreach (var locId in proto.Values)
|
||||
// Check each prototype
|
||||
foreach (var proto in protos)
|
||||
{
|
||||
// Make sure the localization manager has a string for the LocId
|
||||
Assert.That(localizationMan.HasString(locId), $"LocalizedDataset {proto.ID} with prefix \"{proto.Values.Prefix}\" specifies {proto.Values.Count} entries, but no localized string was found matching {locId}!");
|
||||
// Check each value in the prototype
|
||||
foreach (var locId in proto.Values)
|
||||
{
|
||||
// Make sure the localization manager has a string for the LocId
|
||||
Assert.That(localizationMan.HasString(locId), $"LocalizedDataset {proto.ID} with prefix \"{proto.Values.Prefix}\" specifies {proto.Values.Count} entries, but no localized string was found matching {locId}!");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using Robust.Shared.Map.Events;
|
||||
|
||||
namespace Content.IntegrationTests.Tests
|
||||
{
|
||||
@@ -213,9 +214,12 @@ namespace Content.IntegrationTests.Tests
|
||||
}
|
||||
|
||||
var deps = server.ResolveDependency<IEntitySystemManager>().DependencyCollection;
|
||||
var ev = new BeforeEntityReadEvent();
|
||||
server.EntMan.EventBus.RaiseEvent(EventSource.Local, ev);
|
||||
|
||||
foreach (var map in v7Maps)
|
||||
{
|
||||
Assert.That(IsPreInit(map, loader, deps));
|
||||
Assert.That(IsPreInit(map, loader, deps, ev.RenamedPrototypes, ev.DeletedPrototypes));
|
||||
}
|
||||
|
||||
// Check that the test actually does manage to catch post-init maps and isn't just blindly passing everything.
|
||||
@@ -228,12 +232,12 @@ namespace Content.IntegrationTests.Tests
|
||||
// First check that a pre-init version passes
|
||||
var path = new ResPath($"{nameof(NoSavedPostMapInitTest)}.yml");
|
||||
Assert.That(loader.TrySaveMap(id, path));
|
||||
Assert.That(IsPreInit(path, loader, deps));
|
||||
Assert.That(IsPreInit(path, loader, deps, ev.RenamedPrototypes, ev.DeletedPrototypes));
|
||||
|
||||
// and the post-init version fails.
|
||||
await server.WaitPost(() => mapSys.InitializeMap(id));
|
||||
Assert.That(loader.TrySaveMap(id, path));
|
||||
Assert.That(IsPreInit(path, loader, deps), Is.False);
|
||||
Assert.That(IsPreInit(path, loader, deps, ev.RenamedPrototypes, ev.DeletedPrototypes), Is.False);
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
@@ -266,7 +270,11 @@ namespace Content.IntegrationTests.Tests
|
||||
});
|
||||
}
|
||||
|
||||
private bool IsPreInit(ResPath map, MapLoaderSystem loader, IDependencyCollection deps)
|
||||
private bool IsPreInit(ResPath map,
|
||||
MapLoaderSystem loader,
|
||||
IDependencyCollection deps,
|
||||
Dictionary<string, string> renamedPrototypes,
|
||||
HashSet<string> deletedPrototypes)
|
||||
{
|
||||
if (!loader.TryReadFile(map, out var data))
|
||||
{
|
||||
@@ -274,7 +282,12 @@ namespace Content.IntegrationTests.Tests
|
||||
return false;
|
||||
}
|
||||
|
||||
var reader = new EntityDeserializer(deps, data, DeserializationOptions.Default);
|
||||
var reader = new EntityDeserializer(deps,
|
||||
data,
|
||||
DeserializationOptions.Default,
|
||||
renamedPrototypes,
|
||||
deletedPrototypes);
|
||||
|
||||
if (!reader.TryProcessData())
|
||||
{
|
||||
Assert.Fail($"Failed to process {map}");
|
||||
|
||||
@@ -35,7 +35,6 @@ public sealed class PrototypeSaveTest
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var entityMan = server.ResolveDependency<IEntityManager>();
|
||||
var prototypeMan = server.ResolveDependency<IPrototypeManager>();
|
||||
var seriMan = server.ResolveDependency<ISerializationManager>();
|
||||
|
||||
@@ -102,6 +102,12 @@ public sealed class StoreTests
|
||||
+ $"flag as 'true'. This marks the fact that cost modifier of discount is not applied properly!"
|
||||
);
|
||||
|
||||
// The storeComponent returns discounted items with conditions randomly, so we remove these to sanitize the data.
|
||||
foreach (var discountedItem in discountedListingItems)
|
||||
{
|
||||
discountedItem.Conditions = null;
|
||||
}
|
||||
|
||||
// Refund action requests re-generation of listing items so we will be re-acquiring items from component a lot of times.
|
||||
var itemIds = discountedListingItems.Select(x => x.ID);
|
||||
foreach (var itemId in itemIds)
|
||||
@@ -140,6 +146,9 @@ public sealed class StoreTests
|
||||
// get refreshed item after refund re-generated items
|
||||
discountedListingItem = storeComponent.FullListingsCatalog.First(x => x.ID == itemId);
|
||||
|
||||
// The storeComponent can give a discounted item a condition at random, so we remove it to sanitize the data.
|
||||
discountedListingItem.Conditions = null;
|
||||
|
||||
var afterRefundBalance = storeComponent.Balance[UplinkSystem.TelecrystalCurrencyPrototype];
|
||||
Assert.That(afterRefundBalance.Value, Is.EqualTo(originalBalance.Value), "Expected refund to return all discounted cost value.");
|
||||
Assert.That(
|
||||
|
||||
205
Content.IntegrationTests/Tests/Vending/VendingInteractionTest.cs
Normal file
205
Content.IntegrationTests/Tests/Vending/VendingInteractionTest.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
using System.Linq;
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Server.VendingMachines;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.VendingMachines;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Vending;
|
||||
|
||||
public sealed class VendingInteractionTest : InteractionTest
|
||||
{
|
||||
private const string VendingMachineProtoId = "InteractionTestVendingMachine";
|
||||
|
||||
private const string VendedItemProtoId = "InteractionTestItem";
|
||||
|
||||
private const string RestockBoxProtoId = "InteractionTestRestockBox";
|
||||
|
||||
private const string RestockBoxOtherProtoId = "InteractionTestRestockBoxOther";
|
||||
|
||||
[TestPrototypes]
|
||||
private const string TestPrototypes = $@"
|
||||
- type: entity
|
||||
parent: BaseItem
|
||||
id: {VendedItemProtoId}
|
||||
name: {VendedItemProtoId}
|
||||
|
||||
- type: vendingMachineInventory
|
||||
id: InteractionTestVendingInventory
|
||||
startingInventory:
|
||||
{VendedItemProtoId}: 5
|
||||
|
||||
- type: vendingMachineInventory
|
||||
id: InteractionTestVendingInventoryOther
|
||||
startingInventory:
|
||||
{VendedItemProtoId}: 5
|
||||
|
||||
- type: entity
|
||||
parent: BaseVendingMachineRestock
|
||||
id: {RestockBoxProtoId}
|
||||
components:
|
||||
- type: VendingMachineRestock
|
||||
canRestock:
|
||||
- InteractionTestVendingInventory
|
||||
|
||||
- type: entity
|
||||
parent: BaseVendingMachineRestock
|
||||
id: {RestockBoxOtherProtoId}
|
||||
components:
|
||||
- type: VendingMachineRestock
|
||||
canRestock:
|
||||
- InteractionTestVendingInventoryOther
|
||||
|
||||
- type: entity
|
||||
id: {VendingMachineProtoId}
|
||||
parent: VendingMachine
|
||||
components:
|
||||
- type: VendingMachine
|
||||
pack: InteractionTestVendingInventory
|
||||
ejectDelay: 0 # no delay to speed up tests
|
||||
- type: Sprite
|
||||
sprite: error.rsi
|
||||
";
|
||||
|
||||
[Test]
|
||||
public async Task InteractUITest()
|
||||
{
|
||||
await SpawnTarget(VendingMachineProtoId);
|
||||
|
||||
// Should start with no BUI open
|
||||
Assert.That(IsUiOpen(VendingMachineUiKey.Key), Is.False, "BUI was open unexpectedly.");
|
||||
|
||||
// Unpowered vending machine does not open BUI
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(VendingMachineUiKey.Key), Is.False, "BUI opened without power.");
|
||||
|
||||
// Power the vending machine
|
||||
var apc = await SpawnEntity("APCBasic", SEntMan.GetCoordinates(TargetCoords));
|
||||
await RunTicks(1);
|
||||
|
||||
// Interacting with powered vending machine opens BUI
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(VendingMachineUiKey.Key), "BUI failed to open.");
|
||||
|
||||
// Interacting with it again closes the BUI
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(VendingMachineUiKey.Key), Is.False, "BUI failed to close on interaction.");
|
||||
|
||||
// Reopen BUI for the next check
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(VendingMachineUiKey.Key), "BUI failed to reopen.");
|
||||
|
||||
// Remove power
|
||||
await Delete(apc);
|
||||
await RunTicks(1);
|
||||
|
||||
// The BUI should close when power is lost
|
||||
Assert.That(IsUiOpen(VendingMachineUiKey.Key), Is.False, "BUI failed to close on power loss.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DispenseItemTest()
|
||||
{
|
||||
await SpawnTarget(VendingMachineProtoId);
|
||||
var vendorEnt = SEntMan.GetEntity(Target.Value);
|
||||
|
||||
var vendingSystem = SEntMan.System<VendingMachineSystem>();
|
||||
var items = vendingSystem.GetAllInventory(vendorEnt);
|
||||
|
||||
// Verify initial item count
|
||||
Assert.That(items, Is.Not.Empty, $"{VendingMachineProtoId} spawned with no items.");
|
||||
Assert.That(items.First().Amount, Is.EqualTo(5), $"{VendingMachineProtoId} spawned with unexpected item count.");
|
||||
|
||||
// Power the vending machine
|
||||
await SpawnEntity("APCBasic", SEntMan.GetCoordinates(TargetCoords));
|
||||
await RunTicks(1);
|
||||
|
||||
// Open the BUI
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(VendingMachineUiKey.Key), "BUI failed to open.");
|
||||
|
||||
// Request an item be dispensed
|
||||
var ev = new VendingMachineEjectMessage(InventoryType.Regular, VendedItemProtoId);
|
||||
await SendBui(VendingMachineUiKey.Key, ev);
|
||||
|
||||
// Make sure the stock decreased
|
||||
Assert.That(items.First().Amount, Is.EqualTo(4), "Stocked item count did not decrease.");
|
||||
// Make sure the dispensed item was spawned in to the world
|
||||
await AssertEntityLookup(
|
||||
("APCBasic", 1),
|
||||
(VendedItemProtoId, 1)
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RestockTest()
|
||||
{
|
||||
var vendingSystem = SEntMan.System<VendingMachineSystem>();
|
||||
|
||||
await SpawnTarget(VendingMachineProtoId);
|
||||
var vendorEnt = SEntMan.GetEntity(Target.Value);
|
||||
|
||||
var items = vendingSystem.GetAllInventory(vendorEnt);
|
||||
|
||||
Assert.That(items, Is.Not.Empty, $"{VendingMachineProtoId} spawned with no items.");
|
||||
Assert.That(items.First().Amount, Is.EqualTo(5), $"{VendingMachineProtoId} spawned with unexpected item count.");
|
||||
|
||||
// Try to restock with the maintenance panel closed (nothing happens)
|
||||
await InteractUsing(RestockBoxProtoId);
|
||||
|
||||
Assert.That(items.First().Amount, Is.EqualTo(5), "Restocked without opening maintenance panel.");
|
||||
|
||||
// Open the maintenance panel
|
||||
await InteractUsing(Screw);
|
||||
|
||||
// Try to restock using the wrong restock box (nothing happens)
|
||||
await InteractUsing(RestockBoxOtherProtoId);
|
||||
|
||||
Assert.That(items.First().Amount, Is.EqualTo(5), "Restocked with wrong restock box.");
|
||||
|
||||
// Restock the machine
|
||||
await InteractUsing(RestockBoxProtoId);
|
||||
|
||||
Assert.That(items.First().Amount, Is.EqualTo(10), "Restocking resulted in unexpected item count.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RepairTest()
|
||||
{
|
||||
await SpawnTarget(VendingMachineProtoId);
|
||||
|
||||
// Power the vending machine
|
||||
await SpawnEntity("APCBasic", SEntMan.GetCoordinates(TargetCoords));
|
||||
await RunTicks(1);
|
||||
|
||||
// Break it
|
||||
await BreakVendor();
|
||||
Assert.That(IsUiOpen(VendingMachineUiKey.Key), Is.False, "BUI did not close when vending machine broke.");
|
||||
|
||||
// Make sure we can't open the BUI while it's broken
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(VendingMachineUiKey.Key), Is.False, "Opened BUI of broken vending machine.");
|
||||
|
||||
// Repair the vending machine
|
||||
await InteractUsing(Weld);
|
||||
|
||||
// Make sure the BUI can open now that the machine has been repaired
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(VendingMachineUiKey.Key), "Failed to open BUI after repair.");
|
||||
}
|
||||
|
||||
private async Task BreakVendor()
|
||||
{
|
||||
var damageableSys = SEntMan.System<DamageableSystem>();
|
||||
Assert.That(TryComp<DamageableComponent>(out var damageableComp), $"{VendingMachineProtoId} does not have DamageableComponent.");
|
||||
Assert.That(damageableComp.Damage.GetTotal(), Is.EqualTo(FixedPoint2.Zero), $"{VendingMachineProtoId} started with unexpected damage.");
|
||||
|
||||
// Damage the vending machine to the point that it breaks
|
||||
var damageType = ProtoMan.Index<DamageTypePrototype>("Blunt");
|
||||
var damage = new DamageSpecifier(damageType, FixedPoint2.New(100));
|
||||
await Server.WaitPost(() => damageableSys.TryChangeDamage(SEntMan.GetEntity(Target), damage, ignoreResistances: true));
|
||||
await RunTicks(5);
|
||||
Assert.That(damageableComp.Damage.GetTotal(), Is.GreaterThan(FixedPoint2.Zero), $"{VendingMachineProtoId} did not take damage.");
|
||||
}
|
||||
}
|
||||
@@ -241,7 +241,7 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
||||
var addedTags = newAccessList.Except(oldTags).Select(tag => "+" + tag).ToList();
|
||||
var removedTags = oldTags.Except(newAccessList).Select(tag => "-" + tag).ToList();
|
||||
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
_adminLogger.Add(LogType.Action, LogImpact.High,
|
||||
$"{ToPrettyString(player):player} has modified {ToPrettyString(accessReaderEnt.Value):entity} with the following allowed access level holders: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
|
||||
|
||||
accessReaderEnt.Value.Comp.AccessLists = ConvertAccessListToHashSet(newAccessList);
|
||||
|
||||
@@ -168,7 +168,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
|
||||
/*TODO: ECS SharedIdCardConsoleComponent and then log on card ejection, together with the save.
|
||||
This current implementation is pretty shit as it logs 27 entries (27 lines) if someone decides to give themselves AA*/
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
_adminLogger.Add(LogType.Action, LogImpact.High,
|
||||
$"{ToPrettyString(player):player} has modified {ToPrettyString(targetId):entity} with the following accesses: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
||||
access.Tags.Add(random.ID);
|
||||
Dirty(uid, access);
|
||||
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
_adminLogger.Add(LogType.Action, LogImpact.High,
|
||||
$"{ToPrettyString(args.Microwave)} added {random.ID} access to {ToPrettyString(uid):entity}");
|
||||
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ public sealed class ChangeCvarCommand : IConsoleCommand
|
||||
var oldValue = _configurationManager.GetCVar<object>(cvar);
|
||||
_configurationManager.SetCVar(cvar, parsed);
|
||||
_adminLogManager.Add(LogType.AdminCommands,
|
||||
LogImpact.High,
|
||||
LogImpact.Extreme,
|
||||
$"{shell.Player!.Name} ({shell.Player!.UserId}) changed CVAR {cvar} from {oldValue.ToString()} to {parsed.ToString()}"
|
||||
);
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
using Content.Server.Administration.Notes;
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Notes;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.ViewNotes)]
|
||||
public sealed class OpenAdminNotesCommand : IConsoleCommand
|
||||
public sealed class OpenAdminNotesCommand : LocalizedCommands
|
||||
{
|
||||
public const string CommandName = "adminnotes";
|
||||
|
||||
public string Command => CommandName;
|
||||
public string Description => "Opens the admin notes panel.";
|
||||
public string Help => $"Usage: {Command} <notedPlayerUserId OR notedPlayerUsername>";
|
||||
public override string Command => CommandName;
|
||||
|
||||
public async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
@@ -33,17 +33,27 @@ public sealed class OpenAdminNotesCommand : IConsoleCommand
|
||||
|
||||
if (dbGuid == null)
|
||||
{
|
||||
shell.WriteError($"Unable to find {args[0]} netuserid");
|
||||
shell.WriteError(Loc.GetString("cmd-adminnotes-wrong-target", ("user", args[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
notedPlayer = dbGuid.UserId;
|
||||
break;
|
||||
default:
|
||||
shell.WriteError($"Invalid arguments.\n{Help}");
|
||||
shell.WriteError(Loc.GetString("cmd-adminnotes-args-error"));
|
||||
return;
|
||||
}
|
||||
|
||||
await IoCManager.Resolve<IAdminNotesManager>().OpenEui(player, notedPlayer);
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
return CompletionResult.Empty;
|
||||
|
||||
var playerMgr = IoCManager.Resolve<IPlayerManager>();
|
||||
var options = playerMgr.Sessions.Select(c => c.Name).OrderBy(c => c).ToArray();
|
||||
return CompletionResult.FromHintOptions(options, Loc.GetString("cmd-adminnotes-hint"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Content.Server.Administration.Commands
|
||||
}
|
||||
else
|
||||
{
|
||||
if (player.Status != SessionStatus.InGame || player.AttachedEntity is not {Valid: true} playerEntity)
|
||||
if (player.Status != SessionStatus.InGame || player.AttachedEntity is not { Valid: true } playerEntity)
|
||||
{
|
||||
shell.WriteLine("You are not in-game!");
|
||||
return;
|
||||
@@ -57,14 +57,16 @@ namespace Content.Server.Administration.Commands
|
||||
var currentMap = _entManager.GetComponent<TransformComponent>(playerEntity).MapID;
|
||||
var currentGrid = _entManager.GetComponent<TransformComponent>(playerEntity).GridUid;
|
||||
|
||||
var xformSystem = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
var found = GetWarpPointByName(location)
|
||||
.OrderBy(p => p.Item1, Comparer<EntityCoordinates>.Create((a, b) =>
|
||||
{
|
||||
// Sort so that warp points on the same grid/map are first.
|
||||
// So if you have two maps loaded with the same warp points,
|
||||
// it will prefer the warp points on the map you're currently on.
|
||||
var aGrid = a.GetGridUid(_entManager);
|
||||
var bGrid = b.GetGridUid(_entManager);
|
||||
var aGrid = xformSystem.GetGrid(a);
|
||||
var bGrid = xformSystem.GetGrid(b);
|
||||
|
||||
if (aGrid == bGrid)
|
||||
{
|
||||
@@ -81,8 +83,8 @@ namespace Content.Server.Administration.Commands
|
||||
return 1;
|
||||
}
|
||||
|
||||
var mapA = a.GetMapId(_entManager);
|
||||
var mapB = a.GetMapId(_entManager);
|
||||
var mapA = xformSystem.GetMapId(a);
|
||||
var mapB = xformSystem.GetMapId(b);
|
||||
|
||||
if (mapA == mapB)
|
||||
{
|
||||
@@ -117,10 +119,8 @@ namespace Content.Server.Administration.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var xform = _entManager.GetComponent<TransformComponent>(playerEntity);
|
||||
var xformSystem = _entManager.System<SharedTransformSystem>();
|
||||
xform.Coordinates = coords;
|
||||
xformSystem.AttachToGridOrMap(playerEntity, xform);
|
||||
xformSystem.SetCoordinates(playerEntity, coords);
|
||||
xformSystem.AttachToGridOrMap(playerEntity);
|
||||
if (_entManager.TryGetComponent(playerEntity, out PhysicsComponent? physics))
|
||||
{
|
||||
_entManager.System<SharedPhysicsSystem>().SetLinearVelocity(playerEntity, Vector2.Zero, body: physics);
|
||||
|
||||
@@ -2,14 +2,19 @@
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.Database;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Prometheus;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -25,6 +30,9 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
|
||||
[Dependency] private readonly IDynamicTypeFactory _typeFactory = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflection = default!;
|
||||
[Dependency] private readonly IDependencyCollection _dependencies = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
[Dependency] private readonly ISharedPlaytimeManager _playtime = default!;
|
||||
[Dependency] private readonly ISharedChatManager _chat = default!;
|
||||
|
||||
public const string SawmillId = "admin.logs";
|
||||
|
||||
@@ -66,6 +74,7 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
|
||||
private int _queueMax;
|
||||
private int _preRoundQueueMax;
|
||||
private int _dropThreshold;
|
||||
private int _highImpactLogPlaytime;
|
||||
|
||||
// Per update
|
||||
private TimeSpan _nextUpdateTime;
|
||||
@@ -100,6 +109,8 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
|
||||
value => _preRoundQueueMax = value, true);
|
||||
_configuration.OnValueChanged(CCVars.AdminLogsDropThreshold,
|
||||
value => _dropThreshold = value, true);
|
||||
_configuration.OnValueChanged(CCVars.AdminLogsHighLogPlaytime,
|
||||
value => _highImpactLogPlaytime = value, true);
|
||||
|
||||
if (_metricsEnabled)
|
||||
{
|
||||
@@ -300,6 +311,10 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
|
||||
Players = new List<AdminLogPlayer>(players.Count)
|
||||
};
|
||||
|
||||
var adminLog = false;
|
||||
var adminSys = _entityManager.SystemOrNull<AdminSystem>();
|
||||
var logMessage = message;
|
||||
|
||||
foreach (var id in players)
|
||||
{
|
||||
var player = new AdminLogPlayer
|
||||
@@ -309,8 +324,39 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
|
||||
};
|
||||
|
||||
log.Players.Add(player);
|
||||
|
||||
if (adminSys != null)
|
||||
{
|
||||
var cachedInfo = adminSys.GetCachedPlayerInfo(new NetUserId(id));
|
||||
if (cachedInfo != null && cachedInfo.Antag)
|
||||
{
|
||||
logMessage += " [ANTAG: " + cachedInfo.CharacterName + "]";
|
||||
}
|
||||
}
|
||||
|
||||
if (adminLog)
|
||||
continue;
|
||||
|
||||
if (impact == LogImpact.Extreme) // Always chat-notify Extreme logs
|
||||
adminLog = true;
|
||||
|
||||
if (impact == LogImpact.High) // Only chat-notify High logs if the player is below a threshold playtime
|
||||
{
|
||||
if (_highImpactLogPlaytime >= 0 && _player.TryGetSessionById(new NetUserId(id), out var session))
|
||||
{
|
||||
var playtimes = _playtime.GetPlayTimes(session);
|
||||
if (playtimes.TryGetValue(PlayTimeTrackingShared.TrackerOverall, out var overallTime) &&
|
||||
overallTime <= TimeSpan.FromHours(_highImpactLogPlaytime))
|
||||
{
|
||||
adminLog = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (adminLog)
|
||||
_chat.SendAdminAlert(logMessage);
|
||||
|
||||
if (preRound)
|
||||
{
|
||||
_preRoundLogQueue.Enqueue(log);
|
||||
|
||||
@@ -221,6 +221,7 @@ public sealed class AdminSystem : EntitySystem
|
||||
var name = data.UserName;
|
||||
var entityName = string.Empty;
|
||||
var identityName = string.Empty;
|
||||
var sortWeight = 0;
|
||||
|
||||
// Visible (identity) name can be different from real name
|
||||
if (session?.AttachedEntity != null)
|
||||
@@ -234,8 +235,10 @@ public sealed class AdminSystem : EntitySystem
|
||||
// Starting role, antagonist status and role type
|
||||
RoleTypePrototype roleType = new();
|
||||
var startingRole = string.Empty;
|
||||
if (_minds.TryGetMind(session, out var mindId, out var mindComp))
|
||||
if (_minds.TryGetMind(session, out var mindId, out var mindComp) && mindComp is not null)
|
||||
{
|
||||
sortWeight = _role.GetRoleCompByTime(mindComp)?.Comp.SortWeight ?? 0;
|
||||
|
||||
if (_proto.TryIndex(mindComp.RoleType, out var role))
|
||||
roleType = role;
|
||||
else
|
||||
@@ -259,8 +262,19 @@ public sealed class AdminSystem : EntitySystem
|
||||
overallPlaytime = playTime;
|
||||
}
|
||||
|
||||
return new PlayerInfo(name, entityName, identityName, startingRole, antag, roleType, GetNetEntity(session?.AttachedEntity), data.UserId,
|
||||
connected, _roundActivePlayers.Contains(data.UserId), overallPlaytime);
|
||||
return new PlayerInfo(
|
||||
name,
|
||||
entityName,
|
||||
identityName,
|
||||
startingRole,
|
||||
antag,
|
||||
roleType,
|
||||
sortWeight,
|
||||
GetNetEntity(session?.AttachedEntity),
|
||||
data.UserId,
|
||||
connected,
|
||||
_roundActivePlayers.Contains(data.UserId),
|
||||
overallPlaytime);
|
||||
}
|
||||
|
||||
private void OnPanicBunkerChanged(bool enabled)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using Content.Server._CP14.GameTicking.Rules.Components;
|
||||
using Content.Server.Administration.Commands;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Zombies;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Verbs;
|
||||
@@ -18,6 +20,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly ZombieSystem _zombie = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
private const string DefaultTraitorRule = "Traitor";
|
||||
@@ -37,6 +40,8 @@ public sealed partial class AdminVerbSystem
|
||||
[ValidatePrototypeId<StartingGearPrototype>]
|
||||
private const string PirateGearId = "PirateGear";
|
||||
|
||||
private readonly EntProtoId _paradoxCloneRuleId = "ParadoxCloneSpawn";
|
||||
|
||||
//CP14
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
private const string CP14VampireRule = "CP14Vampire";
|
||||
@@ -179,6 +184,30 @@ public sealed partial class AdminVerbSystem
|
||||
Message = string.Join(": ", thiefName, Loc.GetString("admin-verb-make-thief")),
|
||||
};
|
||||
args.Verbs.Add(thief);
|
||||
|
||||
var paradoxCloneName = Loc.GetString("admin-verb-text-make-paradox-clone");
|
||||
Verb paradox = new()
|
||||
{
|
||||
Text = paradoxCloneName,
|
||||
Category = VerbCategory.Antag,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Interface/Misc/job_icons.rsi"), "ParadoxClone"),
|
||||
Act = () =>
|
||||
{
|
||||
var ruleEnt = _gameTicker.AddGameRule(_paradoxCloneRuleId);
|
||||
|
||||
if (!TryComp<ParadoxCloneRuleComponent>(ruleEnt, out var paradoxCloneRuleComp))
|
||||
return;
|
||||
|
||||
paradoxCloneRuleComp.OriginalBody = args.Target; // override the target player
|
||||
|
||||
_gameTicker.StartGameRule(ruleEnt);
|
||||
},
|
||||
Impact = LogImpact.High,
|
||||
Message = string.Join(": ", paradoxCloneName, Loc.GetString("admin-verb-make-paradox-clone")),
|
||||
};
|
||||
|
||||
if (HasComp<HumanoidAppearanceComponent>(args.Target)) // only humanoids can be cloned
|
||||
args.Verbs.Add(paradox);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ public sealed partial class AdminVerbSystem
|
||||
Filter.PvsExcept(args.Target), true, PopupType.MediumCaution);
|
||||
var board = Spawn("ChessBoard", xform.Coordinates);
|
||||
var session = _tabletopSystem.EnsureSession(Comp<TabletopGameComponent>(board));
|
||||
xform.Coordinates = _transformSystem.ToCoordinates(session.Position);
|
||||
_transformSystem.SetMapCoordinates(args.Target, session.Position);
|
||||
_transformSystem.SetWorldRotationNoLerp((args.Target, xform), Angle.Zero);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
@@ -421,7 +421,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
var xform = Transform(args.Target);
|
||||
var fixtures = Comp<FixturesComponent>(args.Target);
|
||||
xform.Anchored = false; // Just in case.
|
||||
_transformSystem.Unanchor(args.Target); // Just in case.
|
||||
_physics.SetBodyType(args.Target, BodyType.Dynamic, manager: fixtures, body: physics);
|
||||
_physics.SetBodyStatus(args.Target, physics, BodyStatus.InAir);
|
||||
_physics.WakeBody(args.Target, manager: fixtures, body: physics);
|
||||
@@ -456,7 +456,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
var xform = Transform(args.Target);
|
||||
var fixtures = Comp<FixturesComponent>(args.Target);
|
||||
xform.Anchored = false; // Just in case.
|
||||
_transformSystem.Unanchor(args.Target); // Just in case.
|
||||
|
||||
_physics.SetBodyType(args.Target, BodyType.Dynamic, body: physics);
|
||||
_physics.SetBodyStatus(args.Target, physics, BodyStatus.InAir);
|
||||
|
||||
@@ -637,7 +637,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
if (_adminManager.HasAdminFlag(player, AdminFlags.Mapping))
|
||||
{
|
||||
if (_mapManager.IsMapPaused(map.MapId))
|
||||
if (_map.IsPaused(map.MapId))
|
||||
{
|
||||
Verb unpauseMap = new()
|
||||
{
|
||||
@@ -646,7 +646,7 @@ public sealed partial class AdminVerbSystem
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/play.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_mapManager.SetMapPaused(map.MapId, false);
|
||||
_map.SetPaused(map.MapId, false);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-unpause-map-description"),
|
||||
@@ -663,7 +663,7 @@ public sealed partial class AdminVerbSystem
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/pause.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_mapManager.SetMapPaused(map.MapId, true);
|
||||
_map.SetPaused(map.MapId, true);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-pause-map-description"),
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Content.Server.Administration.Systems
|
||||
[Dependency] private readonly IConsoleHost _console = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly AdminSystem _adminSystem = default!;
|
||||
[Dependency] private readonly DisposalTubeSystem _disposalTubes = default!;
|
||||
@@ -153,12 +153,10 @@ namespace Content.Server.Administration.Systems
|
||||
|
||||
var profile = _ticker.GetPlayerProfile(targetActor.PlayerSession);
|
||||
var mobUid = _spawning.SpawnPlayerMob(coords.Value, null, profile, stationUid);
|
||||
var targetMind = _mindSystem.GetMind(args.Target);
|
||||
|
||||
if (targetMind != null)
|
||||
{
|
||||
_mindSystem.TransferTo(targetMind.Value, mobUid, true);
|
||||
}
|
||||
if (_mindSystem.TryGetMind(args.Target, out var mindId, out var mindComp))
|
||||
_mindSystem.TransferTo(mindId, mobUid, true, mind: mindComp);
|
||||
|
||||
},
|
||||
ConfirmationPopup = true,
|
||||
Impact = LogImpact.High,
|
||||
|
||||
@@ -14,6 +14,7 @@ internal sealed class ServerAlertsSystem : AlertsSystem
|
||||
|
||||
private void OnGetState(Entity<AlertsComponent> alerts, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new AlertComponentState(alerts.Comp.Alerts);
|
||||
// TODO: Use sourcegen when clone-state bug fixed.
|
||||
args.State = new AlertComponentState(new(alerts.Comp.Alerts));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ public sealed class AmeControllerSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var humanReadableState = value ? "Inject" : "Not inject";
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{EntityManager.ToPrettyString(user.Value):player} has set the AME to {humanReadableState}");
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{EntityManager.ToPrettyString(user.Value):player} has set the AME to {humanReadableState}");
|
||||
}
|
||||
|
||||
public void ToggleInjecting(EntityUid uid, EntityUid? user = null, AmeControllerComponent? controller = null)
|
||||
@@ -267,27 +267,15 @@ public sealed class AmeControllerSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var humanReadableState = controller.Injecting ? "Inject" : "Not inject";
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{EntityManager.ToPrettyString(user.Value):player} has set the AME to inject {controller.InjectionAmount} while set to {humanReadableState}");
|
||||
|
||||
/* This needs to be information which an admin is very likely to want to be informed about in order to be an admin alert or have a sound notification.
|
||||
At the time of editing, players regularly "overclock" the AME and those cases require no admin attention.
|
||||
|
||||
// Admin alert
|
||||
var safeLimit = int.MaxValue;
|
||||
if (TryGetAMENodeGroup(uid, out var group))
|
||||
safeLimit = group.CoreCount * 4;
|
||||
|
||||
if (oldValue <= safeLimit && value > safeLimit)
|
||||
{
|
||||
if (_gameTiming.CurTime > controller.EffectCooldown)
|
||||
{
|
||||
_chatManager.SendAdminAlert(user.Value, $"increased AME over safe limit to {controller.InjectionAmount}");
|
||||
_audioSystem.PlayGlobal("/Audio/Misc/adminlarm.ogg",
|
||||
Filter.Empty().AddPlayers(_adminManager.ActiveAdmins), false, AudioParams.Default.WithVolume(-8f));
|
||||
controller.EffectCooldown = _gameTiming.CurTime + controller.CooldownDuration;
|
||||
}
|
||||
}
|
||||
*/
|
||||
var logImpact = (oldValue <= safeLimit && value > safeLimit) ? LogImpact.Extreme : LogImpact.Medium;
|
||||
|
||||
_adminLogger.Add(LogType.Action, logImpact, $"{EntityManager.ToPrettyString(user.Value):player} has set the AME to inject {controller.InjectionAmount} while set to {humanReadableState}");
|
||||
}
|
||||
|
||||
public void AdjustInjectionAmount(EntityUid uid, int delta, EntityUid? user = null, AmeControllerComponent? controller = null)
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Content.Server.Announcements;
|
||||
/// <summary>
|
||||
/// Used for any announcements on the start of a round.
|
||||
/// </summary>
|
||||
[Prototype("roundAnnouncement")]
|
||||
[Prototype]
|
||||
public sealed partial class RoundAnnouncementPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
|
||||
@@ -116,7 +116,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
|
||||
_popup.PopupEntity(message, ent, ent, PopupType.MediumCaution);
|
||||
|
||||
_adminLog.Add(LogType.Anomaly,LogImpact.Extreme,$"{ToPrettyString(ent)} became anomaly host.");
|
||||
_adminLog.Add(LogType.Anomaly,LogImpact.Medium,$"{ToPrettyString(ent)} became anomaly host.");
|
||||
}
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Antag.Components;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
|
||||
namespace Content.Server.Antag;
|
||||
@@ -14,9 +15,20 @@ public sealed class AntagRandomSpawnSystem : GameRuleSystem<AntagRandomSpawnComp
|
||||
SubscribeLocalEvent<AntagRandomSpawnComponent, AntagSelectLocationEvent>(OnSelectLocation);
|
||||
}
|
||||
|
||||
protected override void Added(EntityUid uid, AntagRandomSpawnComponent comp, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||
{
|
||||
base.Added(uid, comp, gameRule, args);
|
||||
|
||||
// we have to select this here because AntagSelectLocationEvent is raised twice because MakeAntag is called twice
|
||||
// once when a ghost role spawner is created and once when someone takes the ghost role
|
||||
|
||||
if (TryFindRandomTile(out _, out _, out _, out var coords))
|
||||
comp.Coords = coords;
|
||||
}
|
||||
|
||||
private void OnSelectLocation(Entity<AntagRandomSpawnComponent> ent, ref AntagSelectLocationEvent args)
|
||||
{
|
||||
if (TryFindRandomTile(out _, out _, out _, out var coords))
|
||||
args.Coordinates.Add(_transform.ToMapCoordinates(coords));
|
||||
if (ent.Comp.Coords != null)
|
||||
args.Coordinates.Add(_transform.ToMapCoordinates(ent.Comp.Coords.Value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Linq;
|
||||
using Content.Server.Antag.Components;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Events;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Ghost.Roles;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
@@ -65,6 +66,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
|
||||
SubscribeLocalEvent<AntagSelectionComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
|
||||
|
||||
SubscribeLocalEvent<NoJobsAvailableSpawningEvent>(OnJobNotAssigned);
|
||||
SubscribeLocalEvent<RulePlayerSpawningEvent>(OnPlayerSpawning);
|
||||
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnJobsAssigned);
|
||||
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnSpawnComplete);
|
||||
@@ -136,6 +138,28 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
}
|
||||
}
|
||||
|
||||
private void OnJobNotAssigned(NoJobsAvailableSpawningEvent args)
|
||||
{
|
||||
// If someone fails to spawn in due to there being no jobs, they should be removed from any preselected antags.
|
||||
// We only care about delayed rules, since if they're active the player should have already been removed via MakeAntag.
|
||||
var query = QueryDelayedRules();
|
||||
while (query.MoveNext(out var uid, out _, out var comp, out _))
|
||||
{
|
||||
if (comp.SelectionTime != AntagSelectionTime.IntraPlayerSpawn)
|
||||
continue;
|
||||
|
||||
if (!comp.RemoveUponFailedSpawn)
|
||||
continue;
|
||||
|
||||
foreach (var def in comp.Definitions)
|
||||
{
|
||||
if (!comp.PreSelectedSessions.TryGetValue(def, out var session))
|
||||
break;
|
||||
session.Remove(args.Player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSpawnComplete(PlayerSpawnCompleteEvent args)
|
||||
{
|
||||
if (!args.LateJoin)
|
||||
@@ -149,8 +173,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
var rules = new List<(EntityUid, AntagSelectionComponent)>();
|
||||
while (query.MoveNext(out var uid, out var antag, out _))
|
||||
{
|
||||
if (HasComp<ActiveGameRuleComponent>(uid) ||
|
||||
(HasComp<DelayedStartRuleComponent>(uid) && antag.SelectionTime == AntagSelectionTime.IntraPlayerSpawn)) //IntraPlayerSpawn selects antags before spawning, but doesn't activate until after.
|
||||
if (HasComp<ActiveGameRuleComponent>(uid))
|
||||
rules.Add((uid, antag));
|
||||
}
|
||||
RobustRandom.Shuffle(rules);
|
||||
@@ -171,9 +194,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (!TryGetNextAvailableDefinition((uid, antag), out var def, players))
|
||||
continue;
|
||||
|
||||
var onlyPreSelect = (antag.SelectionTime == AntagSelectionTime.IntraPlayerSpawn && !antag.AssignmentComplete); // Don't wanna give them antag status if the rule hasn't assigned its existing ones yet
|
||||
|
||||
if (TryMakeAntag((uid, antag), args.Player, def.Value, onlyPreSelect: onlyPreSelect))
|
||||
if (TryMakeAntag((uid, antag), args.Player, def.Value))
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -209,16 +230,12 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (component.AssignmentComplete)
|
||||
return;
|
||||
|
||||
if (!component.PreSelectionsComplete)
|
||||
{
|
||||
var players = _playerManager.Sessions
|
||||
.Where(x => GameTicker.PlayerGameStatuses.TryGetValue(x.UserId, out var status) &&
|
||||
status == PlayerGameStatus.JoinedGame)
|
||||
.ToList();
|
||||
|
||||
ChooseAntags((uid, component), players, midround: true);
|
||||
}
|
||||
var players = _playerManager.Sessions
|
||||
.Where(x => GameTicker.PlayerGameStatuses.TryGetValue(x.UserId, out var status) &&
|
||||
status == PlayerGameStatus.JoinedGame)
|
||||
.ToList();
|
||||
|
||||
ChooseAntags((uid, component), players, midround: true);
|
||||
AssignPreSelectedSessions((uid, component));
|
||||
}
|
||||
|
||||
@@ -230,9 +247,6 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, bool midround = false)
|
||||
{
|
||||
if (ent.Comp.PreSelectionsComplete)
|
||||
return;
|
||||
|
||||
foreach (var def in ent.Comp.Definitions)
|
||||
{
|
||||
ChooseAntags(ent, pool, def, midround: midround);
|
||||
@@ -254,7 +268,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
bool midround = false)
|
||||
{
|
||||
var playerPool = GetPlayerPool(ent, pool, def);
|
||||
var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def);
|
||||
var existingAntagCount = ent.Comp.PreSelectedSessions.TryGetValue(def, out var existingAntags) ? existingAntags.Count : 0;
|
||||
var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def) - existingAntagCount;
|
||||
|
||||
// if there is both a spawner and players getting picked, let it fall back to a spawner.
|
||||
var noSpawner = def.SpawnerPrototype == null;
|
||||
@@ -327,6 +342,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// </summary>
|
||||
public bool TryMakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false, bool checkPref = true, bool onlyPreSelect = false)
|
||||
{
|
||||
_adminLogger.Add(LogType.AntagSelection, $"Start trying to make {session} become the antagonist: {ToPrettyString(ent)}");
|
||||
|
||||
if (checkPref && !HasPrimaryAntagPreference(session, def))
|
||||
return false;
|
||||
|
||||
@@ -384,7 +401,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (antagEnt is not { } player)
|
||||
{
|
||||
Log.Error($"Attempted to make {session} antagonist in gamerule {ToPrettyString(ent)} but there was no valid entity for player.");
|
||||
if (session != null)
|
||||
_adminLogger.Add(LogType.AntagSelection,$"Attempted to make {session} antagonist in gamerule {ToPrettyString(ent)} but there was no valid entity for player.");
|
||||
if (session != null && ent.Comp.RemoveUponFailedSpawn)
|
||||
{
|
||||
ent.Comp.AssignedSessions.Remove(session);
|
||||
ent.Comp.PreSelectedSessions[def].Remove(session);
|
||||
@@ -393,6 +411,11 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: This is really messy because this part runs twice for midround events.
|
||||
// Once when the ghostrole spawner is created and once when a player takes it.
|
||||
// Therefore any component subscribing to this has to make sure both subscriptions return the same value
|
||||
// or the ghost role raffle location preview will be wrong.
|
||||
|
||||
var getPosEv = new AntagSelectLocationEvent(session, ent);
|
||||
RaiseLocalEvent(ent, ref getPosEv, true);
|
||||
if (getPosEv.Handled)
|
||||
@@ -409,6 +432,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (!TryComp<GhostRoleAntagSpawnerComponent>(player, out var spawnerComp))
|
||||
{
|
||||
Log.Error($"Antag spawner {player} does not have a GhostRoleAntagSpawnerComponent.");
|
||||
_adminLogger.Add(LogType.AntagSelection,$"Antag spawner {player} in gamerule {ToPrettyString(ent)} failed due to not having GhostRoleAntagSpawnerComponent.");
|
||||
if (session != null)
|
||||
{
|
||||
ent.Comp.AssignedSessions.Remove(session);
|
||||
@@ -470,6 +494,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (!IsSessionValid(ent, session, def) || !IsEntityValid(session.AttachedEntity, def))
|
||||
continue;
|
||||
|
||||
if (ent.Comp.PreSelectedSessions.TryGetValue(def, out var preSelected) && preSelected.Contains(session))
|
||||
continue;
|
||||
|
||||
if (HasPrimaryAntagPreference(session, def))
|
||||
{
|
||||
preferredList.Add(session);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Antag.Components;
|
||||
|
||||
/// <summary>
|
||||
@@ -5,4 +7,11 @@ namespace Content.Server.Antag.Components;
|
||||
/// Requires <see cref="AntagSelectionComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class AntagRandomSpawnComponent : Component;
|
||||
public sealed partial class AntagRandomSpawnComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Location that was picked.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityCoordinates? Coords;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,13 @@ public sealed partial class AntagSelectionComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId? AgentName;
|
||||
|
||||
/// <summary>
|
||||
/// If the player is pre-selected but fails to spawn in (e.g. due to only having antag-immune jobs selected),
|
||||
/// should they be removed from the pre-selection list?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool RemoveUponFailedSpawn = true;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user