Compare commits
678 Commits
ed-06-08-2
...
ed-28-08-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a944cd9834 | ||
|
|
79515d9303 | ||
|
|
ea9c6ee7bf | ||
|
|
3850317a37 | ||
|
|
d3189b83cf | ||
|
|
3d864eb0fd | ||
|
|
9c59dab6a2 | ||
|
|
5c4b7a87aa | ||
|
|
2383db6a22 | ||
|
|
cfeb5fae80 | ||
|
|
53a9eb259e | ||
|
|
c82b17a65e | ||
|
|
f6d81c04c0 | ||
|
|
34f90f1bce | ||
|
|
fc33df0e49 | ||
|
|
4cb93fc965 | ||
|
|
05efea3118 | ||
|
|
df6dbd2ac7 | ||
|
|
e438b8b25f | ||
|
|
8eb4e4fa2d | ||
|
|
7a0ee1a074 | ||
|
|
84e5565918 | ||
|
|
6c3dad4330 | ||
|
|
055dfe6f0b | ||
|
|
015cf36469 | ||
|
|
2f923d88e4 | ||
|
|
972bd566f5 | ||
|
|
d297852ba8 | ||
|
|
61f756de8a | ||
|
|
c3f42438f2 | ||
|
|
8486b24db9 | ||
|
|
e85ab9195c | ||
|
|
2c98d0a519 | ||
|
|
476c7751c4 | ||
|
|
1fa447f769 | ||
|
|
94c746abe9 | ||
|
|
1a3dc66d24 | ||
|
|
7ba049be25 | ||
|
|
18c146cc7f | ||
|
|
d11a4ba1f5 | ||
|
|
dd36c85b61 | ||
|
|
802d478e21 | ||
|
|
1ba508aef2 | ||
|
|
1c29310faf | ||
|
|
9b8e30a0b7 | ||
|
|
2577eb10fd | ||
|
|
e2ecd804b6 | ||
|
|
f272097e07 | ||
|
|
ed3e10741f | ||
|
|
ebf62aaca1 | ||
|
|
171884df8f | ||
|
|
0a05ee9a27 | ||
|
|
da3c3b5538 | ||
|
|
edc91621d3 | ||
|
|
3e15e00af5 | ||
|
|
db427dc58f | ||
|
|
529e4b767c | ||
|
|
91e2fdc4b9 | ||
|
|
19b0ce3007 | ||
|
|
9240786b53 | ||
|
|
a2c762dbef | ||
|
|
68d650653f | ||
|
|
45b0543e89 | ||
|
|
864956c66b | ||
|
|
0754a0fdaf | ||
|
|
7e3b9fb2fe | ||
|
|
3dcfe0d850 | ||
|
|
c9854c32ba | ||
|
|
d9e0681a70 | ||
|
|
03f5bf4c97 | ||
|
|
9a68cf0b0e | ||
|
|
c48a96ac15 | ||
|
|
3cb67a3564 | ||
|
|
ad691931c6 | ||
|
|
6cda6c8ba5 | ||
|
|
1952618203 | ||
|
|
7f5c11afaa | ||
|
|
900e57c6de | ||
|
|
86b358be4b | ||
|
|
4fcbb99072 | ||
|
|
b4ef1e9a0f | ||
|
|
878384dbb0 | ||
|
|
2dfcd5cf73 | ||
|
|
8830e26e0e | ||
|
|
d0bd26d575 | ||
|
|
c636b3b25d | ||
|
|
d38d497e62 | ||
|
|
78b3e3253f | ||
|
|
529f4dae64 | ||
|
|
f7fd991bc8 | ||
|
|
0c078f6cc4 | ||
|
|
a540b8840e | ||
|
|
d673bdfe67 | ||
|
|
73331416e3 | ||
|
|
7d48cc5e24 | ||
|
|
0d969f61a8 | ||
|
|
031ddc0420 | ||
|
|
517f7ad344 | ||
|
|
77a60bd3b8 | ||
|
|
047a7a488d | ||
|
|
1545fd5787 | ||
|
|
f09dd465a7 | ||
|
|
16e1c23ffe | ||
|
|
9c9bfda4d2 | ||
|
|
ad4d36f34f | ||
|
|
1f6223229a | ||
|
|
71ef47fe01 | ||
|
|
a4b0bef12c | ||
|
|
c3b3955832 | ||
|
|
9d62570385 | ||
|
|
979661a7b3 | ||
|
|
ef3de39364 | ||
|
|
f13e4c3c4f | ||
|
|
3091893055 | ||
|
|
5cc2b12f4e | ||
|
|
3ce8f16cb9 | ||
|
|
633099e1d3 | ||
|
|
ada6455b17 | ||
|
|
7562b8fd47 | ||
|
|
8440fb6acb | ||
|
|
1e7fb2f4d8 | ||
|
|
38e698e7df | ||
|
|
aa9bf9f3ec | ||
|
|
7066b4a3e0 | ||
|
|
4692c69ce1 | ||
|
|
9343023ffc | ||
|
|
969f17a960 | ||
|
|
9f595164f2 | ||
|
|
27793111bb | ||
|
|
4ee9c00200 | ||
|
|
20786c12fa | ||
|
|
63762d85ca | ||
|
|
3cf2fca993 | ||
|
|
9775975c53 | ||
|
|
79ae2cceb4 | ||
|
|
cc445e07b8 | ||
|
|
f1f16800d6 | ||
|
|
49dd17dcab | ||
|
|
99bc389fd5 | ||
|
|
0b1eae362f | ||
|
|
61a3562451 | ||
|
|
4ecc2c8468 | ||
|
|
1d2c72da80 | ||
|
|
0a9535e67c | ||
|
|
4c523b11d7 | ||
|
|
c43323dba7 | ||
|
|
c9bffa82ee | ||
|
|
7de5156084 | ||
|
|
3cdd62b0dd | ||
|
|
921ed4ec5d | ||
|
|
42316f8ed0 | ||
|
|
28f41e143d | ||
|
|
af2ee4a9e6 | ||
|
|
a17498ee91 | ||
|
|
9c1b8c18de | ||
|
|
8cdc8d7a0d | ||
|
|
53e2dfd496 | ||
|
|
17b4ede7a7 | ||
|
|
cf4f8b630f | ||
|
|
18259d1d2e | ||
|
|
8161402e35 | ||
|
|
0ae5426fe4 | ||
|
|
6c6ee2731f | ||
|
|
f06176ac39 | ||
|
|
796287d695 | ||
|
|
c41fe93040 | ||
|
|
49fd12a65f | ||
|
|
84af71eb7c | ||
|
|
03f9dee244 | ||
|
|
dc66386e5f | ||
|
|
40b9fd4ea3 | ||
|
|
f523df821a | ||
|
|
84f9dd0f0b | ||
|
|
5da2b32099 | ||
|
|
2e3365793c | ||
|
|
25074d0719 | ||
|
|
6567fa36e4 | ||
|
|
02eed07e21 | ||
|
|
b17f1c336b | ||
|
|
af01d8fbff | ||
|
|
d752702092 | ||
|
|
caece1473c | ||
|
|
19e8c2eb3e | ||
|
|
54d42acec8 | ||
|
|
c1d41267e5 | ||
|
|
b53ed58515 | ||
|
|
bc5bc37346 | ||
|
|
0fd7b69daa | ||
|
|
1bde7f3874 | ||
|
|
20334f9115 | ||
|
|
793eb87f31 | ||
|
|
df82c0ac8e | ||
|
|
e5d2738a26 | ||
|
|
b48ac8439d | ||
|
|
dc2c02cd6a | ||
|
|
6c008be126 | ||
|
|
84f229a149 | ||
|
|
cd8bd908da | ||
|
|
890c0eeb99 | ||
|
|
1fb03e9074 | ||
|
|
07451eb92b | ||
|
|
e659eb992f | ||
|
|
8ac0246998 | ||
|
|
12b6cf5e5d | ||
|
|
059e3ef72f | ||
|
|
8cb2cfc0af | ||
|
|
600d5bfae0 | ||
|
|
1ded394ffb | ||
|
|
452be33229 | ||
|
|
07ec00ed05 | ||
|
|
58a33b2593 | ||
|
|
9de922adba | ||
|
|
381e0d6f93 | ||
|
|
204f3e6ae3 | ||
|
|
1e029efd2a | ||
|
|
a186b91631 | ||
|
|
0d5d9f92cd | ||
|
|
02521d59dd | ||
|
|
44db0bfb05 | ||
|
|
208b1d23b8 | ||
|
|
14dfeab306 | ||
|
|
5d6baea7d3 | ||
|
|
dba0ea76b4 | ||
|
|
c729079408 | ||
|
|
6a462221ff | ||
|
|
7153e43946 | ||
|
|
245e111226 | ||
|
|
fab706f0cb | ||
|
|
23b61850a8 | ||
|
|
79315c2531 | ||
|
|
135d52406a | ||
|
|
0998645077 | ||
|
|
cc180e54e6 | ||
|
|
bf7c32737b | ||
|
|
329a0a875b | ||
|
|
d49d78cebf | ||
|
|
dcc11873f9 | ||
|
|
ab1613315d | ||
|
|
c9366b818c | ||
|
|
0c1cfe8f18 | ||
|
|
d4c0155d66 | ||
|
|
6db3ccb342 | ||
|
|
7491c6874d | ||
|
|
5af5aaa651 | ||
|
|
d9336781f1 | ||
|
|
ff8d294af5 | ||
|
|
47dc84e48e | ||
|
|
691e37c0db | ||
|
|
c02aeebdf1 | ||
|
|
883dc998e6 | ||
|
|
7e17063b86 | ||
|
|
ca75d5a3e3 | ||
|
|
c6209cc725 | ||
|
|
d220249902 | ||
|
|
463a5299db | ||
|
|
6759d6a614 | ||
|
|
9f550002d1 | ||
|
|
485caa4553 | ||
|
|
9f180e4f62 | ||
|
|
a0a1421da4 | ||
|
|
15812d2a08 | ||
|
|
bcd7085a3e | ||
|
|
dea9a637fb | ||
|
|
86f4346c05 | ||
|
|
c688da00a0 | ||
|
|
7aaac2c179 | ||
|
|
aa15a9a839 | ||
|
|
46f631fa11 | ||
|
|
65521696c8 | ||
|
|
8ecae1288f | ||
|
|
e26e662023 | ||
|
|
58fd50c369 | ||
|
|
6ec6a28aeb | ||
|
|
28efd76e82 | ||
|
|
31c5d3555e | ||
|
|
e0f292921f | ||
|
|
bd5d415575 | ||
|
|
456410239e | ||
|
|
8c4e5c5848 | ||
|
|
ad71d8cb2f | ||
|
|
b6d04f884c | ||
|
|
93c9d49f9b | ||
|
|
f93fe05f4c | ||
|
|
b16e57ee94 | ||
|
|
aabaaf1fec | ||
|
|
67253a3fe1 | ||
|
|
c8f1c22ff2 | ||
|
|
6aed90ed75 | ||
|
|
d7b5ae061a | ||
|
|
8d72e7d932 | ||
|
|
0dc2d0acd0 | ||
|
|
ee922e37f6 | ||
|
|
220aff21eb | ||
|
|
c25c5ec666 | ||
|
|
2f77d0d4f3 | ||
|
|
d7c3ba8409 | ||
|
|
9be61bfaa5 | ||
|
|
84217011a9 | ||
|
|
32e315c68f | ||
|
|
09c8d4abd4 | ||
|
|
f76fa0e5ae | ||
|
|
179534f18d | ||
|
|
a99900fcd8 | ||
|
|
63967bec88 | ||
|
|
81371b90d1 | ||
|
|
76d60140f6 | ||
|
|
d524d61b22 | ||
|
|
8afec58a0c | ||
|
|
95034512c1 | ||
|
|
6b5eb93fe7 | ||
|
|
6803021623 | ||
|
|
e569a78f17 | ||
|
|
cfd8ef49d9 | ||
|
|
4d75cb54c8 | ||
|
|
707daa1a5e | ||
|
|
b6e10a0b7b | ||
|
|
084812cb44 | ||
|
|
9ba038d04f | ||
|
|
249d3cd74d | ||
|
|
7e44e75d74 | ||
|
|
6926033423 | ||
|
|
fc1446e73a | ||
|
|
53058df8b9 | ||
|
|
c812bd8ff4 | ||
|
|
fdbf2a229e | ||
|
|
2b4acde2c8 | ||
|
|
d340c5c844 | ||
|
|
f642a01f96 | ||
|
|
9bc1fabdb3 | ||
|
|
4e1feded66 | ||
|
|
563190f4d8 | ||
|
|
0a4b220854 | ||
|
|
bf2bd9a33e | ||
|
|
7e70859d2e | ||
|
|
76b89b8dc5 | ||
|
|
bf256b25e9 | ||
|
|
7f23634450 | ||
|
|
45214c2063 | ||
|
|
4c678e4955 | ||
|
|
46398647bf | ||
|
|
1fe46a7210 | ||
|
|
1745000e0d | ||
|
|
437c861622 | ||
|
|
b692ab1850 | ||
|
|
45a2327ef5 | ||
|
|
ee7644c631 | ||
|
|
9f4932a8af | ||
|
|
32ce8f2164 | ||
|
|
c80c26221b | ||
|
|
218fef37c8 | ||
|
|
ed3ec4dc5f | ||
|
|
bd5b13e2f3 | ||
|
|
1e824d704f | ||
|
|
873c314aec | ||
|
|
3f4e093eaf | ||
|
|
9f8ff021ea | ||
|
|
f70e3051ed | ||
|
|
c3e96b3770 | ||
|
|
c1797b769c | ||
|
|
e3e142592a | ||
|
|
ed8bcb0a66 | ||
|
|
d0a2187a9b | ||
|
|
0b2fa941a7 | ||
|
|
1649ed45bd | ||
|
|
b8fc879cd7 | ||
|
|
55d400b1b9 | ||
|
|
0b256a025a | ||
|
|
ecf278a89b | ||
|
|
de86fdb373 | ||
|
|
3c6df0e39f | ||
|
|
268cab97f5 | ||
|
|
c46a4e057a | ||
|
|
efe36c3bf8 | ||
|
|
31cd2a0927 | ||
|
|
736325a31f | ||
|
|
e05df5d3e2 | ||
|
|
619ba162ce | ||
|
|
60b63b58dd | ||
|
|
05a21ab3ca | ||
|
|
4c4cdb5b06 | ||
|
|
8d96c993a2 | ||
|
|
e0d30aff4e | ||
|
|
eab0c34822 | ||
|
|
e6d6416a19 | ||
|
|
c091acd1f5 | ||
|
|
b7ef5ada11 | ||
|
|
1c5c155db1 | ||
|
|
279e28768c | ||
|
|
bce17e6aec | ||
|
|
b54a4206e8 | ||
|
|
06a44a82b6 | ||
|
|
a03d7c6c7f | ||
|
|
d1a60fafe7 | ||
|
|
b266c9b545 | ||
|
|
8a4ef69e86 | ||
|
|
da0b8d4731 | ||
|
|
2ea17a492d | ||
|
|
2528231ad1 | ||
|
|
ad18c6e9a5 | ||
|
|
e4ff5780d5 | ||
|
|
15ae0435c4 | ||
|
|
8ebe105458 | ||
|
|
1b1008ad85 | ||
|
|
0423691d76 | ||
|
|
07174d0aaf | ||
|
|
c43fcdfa06 | ||
|
|
25afc2653d | ||
|
|
3db51a7e36 | ||
|
|
66f0cd4f43 | ||
|
|
703c8840c4 | ||
|
|
6570515c8d | ||
|
|
03745efc7f | ||
|
|
6d6c836095 | ||
|
|
190ceda02e | ||
|
|
bd51cf330b | ||
|
|
aea28b7a99 | ||
|
|
1d5a3303e1 | ||
|
|
35819e4ba5 | ||
|
|
f24949a0b0 | ||
|
|
1df84515c7 | ||
|
|
97939f6e3e | ||
|
|
489efeb717 | ||
|
|
0057141039 | ||
|
|
929e6a2c00 | ||
|
|
ab28e1a9a9 | ||
|
|
23e4a9a004 | ||
|
|
3a50880070 | ||
|
|
b8d10a3f5e | ||
|
|
9645f5528b | ||
|
|
d86c886f61 | ||
|
|
d4ad2e473a | ||
|
|
8e3ddcc278 | ||
|
|
ff28feec14 | ||
|
|
7754ea925d | ||
|
|
3dd5d562da | ||
|
|
6d0e915ebb | ||
|
|
15aa75f092 | ||
|
|
150e8f0721 | ||
|
|
6a5cc883ce | ||
|
|
d86438c22b | ||
|
|
d10fa77372 | ||
|
|
c61384122b | ||
|
|
7dc6a56969 | ||
|
|
d4001598d4 | ||
|
|
ce97225c2d | ||
|
|
2dabf33d46 | ||
|
|
9903d36c7d | ||
|
|
6ccc10e240 | ||
|
|
5247878060 | ||
|
|
05630bd4ba | ||
|
|
b4dfeec696 | ||
|
|
e5fe6f7824 | ||
|
|
bd36ff93d2 | ||
|
|
1caddfa110 | ||
|
|
bb80d44331 | ||
|
|
048da47f32 | ||
|
|
de98531a5d | ||
|
|
37e36bfa7c | ||
|
|
a87efd7d01 | ||
|
|
7b3ff00129 | ||
|
|
7117b925a5 | ||
|
|
4426ce123a | ||
|
|
53719bded3 | ||
|
|
9522dbc655 | ||
|
|
26ad898105 | ||
|
|
fb994e8d21 | ||
|
|
e477a49cc6 | ||
|
|
a8c74ca4e1 | ||
|
|
c948bd08c6 | ||
|
|
f8c5a514bc | ||
|
|
edd17072ae | ||
|
|
9bb9e80ede | ||
|
|
38c3bf2595 | ||
|
|
f5c0efd3cd | ||
|
|
f2b2da41a6 | ||
|
|
f2eccdc3dd | ||
|
|
dd580c68c9 | ||
|
|
f7d8b4e7d4 | ||
|
|
c1eb319bda | ||
|
|
d45810bbdb | ||
|
|
b3a64d4bcd | ||
|
|
50408da21a | ||
|
|
bf06d0e869 | ||
|
|
8197382444 | ||
|
|
52e03d2de5 | ||
|
|
8771cc03db | ||
|
|
bc3d53ec0a | ||
|
|
1629e200cb | ||
|
|
e2ca1a1957 | ||
|
|
c9b69ef491 | ||
|
|
a3cbbe745e | ||
|
|
9a8620d9d8 | ||
|
|
6264a557a6 | ||
|
|
71a8fd5eab | ||
|
|
a95d2257c3 | ||
|
|
01e1624567 | ||
|
|
0ef5f668e2 | ||
|
|
68dbf6449c | ||
|
|
a79cbf383c | ||
|
|
7e285ee55c | ||
|
|
6503cb7833 | ||
|
|
c5b887fb4e | ||
|
|
d0884a3074 | ||
|
|
82d7636619 | ||
|
|
118ea97652 | ||
|
|
490de1de4e | ||
|
|
84a9253b10 | ||
|
|
d87d9c4676 | ||
|
|
c04f71728e | ||
|
|
b306130fbc | ||
|
|
8e43bb1b07 | ||
|
|
c237ef2843 | ||
|
|
548f9e31fb | ||
|
|
17cececde0 | ||
|
|
f1b8dcbfba | ||
|
|
21d0326e13 | ||
|
|
65d5539248 | ||
|
|
f15cb8b77d | ||
|
|
bb2981400c | ||
|
|
206495fd6d | ||
|
|
c3c5a6ba22 | ||
|
|
f8bbc31903 | ||
|
|
4fc8790452 | ||
|
|
94a1b67c67 | ||
|
|
8865d4a782 | ||
|
|
2aa7226266 | ||
|
|
968cd9dac6 | ||
|
|
0d506ccffc | ||
|
|
54d5bd266c | ||
|
|
6e8f8d706a | ||
|
|
b55c37ec3e | ||
|
|
936dc94a8c | ||
|
|
a937f05929 | ||
|
|
77eb70ea8a | ||
|
|
aad2441f30 | ||
|
|
a15fb56381 | ||
|
|
a9693322be | ||
|
|
b432dc6125 | ||
|
|
64273cb914 | ||
|
|
893180038f | ||
|
|
7c7dfb48bb | ||
|
|
b72818cb12 | ||
|
|
0689e16939 | ||
|
|
4a1b654928 | ||
|
|
be681aac11 | ||
|
|
7efc557bb4 | ||
|
|
32d1ea41bf | ||
|
|
a2f27f956a | ||
|
|
858f954c2f | ||
|
|
577f30fb13 | ||
|
|
ea6be02ea3 | ||
|
|
0f33025d34 | ||
|
|
1fd934d5dc | ||
|
|
f153f134fc | ||
|
|
2a29fea51d | ||
|
|
af75258601 | ||
|
|
ac4086f984 | ||
|
|
ed4ae1628c | ||
|
|
029771d672 | ||
|
|
36038a1707 | ||
|
|
8d23d36e88 | ||
|
|
1579c8a424 | ||
|
|
ccdbdee929 | ||
|
|
448f3804e6 | ||
|
|
533c968928 | ||
|
|
85bcc74bdf | ||
|
|
378d1e5c71 | ||
|
|
c36b94316e | ||
|
|
bfd47ed69a | ||
|
|
d13bcd1faa | ||
|
|
5dd3e9b514 | ||
|
|
b4607acea0 | ||
|
|
fa5995ddab | ||
|
|
2c31e03ee3 | ||
|
|
0b253e94eb | ||
|
|
67fc8af814 | ||
|
|
745d90ad73 | ||
|
|
4179821ffa | ||
|
|
c99cdf5c59 | ||
|
|
a2e945f272 | ||
|
|
20cb65f2e6 | ||
|
|
e6b67540e4 | ||
|
|
dceb562be7 | ||
|
|
1fcbaa132f | ||
|
|
b8fc703bf0 | ||
|
|
ce3169ed17 | ||
|
|
dc5f951f0e | ||
|
|
2422a606d5 | ||
|
|
71ea1516ed | ||
|
|
7da2fb1218 | ||
|
|
ff76214247 | ||
|
|
27f3bea46d | ||
|
|
2c26be606f | ||
|
|
4b7325098a | ||
|
|
ab05228479 | ||
|
|
1d79b924a6 | ||
|
|
b5524bfcd1 | ||
|
|
ad0aba5108 | ||
|
|
34450dc908 | ||
|
|
1d2b7131ab | ||
|
|
1ef4f26a44 | ||
|
|
ba1610012a | ||
|
|
52b32fa7fa | ||
|
|
7a99c962a3 | ||
|
|
b5a6f85a75 | ||
|
|
2575e44ec3 | ||
|
|
5eb6425c42 | ||
|
|
282292fdae | ||
|
|
3cecfd6e4c | ||
|
|
d09ed501f0 | ||
|
|
0432f21110 | ||
|
|
5bc9c04a02 | ||
|
|
778bfe3355 | ||
|
|
f264da89c4 | ||
|
|
b784edb758 | ||
|
|
61e87cb43a | ||
|
|
0ceb99df1c | ||
|
|
524dad566b | ||
|
|
50fcde25dd | ||
|
|
bea8c1a0b8 | ||
|
|
7fefcbc6fb | ||
|
|
23b97bd8b5 | ||
|
|
f501bd500c | ||
|
|
0fddee233d | ||
|
|
ea136838c0 | ||
|
|
9e1e9b8c34 | ||
|
|
1a672c9c6d | ||
|
|
5607c35744 | ||
|
|
247222beab | ||
|
|
04b7b80210 | ||
|
|
0b95de1efc | ||
|
|
89b8ec713a | ||
|
|
648f94f4bb | ||
|
|
517598f72d | ||
|
|
fda852d2e0 | ||
|
|
a72445c419 | ||
|
|
2fc9a65da6 | ||
|
|
9a46c24854 | ||
|
|
7eec380d99 | ||
|
|
517a62a6dd | ||
|
|
0266ac029d | ||
|
|
c4c4a934af | ||
|
|
fc65acc7a0 | ||
|
|
92210ff51b | ||
|
|
16ec6443f6 | ||
|
|
ad81c30dcb | ||
|
|
669cc55ba4 | ||
|
|
cc3d6441be | ||
|
|
031eb9606c | ||
|
|
3104215ced | ||
|
|
2f42e1d18e | ||
|
|
18370c3d1c | ||
|
|
ca8ee5fb77 | ||
|
|
85e36266fa | ||
|
|
b16de9bb35 | ||
|
|
502ceb69de | ||
|
|
0bbb099a38 | ||
|
|
c5f332ea9f | ||
|
|
83a56cc74a | ||
|
|
fcaf04d72b | ||
|
|
961514dd28 | ||
|
|
8595f59b83 | ||
|
|
16cbd1a36b | ||
|
|
7761f86b99 | ||
|
|
6114a88628 | ||
|
|
7244b9b912 | ||
|
|
3db33d944f | ||
|
|
cf7e680770 | ||
|
|
a9fc5a4b94 | ||
|
|
0e4bc4cefc | ||
|
|
ec19f9f4a9 | ||
|
|
c586561a9e | ||
|
|
0dd22c31d1 | ||
|
|
14a8e94f1e | ||
|
|
c4ef5e4205 | ||
|
|
4a13212bc3 | ||
|
|
da5918c951 | ||
|
|
46c8cecbbc |
6
.vscode/tasks.json
vendored
6
.vscode/tasks.json
vendored
@@ -10,7 +10,7 @@
|
||||
"args": [
|
||||
"build",
|
||||
"/property:GenerateFullPaths=true", // Ask dotnet build to generate full paths for file names.
|
||||
"/consoleloggerparameters:NoSummary" // Do not generate summary otherwise it leads to duplicate errors in Problems panel
|
||||
"/consoleloggerparameters:'ForceNoAlign;NoSummary'" // Do not generate summary otherwise it leads to duplicate errors in Problems panel
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
@@ -29,9 +29,9 @@
|
||||
"build",
|
||||
"${workspaceFolder}/Content.YAMLLinter/Content.YAMLLinter.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
"/consoleloggerparameters:'ForceNoAlign;NoSummary'"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public class MapLoadBenchmark
|
||||
PoolManager.Shutdown();
|
||||
}
|
||||
|
||||
public static readonly string[] MapsSource = { "Empty", "Box", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Saltern", "Packed", "Omega", "Cluster", "Reach", "Origin", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Barratry", "Oasis" };
|
||||
public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Cog" };
|
||||
|
||||
[ParamsSource(nameof(MapsSource))]
|
||||
public string Map;
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Content.Client.Access.UI
|
||||
SendMessage(new AgentIDCardJobChangedMessage(newJob));
|
||||
}
|
||||
|
||||
public void OnJobIconChanged(ProtoId<StatusIconPrototype> newJobIconId)
|
||||
public void OnJobIconChanged(ProtoId<JobIconPrototype> newJobIconId)
|
||||
{
|
||||
SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId));
|
||||
}
|
||||
@@ -55,7 +55,7 @@ namespace Content.Client.Access.UI
|
||||
|
||||
_window.SetCurrentName(cast.CurrentName);
|
||||
_window.SetCurrentJob(cast.CurrentJob);
|
||||
_window.SetAllowedIcons(cast.Icons, cast.CurrentJobIconId);
|
||||
_window.SetAllowedIcons(cast.CurrentJobIconId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,9 @@
|
||||
<LineEdit Name="NameLineEdit" />
|
||||
<Label Name="CurrentJob" Text="{Loc 'agent-id-card-current-job'}" />
|
||||
<LineEdit Name="JobLineEdit" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'agent-id-card-job-icon-label'}"/>
|
||||
<Control HorizontalExpand="True" MinSize="50 0"/>
|
||||
<GridContainer Name="IconGrid" Columns="10">
|
||||
<!-- Job icon buttons are generated in the code -->
|
||||
</GridContainer>
|
||||
</BoxContainer>
|
||||
<Label Text="{Loc 'agent-id-card-job-icon-label'}"/>
|
||||
<GridContainer Name="IconGrid" Columns="10">
|
||||
<!-- Job icon buttons are generated in the code -->
|
||||
</GridContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Numerics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Access.UI
|
||||
{
|
||||
@@ -23,7 +24,7 @@ namespace Content.Client.Access.UI
|
||||
public event Action<string>? OnNameChanged;
|
||||
public event Action<string>? OnJobChanged;
|
||||
|
||||
public event Action<ProtoId<StatusIconPrototype>>? OnJobIconChanged;
|
||||
public event Action<ProtoId<JobIconPrototype>>? OnJobIconChanged;
|
||||
|
||||
public AgentIDCardWindow()
|
||||
{
|
||||
@@ -38,17 +39,16 @@ namespace Content.Client.Access.UI
|
||||
JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text);
|
||||
}
|
||||
|
||||
public void SetAllowedIcons(HashSet<ProtoId<StatusIconPrototype>> icons, string currentJobIconId)
|
||||
public void SetAllowedIcons(string currentJobIconId)
|
||||
{
|
||||
IconGrid.DisposeAllChildren();
|
||||
|
||||
var jobIconGroup = new ButtonGroup();
|
||||
var jobIconButtonGroup = new ButtonGroup();
|
||||
var i = 0;
|
||||
foreach (var jobIconId in icons)
|
||||
var icons = _prototypeManager.EnumeratePrototypes<JobIconPrototype>().Where(icon => icon.AllowSelection).ToList();
|
||||
icons.Sort((x, y) => string.Compare(x.LocalizedJobName, y.LocalizedJobName, StringComparison.CurrentCulture));
|
||||
foreach (var jobIcon in icons)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(jobIconId, out var jobIcon))
|
||||
continue;
|
||||
|
||||
String styleBase = StyleBase.ButtonOpenBoth;
|
||||
var modulo = i % JobIconColumnCount;
|
||||
if (modulo == 0)
|
||||
@@ -62,8 +62,9 @@ namespace Content.Client.Access.UI
|
||||
Access = AccessLevel.Public,
|
||||
StyleClasses = { styleBase },
|
||||
MaxSize = new Vector2(42, 28),
|
||||
Group = jobIconGroup,
|
||||
Pressed = i == 0,
|
||||
Group = jobIconButtonGroup,
|
||||
Pressed = currentJobIconId == jobIcon.ID,
|
||||
ToolTip = jobIcon.LocalizedJobName
|
||||
};
|
||||
|
||||
// Generate buttons textures
|
||||
@@ -78,9 +79,6 @@ namespace Content.Client.Access.UI
|
||||
jobIconButton.OnPressed += _ => OnJobIconChanged?.Invoke(jobIcon.ID);
|
||||
IconGrid.AddChild(jobIconButton);
|
||||
|
||||
if (jobIconId.Equals(currentJobIconId))
|
||||
jobIconButton.Pressed = true;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace Content.Client.Actions
|
||||
SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState);
|
||||
SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState);
|
||||
SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState);
|
||||
SubscribeLocalEvent<EntityWorldTargetActionComponent, ComponentHandleState>(OnEntityWorldTargetHandleState);
|
||||
}
|
||||
|
||||
private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
|
||||
@@ -76,6 +77,18 @@ namespace Content.Client.Actions
|
||||
BaseHandleState<WorldTargetActionComponent>(uid, component, state);
|
||||
}
|
||||
|
||||
private void OnEntityWorldTargetHandleState(EntityUid uid,
|
||||
EntityWorldTargetActionComponent component,
|
||||
ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not EntityWorldTargetActionComponentState state)
|
||||
return;
|
||||
|
||||
component.Whitelist = state.Whitelist;
|
||||
component.CanTargetSelf = state.CanTargetSelf;
|
||||
BaseHandleState<EntityWorldTargetActionComponent>(uid, component, state);
|
||||
}
|
||||
|
||||
private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent
|
||||
{
|
||||
// TODO ACTIONS use auto comp states
|
||||
@@ -293,7 +306,7 @@ namespace Content.Client.Actions
|
||||
continue;
|
||||
|
||||
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
|
||||
var actionId = Spawn(null);
|
||||
var actionId = Spawn();
|
||||
AddComp(actionId, action);
|
||||
AddActionDirect(user, actionId);
|
||||
|
||||
|
||||
@@ -7,67 +7,66 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.Administration
|
||||
namespace Content.Client.Administration;
|
||||
|
||||
internal sealed class AdminNameOverlay : Overlay
|
||||
{
|
||||
internal sealed class AdminNameOverlay : Overlay
|
||||
private readonly AdminSystem _system;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly EntityLookupSystem _entityLookup;
|
||||
private readonly Font _font;
|
||||
|
||||
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup)
|
||||
{
|
||||
private readonly AdminSystem _system;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly EntityLookupSystem _entityLookup;
|
||||
private readonly Font _font;
|
||||
_system = system;
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
_entityLookup = entityLookup;
|
||||
ZIndex = 200;
|
||||
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup)
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var viewport = args.WorldAABB;
|
||||
|
||||
foreach (var playerInfo in _system.PlayerList)
|
||||
{
|
||||
_system = system;
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
_entityLookup = entityLookup;
|
||||
ZIndex = 200;
|
||||
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
var entity = _entityManager.GetEntity(playerInfo.NetEntity);
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var viewport = args.WorldAABB;
|
||||
|
||||
foreach (var playerInfo in _system.PlayerList)
|
||||
// Otherwise the entity can not exist yet
|
||||
if (entity == null || !_entityManager.EntityExists(entity))
|
||||
{
|
||||
var entity = _entityManager.GetEntity(playerInfo.NetEntity);
|
||||
|
||||
// Otherwise the entity can not exist yet
|
||||
if (entity == null || !_entityManager.EntityExists(entity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// if not on the same map, continue
|
||||
if (_entityManager.GetComponent<TransformComponent>(entity.Value).MapID != _eyeManager.CurrentMap)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var aabb = _entityLookup.GetWorldAABB(entity.Value);
|
||||
|
||||
// if not on screen, continue
|
||||
if (!aabb.Intersects(in viewport))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var lineoffset = new Vector2(0f, 11f);
|
||||
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
|
||||
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
|
||||
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
|
||||
if (playerInfo.Antag)
|
||||
{
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", Color.OrangeRed);
|
||||
}
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, playerInfo.Connected ? Color.Yellow : Color.White);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, playerInfo.Connected ? Color.Aquamarine : Color.White);
|
||||
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 (!aabb.Intersects(in viewport))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var lineoffset = new Vector2(0f, 11f);
|
||||
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
|
||||
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
|
||||
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
|
||||
if (playerInfo.Antag)
|
||||
{
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", Color.OrangeRed);
|
||||
}
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, playerInfo.Connected ? Color.Yellow : Color.White);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, playerInfo.Connected ? Color.Aquamarine : Color.White);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,26 +88,51 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
var ach = AHelpHelper.EnsurePanel(a.SessionId);
|
||||
var bch = AHelpHelper.EnsurePanel(b.SessionId);
|
||||
|
||||
// First, sort by unread. Any chat with unread messages appears first. We just sort based on unread
|
||||
// status, not number of unread messages, so that more recent unread messages take priority.
|
||||
// Pinned players first
|
||||
if (a.IsPinned != b.IsPinned)
|
||||
return a.IsPinned ? -1 : 1;
|
||||
|
||||
// First, sort by unread. Any chat with unread messages appears first.
|
||||
var aUnread = ach.Unread > 0;
|
||||
var bUnread = bch.Unread > 0;
|
||||
if (aUnread != bUnread)
|
||||
return aUnread ? -1 : 1;
|
||||
|
||||
// Sort by recent messages during the current round.
|
||||
var aRecent = a.ActiveThisRound && ach.LastMessage != DateTime.MinValue;
|
||||
var bRecent = b.ActiveThisRound && bch.LastMessage != DateTime.MinValue;
|
||||
if (aRecent != bRecent)
|
||||
return aRecent ? -1 : 1;
|
||||
|
||||
// Next, sort by connection status. Any disconnected players are grouped towards the end.
|
||||
if (a.Connected != b.Connected)
|
||||
return a.Connected ? -1 : 1;
|
||||
|
||||
// Next, group by whether or not the players have participated in this round.
|
||||
// The ahelp window shows all players that have connected since server restart, this groups them all towards the bottom.
|
||||
if (a.ActiveThisRound != b.ActiveThisRound)
|
||||
return a.ActiveThisRound ? -1 : 1;
|
||||
// Sort connected players by New Player status, then by Antag status
|
||||
if (a.Connected && b.Connected)
|
||||
{
|
||||
var aNewPlayer = a.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
|
||||
var bNewPlayer = b.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
|
||||
|
||||
if (aNewPlayer != bNewPlayer)
|
||||
return aNewPlayer ? -1 : 1;
|
||||
|
||||
if (a.Antag != b.Antag)
|
||||
return a.Antag ? -1 : 1;
|
||||
}
|
||||
|
||||
// Sort disconnected players by participation in the round
|
||||
if (!a.Connected && !b.Connected)
|
||||
{
|
||||
if (a.ActiveThisRound != b.ActiveThisRound)
|
||||
return a.ActiveThisRound ? -1 : 1;
|
||||
}
|
||||
|
||||
// Finally, sort by the most recent message.
|
||||
return bch.LastMessage.CompareTo(ach.LastMessage);
|
||||
};
|
||||
|
||||
|
||||
Bans.OnPressed += _ =>
|
||||
{
|
||||
if (_currentPlayer is not null)
|
||||
@@ -253,7 +278,20 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
|
||||
public void PopulateList()
|
||||
{
|
||||
// Maintain existing pin statuses
|
||||
var pinnedPlayers = ChannelSelector.PlayerInfo.Where(p => p.IsPinned).ToDictionary(p => p.SessionId);
|
||||
|
||||
ChannelSelector.PopulateList();
|
||||
|
||||
// Restore pin statuses
|
||||
foreach (var player in ChannelSelector.PlayerInfo)
|
||||
{
|
||||
if (pinnedPlayers.TryGetValue(player.SessionId, out var pinnedPlayer))
|
||||
{
|
||||
player.IsPinned = pinnedPlayer.IsPinned;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateButtons();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,11 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
}
|
||||
};
|
||||
|
||||
OnOpen += () => Bwoink.PopulateList();
|
||||
OnOpen += () =>
|
||||
{
|
||||
Bwoink.ChannelSelector.StopFiltering();
|
||||
Bwoink.PopulateList();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,154 +4,166 @@ using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.Verbs.UI;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Administration.UI.CustomControls
|
||||
namespace Content.Client.Administration.UI.CustomControls;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerListControl : BoxContainer
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerListControl : BoxContainer
|
||||
private readonly AdminSystem _adminSystem;
|
||||
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IUserInterfaceManager _uiManager;
|
||||
|
||||
private PlayerInfo? _selectedPlayer;
|
||||
|
||||
private List<PlayerInfo> _playerList = new();
|
||||
private List<PlayerInfo> _sortedPlayerList = new();
|
||||
|
||||
public Comparison<PlayerInfo>? Comparison;
|
||||
public Func<PlayerInfo, string, string>? OverrideText;
|
||||
|
||||
public PlayerListControl()
|
||||
{
|
||||
private readonly AdminSystem _adminSystem;
|
||||
|
||||
private List<PlayerInfo> _playerList = new();
|
||||
private readonly List<PlayerInfo> _sortedPlayerList = new();
|
||||
|
||||
public event Action<PlayerInfo?>? OnSelectionChanged;
|
||||
public IReadOnlyList<PlayerInfo> PlayerInfo => _playerList;
|
||||
|
||||
public Func<PlayerInfo, string, string>? OverrideText;
|
||||
public Comparison<PlayerInfo>? Comparison;
|
||||
|
||||
private IEntityManager _entManager;
|
||||
private IUserInterfaceManager _uiManager;
|
||||
|
||||
private PlayerInfo? _selectedPlayer;
|
||||
|
||||
public PlayerListControl()
|
||||
{
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
_uiManager = IoCManager.Resolve<IUserInterfaceManager>();
|
||||
_adminSystem = _entManager.System<AdminSystem>();
|
||||
RobustXamlLoader.Load(this);
|
||||
// Fill the Option data
|
||||
PlayerListContainer.ItemPressed += PlayerListItemPressed;
|
||||
PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
|
||||
PlayerListContainer.GenerateItem += GenerateButton;
|
||||
PlayerListContainer.NoItemSelected += PlayerListNoItemSelected;
|
||||
PopulateList(_adminSystem.PlayerList);
|
||||
FilterLineEdit.OnTextChanged += _ => FilterList();
|
||||
_adminSystem.PlayerListChanged += PopulateList;
|
||||
BackgroundPanel.PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 40)};
|
||||
}
|
||||
|
||||
private void PlayerListNoItemSelected()
|
||||
{
|
||||
_selectedPlayer = null;
|
||||
OnSelectionChanged?.Invoke(null);
|
||||
}
|
||||
|
||||
private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
|
||||
{
|
||||
if (args == null || data is not PlayerListData {Info: var selectedPlayer})
|
||||
return;
|
||||
|
||||
if (selectedPlayer == _selectedPlayer)
|
||||
return;
|
||||
|
||||
if (args.Event.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
|
||||
OnSelectionChanged?.Invoke(selectedPlayer);
|
||||
_selectedPlayer = selectedPlayer;
|
||||
|
||||
// update label text. Only required if there is some override (e.g. unread bwoink count).
|
||||
if (OverrideText != null && args.Button.Children.FirstOrDefault()?.Children?.FirstOrDefault() is Label label)
|
||||
label.Text = GetText(selectedPlayer);
|
||||
}
|
||||
|
||||
private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
|
||||
{
|
||||
if (args == null || data is not PlayerListData { Info: var selectedPlayer })
|
||||
return;
|
||||
|
||||
if (args.Function != EngineKeyFunctions.UIRightClick || selectedPlayer.NetEntity == null)
|
||||
return;
|
||||
|
||||
_uiManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
public void StopFiltering()
|
||||
{
|
||||
FilterLineEdit.Text = string.Empty;
|
||||
}
|
||||
|
||||
private void FilterList()
|
||||
{
|
||||
_sortedPlayerList.Clear();
|
||||
foreach (var info in _playerList)
|
||||
{
|
||||
var displayName = $"{info.CharacterName} ({info.Username})";
|
||||
if (info.IdentityName != info.CharacterName)
|
||||
displayName += $" [{info.IdentityName}]";
|
||||
if (!string.IsNullOrEmpty(FilterLineEdit.Text)
|
||||
&& !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant()))
|
||||
continue;
|
||||
_sortedPlayerList.Add(info);
|
||||
}
|
||||
|
||||
if (Comparison != null)
|
||||
_sortedPlayerList.Sort((a, b) => Comparison(a, b));
|
||||
|
||||
PlayerListContainer.PopulateList(_sortedPlayerList.Select(info => new PlayerListData(info)).ToList());
|
||||
if (_selectedPlayer != null)
|
||||
PlayerListContainer.Select(new PlayerListData(_selectedPlayer));
|
||||
}
|
||||
|
||||
public void PopulateList(IReadOnlyList<PlayerInfo>? players = null)
|
||||
{
|
||||
players ??= _adminSystem.PlayerList;
|
||||
|
||||
_playerList = players.ToList();
|
||||
if (_selectedPlayer != null && !_playerList.Contains(_selectedPlayer))
|
||||
_selectedPlayer = null;
|
||||
|
||||
FilterList();
|
||||
}
|
||||
|
||||
private string GetText(PlayerInfo info)
|
||||
{
|
||||
var text = $"{info.CharacterName} ({info.Username})";
|
||||
if (OverrideText != null)
|
||||
text = OverrideText.Invoke(info, text);
|
||||
return text;
|
||||
}
|
||||
|
||||
private void GenerateButton(ListData data, ListContainerButton button)
|
||||
{
|
||||
if (data is not PlayerListData { Info: var info })
|
||||
return;
|
||||
|
||||
button.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
ClipText = true,
|
||||
Text = GetText(info)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
|
||||
}
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
_uiManager = IoCManager.Resolve<IUserInterfaceManager>();
|
||||
_adminSystem = _entManager.System<AdminSystem>();
|
||||
RobustXamlLoader.Load(this);
|
||||
// Fill the Option data
|
||||
PlayerListContainer.ItemPressed += PlayerListItemPressed;
|
||||
PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
|
||||
PlayerListContainer.GenerateItem += GenerateButton;
|
||||
PlayerListContainer.NoItemSelected += PlayerListNoItemSelected;
|
||||
PopulateList(_adminSystem.PlayerList);
|
||||
FilterLineEdit.OnTextChanged += _ => FilterList();
|
||||
_adminSystem.PlayerListChanged += PopulateList;
|
||||
BackgroundPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = new Color(32, 32, 40) };
|
||||
}
|
||||
|
||||
public record PlayerListData(PlayerInfo Info) : ListData;
|
||||
public IReadOnlyList<PlayerInfo> PlayerInfo => _playerList;
|
||||
|
||||
public event Action<PlayerInfo?>? OnSelectionChanged;
|
||||
|
||||
private void PlayerListNoItemSelected()
|
||||
{
|
||||
_selectedPlayer = null;
|
||||
OnSelectionChanged?.Invoke(null);
|
||||
}
|
||||
|
||||
private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
|
||||
{
|
||||
if (args == null || data is not PlayerListData { Info: var selectedPlayer })
|
||||
return;
|
||||
|
||||
if (selectedPlayer == _selectedPlayer)
|
||||
return;
|
||||
|
||||
if (args.Event.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
|
||||
OnSelectionChanged?.Invoke(selectedPlayer);
|
||||
_selectedPlayer = selectedPlayer;
|
||||
|
||||
// update label text. Only required if there is some override (e.g. unread bwoink count).
|
||||
if (OverrideText != null && args.Button.Children.FirstOrDefault()?.Children?.FirstOrDefault() is Label label)
|
||||
label.Text = GetText(selectedPlayer);
|
||||
}
|
||||
|
||||
private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
|
||||
{
|
||||
if (args == null || data is not PlayerListData { Info: var selectedPlayer })
|
||||
return;
|
||||
|
||||
if (args.Function != EngineKeyFunctions.UIRightClick || selectedPlayer.NetEntity == null)
|
||||
return;
|
||||
|
||||
_uiManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
public void StopFiltering()
|
||||
{
|
||||
FilterLineEdit.Text = string.Empty;
|
||||
}
|
||||
|
||||
private void FilterList()
|
||||
{
|
||||
_sortedPlayerList.Clear();
|
||||
foreach (var info in _playerList)
|
||||
{
|
||||
var displayName = $"{info.CharacterName} ({info.Username})";
|
||||
if (info.IdentityName != info.CharacterName)
|
||||
displayName += $" [{info.IdentityName}]";
|
||||
if (!string.IsNullOrEmpty(FilterLineEdit.Text)
|
||||
&& !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant()))
|
||||
continue;
|
||||
_sortedPlayerList.Add(info);
|
||||
}
|
||||
|
||||
if (Comparison != null)
|
||||
_sortedPlayerList.Sort((a, b) => Comparison(a, b));
|
||||
|
||||
PlayerListContainer.PopulateList(_sortedPlayerList.Select(info => new PlayerListData(info)).ToList());
|
||||
if (_selectedPlayer != null)
|
||||
PlayerListContainer.Select(new PlayerListData(_selectedPlayer));
|
||||
}
|
||||
|
||||
|
||||
public void PopulateList(IReadOnlyList<PlayerInfo>? players = null)
|
||||
{
|
||||
// Maintain existing pin statuses
|
||||
var pinnedPlayers = _playerList.Where(p => p.IsPinned).ToDictionary(p => p.SessionId);
|
||||
|
||||
players ??= _adminSystem.PlayerList;
|
||||
|
||||
_playerList = players.ToList();
|
||||
|
||||
// Restore pin statuses
|
||||
foreach (var player in _playerList)
|
||||
{
|
||||
if (pinnedPlayers.TryGetValue(player.SessionId, out var pinnedPlayer))
|
||||
{
|
||||
player.IsPinned = pinnedPlayer.IsPinned;
|
||||
}
|
||||
}
|
||||
|
||||
if (_selectedPlayer != null && !_playerList.Contains(_selectedPlayer))
|
||||
_selectedPlayer = null;
|
||||
|
||||
FilterList();
|
||||
}
|
||||
|
||||
|
||||
private string GetText(PlayerInfo info)
|
||||
{
|
||||
var text = $"{info.CharacterName} ({info.Username})";
|
||||
if (OverrideText != null)
|
||||
text = OverrideText.Invoke(info, text);
|
||||
return text;
|
||||
}
|
||||
|
||||
private void GenerateButton(ListData data, ListContainerButton button)
|
||||
{
|
||||
if (data is not PlayerListData { Info: var info })
|
||||
return;
|
||||
|
||||
var entry = new PlayerListEntry();
|
||||
entry.Setup(info, OverrideText);
|
||||
entry.OnPinStatusChanged += _ =>
|
||||
{
|
||||
FilterList();
|
||||
};
|
||||
|
||||
button.AddChild(entry);
|
||||
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
|
||||
}
|
||||
}
|
||||
|
||||
public record PlayerListData(PlayerInfo Info) : ListData;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
Orientation="Horizontal" HorizontalExpand="true">
|
||||
<Label Name="PlayerEntryLabel" Text="" ClipText="True" HorizontalExpand="True" />
|
||||
<TextureButton Name="PlayerEntryPinButton"
|
||||
HorizontalAlignment="Right" />
|
||||
</BoxContainer>
|
||||
@@ -0,0 +1,58 @@
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Administration.UI.CustomControls;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerListEntry : BoxContainer
|
||||
{
|
||||
public PlayerListEntry()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public event Action<PlayerInfo>? OnPinStatusChanged;
|
||||
|
||||
public void Setup(PlayerInfo info, Func<PlayerInfo, string, string>? overrideText)
|
||||
{
|
||||
Update(info, overrideText);
|
||||
PlayerEntryPinButton.OnPressed += HandlePinButtonPressed(info);
|
||||
}
|
||||
|
||||
private Action<BaseButton.ButtonEventArgs> HandlePinButtonPressed(PlayerInfo info)
|
||||
{
|
||||
return args =>
|
||||
{
|
||||
info.IsPinned = !info.IsPinned;
|
||||
UpdatePinButtonTexture(info.IsPinned);
|
||||
OnPinStatusChanged?.Invoke(info);
|
||||
};
|
||||
}
|
||||
|
||||
private void Update(PlayerInfo info, Func<PlayerInfo, string, string>? overrideText)
|
||||
{
|
||||
PlayerEntryLabel.Text = overrideText?.Invoke(info, $"{info.CharacterName} ({info.Username})") ??
|
||||
$"{info.CharacterName} ({info.Username})";
|
||||
|
||||
UpdatePinButtonTexture(info.IsPinned);
|
||||
}
|
||||
|
||||
private void UpdatePinButtonTexture(bool isPinned)
|
||||
{
|
||||
if (isPinned)
|
||||
{
|
||||
PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonUnpinned);
|
||||
PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonPinned);
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonPinned);
|
||||
PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonUnpinned);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<ui:FancyWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc ban-panel-title}" MinSize="300 300">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="PlayerName"/>
|
||||
<Button Name="UsernameCopyButton" Text="{Loc player-panel-copy-username}"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="Whitelisted"/>
|
||||
<controls:ConfirmButton Name="WhitelistToggle" Text="{Loc 'player-panel-false'}" Visible="False"></controls:ConfirmButton>
|
||||
</BoxContainer>
|
||||
<Label Name="Playtime"/>
|
||||
<Label Name="Notes"/>
|
||||
<Label Name="Bans"/>
|
||||
<Label Name="RoleBans"/>
|
||||
<Label Name="SharedConnections"/>
|
||||
|
||||
<BoxContainer Align="Center">
|
||||
<GridContainer Rows="5">
|
||||
<Button Name="NotesButton" Text="{Loc player-panel-show-notes}" SetWidth="136" Disabled="True"/>
|
||||
<Button Name="AhelpButton" Text="{Loc player-panel-help}" Disabled="True"/>
|
||||
<Button Name="FreezeButton" Text = "{Loc player-panel-freeze}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="KickButton" Text="{Loc player-panel-kick}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="DeleteButton" Text="{Loc player-panel-delete}" Disabled="True"/>
|
||||
<Button Name="ShowBansButton" Text="{Loc player-panel-show-bans}" SetWidth="136" Disabled="True"/>
|
||||
<Button Name="LogsButton" Text="{Loc player-panel-logs}" Disabled="True"/>
|
||||
<Button Name="FreezeAndMuteToggleButton" Text="{Loc player-panel-freeze-and-mute}" Disabled="True"/>
|
||||
<Button Name="BanButton" Text="{Loc player-panel-ban}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="RejuvenateButton" Text="{Loc player-panel-rejuvenate}" Disabled="True"/>
|
||||
</GridContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</ui:FancyWindow>
|
||||
132
Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml.cs
Normal file
132
Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Administration.UI.PlayerPanel;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerPanel : FancyWindow
|
||||
{
|
||||
private readonly IClientAdminManager _adminManager;
|
||||
|
||||
public event Action<string>? OnUsernameCopy;
|
||||
public event Action<NetUserId?>? OnOpenNotes;
|
||||
public event Action<NetUserId?>? OnOpenBans;
|
||||
public event Action<NetUserId?>? OnAhelp;
|
||||
public event Action<string?>? OnKick;
|
||||
public event Action<NetUserId?>? OnOpenBanPanel;
|
||||
public event Action<NetUserId?, bool>? OnWhitelistToggle;
|
||||
public event Action? OnFreezeAndMuteToggle;
|
||||
public event Action? OnFreeze;
|
||||
public event Action? OnLogs;
|
||||
public event Action? OnDelete;
|
||||
public event Action? OnRejuvenate;
|
||||
|
||||
public NetUserId? TargetPlayer;
|
||||
public string? TargetUsername;
|
||||
private bool _isWhitelisted;
|
||||
|
||||
public PlayerPanel(IClientAdminManager adminManager)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_adminManager = adminManager;
|
||||
|
||||
UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(PlayerName.Text ?? "");
|
||||
BanButton.OnPressed += _ => OnOpenBanPanel?.Invoke(TargetPlayer);
|
||||
KickButton.OnPressed += _ => OnKick?.Invoke(TargetUsername);
|
||||
NotesButton.OnPressed += _ => OnOpenNotes?.Invoke(TargetPlayer);
|
||||
ShowBansButton.OnPressed += _ => OnOpenBans?.Invoke(TargetPlayer);
|
||||
AhelpButton.OnPressed += _ => OnAhelp?.Invoke(TargetPlayer);
|
||||
WhitelistToggle.OnPressed += _ =>
|
||||
{
|
||||
OnWhitelistToggle?.Invoke(TargetPlayer, _isWhitelisted);
|
||||
SetWhitelisted(!_isWhitelisted);
|
||||
};
|
||||
FreezeButton.OnPressed += _ => OnFreeze?.Invoke();
|
||||
FreezeAndMuteToggleButton.OnPressed += _ => OnFreezeAndMuteToggle?.Invoke();
|
||||
LogsButton.OnPressed += _ => OnLogs?.Invoke();
|
||||
DeleteButton.OnPressed += _ => OnDelete?.Invoke();
|
||||
RejuvenateButton.OnPressed += _ => OnRejuvenate?.Invoke();
|
||||
}
|
||||
|
||||
public void SetUsername(string player)
|
||||
{
|
||||
Title = Loc.GetString("player-panel-title", ("player", player));
|
||||
PlayerName.Text = Loc.GetString("player-panel-username", ("player", player));
|
||||
}
|
||||
|
||||
public void SetWhitelisted(bool? whitelisted)
|
||||
{
|
||||
if (whitelisted == null)
|
||||
{
|
||||
Whitelisted.Text = null;
|
||||
WhitelistToggle.Visible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Whitelisted.Text = Loc.GetString("player-panel-whitelisted");
|
||||
WhitelistToggle.Text = whitelisted.Value ? Loc.GetString("player-panel-true") : Loc.GetString("player-panel-false");
|
||||
WhitelistToggle.Visible = true;
|
||||
_isWhitelisted = whitelisted.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetBans(int? totalBans, int? totalRoleBans)
|
||||
{
|
||||
// If one value exists then so should the other.
|
||||
DebugTools.Assert(totalBans.HasValue && totalRoleBans.HasValue || totalBans == null && totalRoleBans == null);
|
||||
|
||||
Bans.Text = totalBans != null ? Loc.GetString("player-panel-bans", ("totalBans", totalBans)) : null;
|
||||
|
||||
RoleBans.Text = totalRoleBans != null ? Loc.GetString("player-panel-rolebans", ("totalRoleBans", totalRoleBans)) : null;
|
||||
}
|
||||
|
||||
public void SetNotes(int? totalNotes)
|
||||
{
|
||||
Notes.Text = totalNotes != null ? Loc.GetString("player-panel-notes", ("totalNotes", totalNotes)) : null;
|
||||
}
|
||||
|
||||
public void SetSharedConnections(int sharedConnections)
|
||||
{
|
||||
SharedConnections.Text = Loc.GetString("player-panel-shared-connections", ("sharedConnections", sharedConnections));
|
||||
}
|
||||
|
||||
public void SetPlaytime(TimeSpan playtime)
|
||||
{
|
||||
Playtime.Text = Loc.GetString("player-panel-playtime",
|
||||
("days", playtime.Days),
|
||||
("hours", playtime.Hours % 24),
|
||||
("minutes", playtime.Minutes % (24 * 60)));
|
||||
}
|
||||
|
||||
public void SetFrozen(bool canFreeze, bool frozen)
|
||||
{
|
||||
FreezeAndMuteToggleButton.Disabled = !canFreeze;
|
||||
FreezeButton.Disabled = !canFreeze || frozen;
|
||||
|
||||
FreezeAndMuteToggleButton.Text = Loc.GetString(!frozen ? "player-panel-freeze-and-mute" : "player-panel-unfreeze");
|
||||
}
|
||||
|
||||
public void SetAhelp(bool canAhelp)
|
||||
{
|
||||
AhelpButton.Disabled = !canAhelp;
|
||||
}
|
||||
|
||||
public void SetButtons()
|
||||
{
|
||||
BanButton.Disabled = !_adminManager.CanCommand("banpanel");
|
||||
KickButton.Disabled = !_adminManager.CanCommand("kick");
|
||||
NotesButton.Disabled = !_adminManager.CanCommand("adminnotes");
|
||||
ShowBansButton.Disabled = !_adminManager.CanCommand("banlist");
|
||||
WhitelistToggle.Disabled =
|
||||
!(_adminManager.CanCommand("whitelistadd") && _adminManager.CanCommand("whitelistremove"));
|
||||
LogsButton.Disabled = !_adminManager.CanCommand("adminlogs");
|
||||
RejuvenateButton.Disabled = !_adminManager.HasFlag(AdminFlags.Debug);
|
||||
DeleteButton.Disabled = !_adminManager.HasFlag(AdminFlags.Debug);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Administration.UI.PlayerPanel;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class PlayerPanelEui : BaseEui
|
||||
{
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
[Dependency] private readonly IClipboardManager _clipboard = default!;
|
||||
|
||||
private PlayerPanel PlayerPanel { get; }
|
||||
|
||||
public PlayerPanelEui()
|
||||
{
|
||||
PlayerPanel = new PlayerPanel(_admin);
|
||||
|
||||
PlayerPanel.OnUsernameCopy += username => _clipboard.SetText(username);
|
||||
PlayerPanel.OnOpenNotes += id => _console.ExecuteCommand($"adminnotes \"{id}\"");
|
||||
// Kick command does not support GUIDs
|
||||
PlayerPanel.OnKick += username => _console.ExecuteCommand($"kick \"{username}\"");
|
||||
PlayerPanel.OnOpenBanPanel += id => _console.ExecuteCommand($"banpanel \"{id}\"");
|
||||
PlayerPanel.OnOpenBans += id => _console.ExecuteCommand($"banlist \"{id}\"");
|
||||
PlayerPanel.OnAhelp += id => _console.ExecuteCommand($"openahelp \"{id}\"");
|
||||
PlayerPanel.OnWhitelistToggle += (id, whitelisted) =>
|
||||
{
|
||||
_console.ExecuteCommand(whitelisted ? $"whitelistremove \"{id}\"" : $"whitelistadd \"{id}\"");
|
||||
};
|
||||
|
||||
PlayerPanel.OnFreezeAndMuteToggle += () => SendMessage(new PlayerPanelFreezeMessage(true));
|
||||
PlayerPanel.OnFreeze += () => SendMessage(new PlayerPanelFreezeMessage());
|
||||
PlayerPanel.OnLogs += () => SendMessage(new PlayerPanelLogsMessage());
|
||||
PlayerPanel.OnRejuvenate += () => SendMessage(new PlayerPanelRejuvenationMessage());
|
||||
PlayerPanel.OnDelete+= () => SendMessage(new PlayerPanelDeleteMessage());
|
||||
|
||||
PlayerPanel.OnClose += () => SendMessage(new CloseEuiMessage());
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
PlayerPanel.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
PlayerPanel.Close();
|
||||
}
|
||||
|
||||
public override void HandleState(EuiStateBase state)
|
||||
{
|
||||
if (state is not PlayerPanelEuiState s)
|
||||
return;
|
||||
|
||||
PlayerPanel.TargetPlayer = s.Guid;
|
||||
PlayerPanel.TargetUsername = s.Username;
|
||||
PlayerPanel.SetUsername(s.Username);
|
||||
PlayerPanel.SetPlaytime(s.Playtime);
|
||||
PlayerPanel.SetBans(s.TotalBans, s.TotalRoleBans);
|
||||
PlayerPanel.SetNotes(s.TotalNotes);
|
||||
PlayerPanel.SetWhitelisted(s.Whitelisted);
|
||||
PlayerPanel.SetSharedConnections(s.SharedConnections);
|
||||
PlayerPanel.SetFrozen(s.CanFreeze, s.Frozen);
|
||||
PlayerPanel.SetAhelp(s.CanAhelp);
|
||||
PlayerPanel.SetButtons();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,21 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.Station;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ObjectsTab : Control
|
||||
{
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
|
||||
private readonly Color _altColor = Color.FromHex("#292B38");
|
||||
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
|
||||
@@ -49,10 +51,20 @@ public sealed partial class ObjectsTab : Control
|
||||
RefreshListButton.OnPressed += _ => RefreshObjectList();
|
||||
|
||||
var defaultSelection = ObjectsTabSelection.Grids;
|
||||
ObjectTypeOptions.SelectId((int) defaultSelection);
|
||||
ObjectTypeOptions.SelectId((int)defaultSelection);
|
||||
RefreshObjectList(defaultSelection);
|
||||
}
|
||||
|
||||
private void TeleportTo(NetEntity nent)
|
||||
{
|
||||
_console.ExecuteCommand($"tpto {nent}");
|
||||
}
|
||||
|
||||
private void Delete(NetEntity nent)
|
||||
{
|
||||
_console.ExecuteCommand($"delete {nent}");
|
||||
}
|
||||
|
||||
public void RefreshObjectList()
|
||||
{
|
||||
RefreshObjectList(_selections[ObjectTypeOptions.SelectedId]);
|
||||
@@ -116,9 +128,9 @@ public sealed partial class ObjectsTab : Control
|
||||
if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor })
|
||||
return;
|
||||
|
||||
var entry = new ObjectsTabEntry(info.Name,
|
||||
info.Entity,
|
||||
new StyleBoxFlat { BackgroundColor = backgroundColor });
|
||||
var entry = new ObjectsTabEntry(_admin, info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor });
|
||||
entry.OnTeleport += TeleportTo;
|
||||
entry.OnDelete += Delete;
|
||||
button.ToolTip = $"{info.Name}, {info.Entity}";
|
||||
|
||||
button.AddChild(entry);
|
||||
|
||||
@@ -5,13 +5,25 @@
|
||||
HorizontalExpand="True"
|
||||
SeparationOverride="4">
|
||||
<Label Name="NameLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
SizeFlagsStretchRatio="5"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<Label Name="EIDLabel"
|
||||
SizeFlagsStretchRatio="5"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<Button Name="TeleportButton"
|
||||
Text="{Loc object-tab-entity-teleport}"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<Button Name="DeleteButton"
|
||||
Text="{Loc object-tab-entity-delete}"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Content.Client.Administration.Managers;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -10,12 +11,30 @@ public sealed partial class ObjectsTabEntry : PanelContainer
|
||||
{
|
||||
public NetEntity AssocEntity;
|
||||
|
||||
public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox)
|
||||
public Action<NetEntity>? OnTeleport;
|
||||
public Action<NetEntity>? OnDelete;
|
||||
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
|
||||
|
||||
public ObjectsTabEntry(IClientAdminManager manager, string name, NetEntity nent, StyleBox styleBox)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
AssocEntity = nent;
|
||||
EIDLabel.Text = nent.ToString();
|
||||
NameLabel.Text = name;
|
||||
BackgroundColorPanel.PanelOverride = styleBox;
|
||||
|
||||
TeleportButton.Disabled = !manager.CanCommand("tpto");
|
||||
DeleteButton.Disabled = !manager.CanCommand("delete");
|
||||
|
||||
TeleportButton.OnPressed += _ => OnTeleport?.Invoke(nent);
|
||||
DeleteButton.OnPressed += _ =>
|
||||
{
|
||||
if (!AdminUIHelpers.TryConfirm(DeleteButton, _confirmations))
|
||||
{
|
||||
return;
|
||||
}
|
||||
OnDelete?.Invoke(nent);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,23 @@
|
||||
HorizontalExpand="True"
|
||||
SeparationOverride="4">
|
||||
<Label Name="ObjectNameLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
SizeFlagsStretchRatio="5"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc object-tab-object-name}"
|
||||
MouseFilter="Pass"/>
|
||||
<cc:VSeparator/>
|
||||
<Label Name="EntityIDLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
SizeFlagsStretchRatio="5"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc object-tab-entity-id}"
|
||||
MouseFilter="Pass"/>
|
||||
<Label Name="EntityTeleportLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"/>
|
||||
<Label Name="EntityDeleteLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"/>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -10,220 +10,219 @@ using Robust.Client.UserInterface.XAML;
|
||||
using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerTab : Control
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerTab : Control
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
|
||||
private const string ArrowUp = "↑";
|
||||
private const string ArrowDown = "↓";
|
||||
private readonly Color _altColor = Color.FromHex("#292B38");
|
||||
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
|
||||
private readonly AdminSystem _adminSystem;
|
||||
private IReadOnlyList<PlayerInfo> _players = new List<PlayerInfo>();
|
||||
|
||||
private Header _headerClicked = Header.Username;
|
||||
private bool _ascending = true;
|
||||
private bool _showDisconnected;
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
|
||||
|
||||
public PlayerTab()
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
private const string ArrowUp = "↑";
|
||||
private const string ArrowDown = "↓";
|
||||
private readonly Color _altColor = Color.FromHex("#292B38");
|
||||
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
|
||||
private readonly AdminSystem _adminSystem;
|
||||
private IReadOnlyList<PlayerInfo> _players = new List<PlayerInfo>();
|
||||
_adminSystem = _entManager.System<AdminSystem>();
|
||||
_adminSystem.PlayerListChanged += RefreshPlayerList;
|
||||
_adminSystem.OverlayEnabled += OverlayEnabled;
|
||||
_adminSystem.OverlayDisabled += OverlayDisabled;
|
||||
|
||||
private Header _headerClicked = Header.Username;
|
||||
private bool _ascending = true;
|
||||
private bool _showDisconnected;
|
||||
OverlayButton.OnPressed += OverlayButtonPressed;
|
||||
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
|
||||
ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor);
|
||||
ListHeader.OnHeaderClicked += HeaderClicked;
|
||||
|
||||
public PlayerTab()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
SearchList.SearchBar = SearchLineEdit;
|
||||
SearchList.GenerateItem += GenerateButton;
|
||||
SearchList.DataFilterCondition += DataFilterCondition;
|
||||
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
|
||||
|
||||
_adminSystem = _entManager.System<AdminSystem>();
|
||||
_adminSystem.PlayerListChanged += RefreshPlayerList;
|
||||
_adminSystem.OverlayEnabled += OverlayEnabled;
|
||||
_adminSystem.OverlayDisabled += OverlayDisabled;
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
|
||||
OverlayButton.OnPressed += OverlayButtonPressed;
|
||||
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
|
||||
|
||||
ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor);
|
||||
ListHeader.OnHeaderClicked += HeaderClicked;
|
||||
|
||||
SearchList.SearchBar = SearchLineEdit;
|
||||
SearchList.GenerateItem += GenerateButton;
|
||||
SearchList.DataFilterCondition += DataFilterCondition;
|
||||
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
|
||||
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
|
||||
}
|
||||
|
||||
#region Antag Overlay
|
||||
|
||||
private void OverlayEnabled()
|
||||
{
|
||||
OverlayButton.Pressed = true;
|
||||
}
|
||||
|
||||
private void OverlayDisabled()
|
||||
{
|
||||
OverlayButton.Pressed = false;
|
||||
}
|
||||
|
||||
private void OverlayButtonPressed(ButtonEventArgs args)
|
||||
{
|
||||
if (args.Button.Pressed)
|
||||
{
|
||||
_adminSystem.AdminOverlayOn();
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminSystem.AdminOverlayOff();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void ShowDisconnectedPressed(ButtonEventArgs args)
|
||||
{
|
||||
_showDisconnected = args.Button.Pressed;
|
||||
RefreshPlayerList(_players);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_adminSystem.PlayerListChanged -= RefreshPlayerList;
|
||||
_adminSystem.OverlayEnabled -= OverlayEnabled;
|
||||
_adminSystem.OverlayDisabled -= OverlayDisabled;
|
||||
|
||||
OverlayButton.OnPressed -= OverlayButtonPressed;
|
||||
|
||||
ListHeader.OnHeaderClicked -= HeaderClicked;
|
||||
}
|
||||
}
|
||||
|
||||
#region ListContainer
|
||||
|
||||
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
|
||||
{
|
||||
_players = players;
|
||||
PlayerCount.Text = Loc.GetString("player-tab-player-count", ("count", _playerMan.PlayerCount));
|
||||
|
||||
var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList();
|
||||
|
||||
var sortedPlayers = new List<PlayerInfo>(filteredPlayers);
|
||||
sortedPlayers.Sort(Compare);
|
||||
|
||||
UpdateHeaderSymbols();
|
||||
|
||||
SearchList.PopulateList(sortedPlayers.Select(info => new PlayerListData(info,
|
||||
$"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}"))
|
||||
.ToList());
|
||||
}
|
||||
|
||||
private void GenerateButton(ListData data, ListContainerButton button)
|
||||
{
|
||||
if (data is not PlayerListData { Info: var player})
|
||||
return;
|
||||
|
||||
var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor));
|
||||
button.AddChild(entry);
|
||||
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.
|
||||
/// If all characters are lowercase, the comparison ignores case.
|
||||
/// If there is an uppercase character, the comparison is case sensitive.
|
||||
/// </summary>
|
||||
/// <param name="filter"></param>
|
||||
/// <param name="listData"></param>
|
||||
/// <returns>Whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.</returns>
|
||||
private bool DataFilterCondition(string filter, ListData listData)
|
||||
{
|
||||
if (listData is not PlayerListData {Info: var info, FilteringString: var playerString})
|
||||
return false;
|
||||
|
||||
if (!_showDisconnected && !info.Connected)
|
||||
return false;
|
||||
|
||||
if (IsAllLower(filter))
|
||||
{
|
||||
if (!playerString.Contains(filter, StringComparison.CurrentCultureIgnoreCase))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!playerString.Contains(filter))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsAllLower(string input)
|
||||
{
|
||||
foreach (var c in input)
|
||||
{
|
||||
if (char.IsLetter(c) && !char.IsLower(c))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Header
|
||||
|
||||
private void UpdateHeaderSymbols()
|
||||
{
|
||||
ListHeader.ResetHeaderText();
|
||||
ListHeader.GetHeader(_headerClicked).Text += $" {(_ascending ? ArrowUp : ArrowDown)}";
|
||||
}
|
||||
|
||||
private int Compare(PlayerInfo x, PlayerInfo y)
|
||||
{
|
||||
if (!_ascending)
|
||||
{
|
||||
(x, y) = (y, x);
|
||||
}
|
||||
|
||||
return _headerClicked switch
|
||||
{
|
||||
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.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
|
||||
private int Compare(string x, string y)
|
||||
{
|
||||
return string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private void HeaderClicked(Header header)
|
||||
{
|
||||
if (_headerClicked == header)
|
||||
{
|
||||
_ascending = !_ascending;
|
||||
}
|
||||
else
|
||||
{
|
||||
_headerClicked = header;
|
||||
_ascending = true;
|
||||
}
|
||||
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public record PlayerListData(PlayerInfo Info, string FilteringString) : ListData;
|
||||
#region Antag Overlay
|
||||
|
||||
private void OverlayEnabled()
|
||||
{
|
||||
OverlayButton.Pressed = true;
|
||||
}
|
||||
|
||||
private void OverlayDisabled()
|
||||
{
|
||||
OverlayButton.Pressed = false;
|
||||
}
|
||||
|
||||
private void OverlayButtonPressed(ButtonEventArgs args)
|
||||
{
|
||||
if (args.Button.Pressed)
|
||||
{
|
||||
_adminSystem.AdminOverlayOn();
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminSystem.AdminOverlayOff();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void ShowDisconnectedPressed(ButtonEventArgs args)
|
||||
{
|
||||
_showDisconnected = args.Button.Pressed;
|
||||
RefreshPlayerList(_players);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_adminSystem.PlayerListChanged -= RefreshPlayerList;
|
||||
_adminSystem.OverlayEnabled -= OverlayEnabled;
|
||||
_adminSystem.OverlayDisabled -= OverlayDisabled;
|
||||
|
||||
OverlayButton.OnPressed -= OverlayButtonPressed;
|
||||
|
||||
ListHeader.OnHeaderClicked -= HeaderClicked;
|
||||
}
|
||||
}
|
||||
|
||||
#region ListContainer
|
||||
|
||||
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
|
||||
{
|
||||
_players = players;
|
||||
PlayerCount.Text = Loc.GetString("player-tab-player-count", ("count", _playerMan.PlayerCount));
|
||||
|
||||
var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList();
|
||||
|
||||
var sortedPlayers = new List<PlayerInfo>(filteredPlayers);
|
||||
sortedPlayers.Sort(Compare);
|
||||
|
||||
UpdateHeaderSymbols();
|
||||
|
||||
SearchList.PopulateList(sortedPlayers.Select(info => new PlayerListData(info,
|
||||
$"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}"))
|
||||
.ToList());
|
||||
}
|
||||
|
||||
private void GenerateButton(ListData data, ListContainerButton button)
|
||||
{
|
||||
if (data is not PlayerListData { Info: var player})
|
||||
return;
|
||||
|
||||
var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor));
|
||||
button.AddChild(entry);
|
||||
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.
|
||||
/// If all characters are lowercase, the comparison ignores case.
|
||||
/// If there is an uppercase character, the comparison is case sensitive.
|
||||
/// </summary>
|
||||
/// <param name="filter"></param>
|
||||
/// <param name="listData"></param>
|
||||
/// <returns>Whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.</returns>
|
||||
private bool DataFilterCondition(string filter, ListData listData)
|
||||
{
|
||||
if (listData is not PlayerListData {Info: var info, FilteringString: var playerString})
|
||||
return false;
|
||||
|
||||
if (!_showDisconnected && !info.Connected)
|
||||
return false;
|
||||
|
||||
if (IsAllLower(filter))
|
||||
{
|
||||
if (!playerString.Contains(filter, StringComparison.CurrentCultureIgnoreCase))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!playerString.Contains(filter))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsAllLower(string input)
|
||||
{
|
||||
foreach (var c in input)
|
||||
{
|
||||
if (char.IsLetter(c) && !char.IsLower(c))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Header
|
||||
|
||||
private void UpdateHeaderSymbols()
|
||||
{
|
||||
ListHeader.ResetHeaderText();
|
||||
ListHeader.GetHeader(_headerClicked).Text += $" {(_ascending ? ArrowUp : ArrowDown)}";
|
||||
}
|
||||
|
||||
private int Compare(PlayerInfo x, PlayerInfo y)
|
||||
{
|
||||
if (!_ascending)
|
||||
{
|
||||
(x, y) = (y, x);
|
||||
}
|
||||
|
||||
return _headerClicked switch
|
||||
{
|
||||
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.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
|
||||
private int Compare(string x, string y)
|
||||
{
|
||||
return string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private void HeaderClicked(Header header)
|
||||
{
|
||||
if (_headerClicked == header)
|
||||
{
|
||||
_ascending = !_ascending;
|
||||
}
|
||||
else
|
||||
{
|
||||
_headerClicked = header;
|
||||
_ascending = true;
|
||||
}
|
||||
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public record PlayerListData(PlayerInfo Info, string FilteringString) : ListData;
|
||||
|
||||
@@ -93,6 +93,6 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
|
||||
public void AlertClicked(ProtoId<AlertPrototype> alertType)
|
||||
{
|
||||
RaiseNetworkEvent(new ClickAlertEvent(alertType));
|
||||
RaisePredictiveEvent(new ClickAlertEvent(alertType));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public sealed partial class AnomalyScannerMenu : FancyWindow
|
||||
msg.PushNewline();
|
||||
var time = NextPulseTime.Value - _timing.CurTime;
|
||||
var timestring = $"{time.Minutes:00}:{time.Seconds:00}";
|
||||
msg.AddMarkup(Loc.GetString("anomaly-scanner-pulse-timer", ("time", timestring)));
|
||||
msg.AddMarkupOrThrow(Loc.GetString("anomaly-scanner-pulse-timer", ("time", timestring)));
|
||||
}
|
||||
|
||||
TextDisplay.SetMarkup(msg.ToMarkup());
|
||||
|
||||
10
Content.Client/Atmos/EntitySystems/GasMinerSystem.cs
Normal file
10
Content.Client/Atmos/EntitySystems/GasMinerSystem.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class GasMinerSystem : SharedGasMinerSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -16,6 +16,8 @@ namespace Content.Client.Atmos.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GasAnalyzerWindow : DefaultWindow
|
||||
{
|
||||
private NetEntity _currentEntity = NetEntity.Invalid;
|
||||
|
||||
public GasAnalyzerWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -55,6 +57,13 @@ namespace Content.Client.Atmos.UI
|
||||
// Device Tab
|
||||
if (msg.NodeGasMixes.Length > 1)
|
||||
{
|
||||
if (_currentEntity != msg.DeviceUid)
|
||||
{
|
||||
// when we get new device data switch to the device tab
|
||||
CTabContainer.CurrentTab = 0;
|
||||
_currentEntity = msg.DeviceUid;
|
||||
}
|
||||
|
||||
CTabContainer.SetTabVisible(0, true);
|
||||
CTabContainer.SetTabTitle(0, Loc.GetString("gas-analyzer-window-tab-title-capitalized", ("title", msg.DeviceName)));
|
||||
// Set up Grid
|
||||
@@ -143,6 +152,7 @@ namespace Content.Client.Atmos.UI
|
||||
CTabContainer.SetTabVisible(0, false);
|
||||
CTabContainer.CurrentTab = 1;
|
||||
minSize = new Vector2(CEnvironmentMix.DesiredSize.X + 40, MinSize.Y);
|
||||
_currentEntity = NetEntity.Invalid;
|
||||
}
|
||||
|
||||
MinSize = minSize;
|
||||
|
||||
@@ -57,7 +57,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
/// </summary>
|
||||
private int MaxSingleSound => (int) (_maxAmbientCount / (16.0f / 6.0f));
|
||||
|
||||
private readonly Dictionary<AmbientSoundComponent, (EntityUid? Stream, SoundSpecifier Sound, string Path)> _playingSounds = new();
|
||||
private readonly Dictionary<Entity<AmbientSoundComponent>, (EntityUid? Stream, SoundSpecifier Sound, string Path)> _playingSounds = new();
|
||||
private readonly Dictionary<string, int> _playingCount = new();
|
||||
|
||||
public bool OverlayEnabled
|
||||
@@ -107,7 +107,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
|
||||
private void OnShutdown(EntityUid uid, AmbientSoundComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (!_playingSounds.Remove(component, out var sound))
|
||||
if (!_playingSounds.Remove((uid, component), out var sound))
|
||||
return;
|
||||
|
||||
_audio.Stop(sound.Stream);
|
||||
@@ -120,13 +120,13 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
{
|
||||
_ambienceVolume = SharedAudioSystem.GainToVolume(value);
|
||||
|
||||
foreach (var (comp, values) in _playingSounds)
|
||||
foreach (var (ent, values) in _playingSounds)
|
||||
{
|
||||
if (values.Stream == null)
|
||||
continue;
|
||||
|
||||
var stream = values.Stream;
|
||||
_audio.SetVolume(stream, _params.Volume + comp.Volume + _ambienceVolume);
|
||||
_audio.SetVolume(stream, _params.Volume + ent.Comp.Volume + _ambienceVolume);
|
||||
}
|
||||
}
|
||||
private void SetCooldown(float value) => _cooldown = value;
|
||||
@@ -165,7 +165,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
if (_gameTiming.CurTime < _targetTime)
|
||||
return;
|
||||
|
||||
_targetTime = _gameTiming.CurTime+TimeSpan.FromSeconds(_cooldown);
|
||||
_targetTime = _gameTiming.CurTime + TimeSpan.FromSeconds(_cooldown);
|
||||
|
||||
var player = _playerManager.LocalEntity;
|
||||
if (!EntityManager.TryGetComponent(player, out TransformComponent? xform))
|
||||
@@ -190,7 +190,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
|
||||
private readonly struct QueryState
|
||||
{
|
||||
public readonly Dictionary<string, List<(float Importance, AmbientSoundComponent)>> SourceDict = new();
|
||||
public readonly Dictionary<string, List<(float Importance, Entity<AmbientSoundComponent>)>> SourceDict = new();
|
||||
public readonly Vector2 MapPos;
|
||||
public readonly TransformComponent Player;
|
||||
public readonly SharedTransformSystem TransformSystem;
|
||||
@@ -224,11 +224,11 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
if (ambientComp.Sound is SoundPathSpecifier path)
|
||||
key = path.Path.ToString();
|
||||
else
|
||||
key = ((SoundCollectionSpecifier) ambientComp.Sound).Collection ?? string.Empty;
|
||||
key = ((SoundCollectionSpecifier)ambientComp.Sound).Collection ?? string.Empty;
|
||||
|
||||
// Prioritize far away & loud sounds.
|
||||
var importance = range * (ambientComp.Volume + 32);
|
||||
state.SourceDict.GetOrNew(key).Add((importance, ambientComp));
|
||||
state.SourceDict.GetOrNew(key).Add((importance, (value.Uid, ambientComp)));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -242,16 +242,18 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
var mapPos = _xformSystem.GetMapCoordinates(playerXform);
|
||||
|
||||
// Remove out-of-range ambiences
|
||||
foreach (var (comp, sound) in _playingSounds)
|
||||
foreach (var (ent, sound) in _playingSounds)
|
||||
{
|
||||
var entity = comp.Owner;
|
||||
//var entity = comp.Owner;
|
||||
var owner = ent.Owner;
|
||||
var comp = ent.Comp;
|
||||
|
||||
if (comp.Enabled &&
|
||||
// Don't keep playing sounds that have changed since.
|
||||
sound.Sound == comp.Sound &&
|
||||
query.TryGetComponent(entity, out var xform) &&
|
||||
query.TryGetComponent(owner, out var xform) &&
|
||||
xform.MapID == playerXform.MapID &&
|
||||
!metaQuery.GetComponent(entity).EntityPaused)
|
||||
!metaQuery.GetComponent(owner).EntityPaused)
|
||||
{
|
||||
// TODO: This is just trydistance for coordinates.
|
||||
var distance = (xform.ParentUid == playerXform.ParentUid)
|
||||
@@ -263,7 +265,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
}
|
||||
|
||||
_audio.Stop(sound.Stream);
|
||||
_playingSounds.Remove(comp);
|
||||
_playingSounds.Remove(ent);
|
||||
_playingCount[sound.Path] -= 1;
|
||||
if (_playingCount[sound.Path] == 0)
|
||||
_playingCount.Remove(sound.Path);
|
||||
@@ -278,7 +280,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
_treeSys.QueryAabb(ref state, Callback, mapPos.MapId, worldAabb);
|
||||
|
||||
// Add in range ambiences
|
||||
foreach (var (key, sources) in state.SourceDict)
|
||||
foreach (var (key, sourceList) in state.SourceDict)
|
||||
{
|
||||
if (_playingSounds.Count >= _maxAmbientCount)
|
||||
break;
|
||||
@@ -286,13 +288,14 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
if (_playingCount.TryGetValue(key, out var playingCount) && playingCount >= MaxSingleSound)
|
||||
continue;
|
||||
|
||||
sources.Sort(static (a, b) => b.Importance.CompareTo(a.Importance));
|
||||
sourceList.Sort(static (a, b) => b.Importance.CompareTo(a.Importance));
|
||||
|
||||
foreach (var (_, comp) in sources)
|
||||
foreach (var (_, sourceEntity) in sourceList)
|
||||
{
|
||||
var uid = comp.Owner;
|
||||
var uid = sourceEntity.Owner;
|
||||
var comp = sourceEntity.Comp;
|
||||
|
||||
if (_playingSounds.ContainsKey(comp) ||
|
||||
if (_playingSounds.ContainsKey(sourceEntity) ||
|
||||
metaQuery.GetComponent(uid).EntityPaused)
|
||||
continue;
|
||||
|
||||
@@ -303,7 +306,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
.WithMaxDistance(comp.Range);
|
||||
|
||||
var stream = _audio.PlayEntity(comp.Sound, Filter.Local(), uid, false, audioParams);
|
||||
_playingSounds[comp] = (stream.Value.Entity, comp.Sound, key);
|
||||
_playingSounds[sourceEntity] = (stream.Value.Entity, comp.Sound, key);
|
||||
playingCount++;
|
||||
|
||||
if (_playingSounds.Count >= _maxAmbientCount)
|
||||
|
||||
@@ -5,71 +5,70 @@ using Content.Shared.Chat;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Chat.Managers
|
||||
namespace Content.Client.Chat.Managers;
|
||||
|
||||
internal sealed class ChatManager : IChatManager
|
||||
{
|
||||
internal sealed class ChatManager : IChatManager
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IClientAdminManager _adminMgr = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IClientAdminManager _adminMgr = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
_sawmill = Logger.GetSawmill("chat");
|
||||
_sawmill.Level = LogLevel.Info;
|
||||
}
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public void Initialize()
|
||||
public void SendMessage(string text, ChatSelectChannel channel)
|
||||
{
|
||||
var str = text.ToString();
|
||||
switch (channel)
|
||||
{
|
||||
_sawmill = Logger.GetSawmill("chat");
|
||||
_sawmill.Level = LogLevel.Info;
|
||||
}
|
||||
case ChatSelectChannel.Console:
|
||||
// run locally
|
||||
_consoleHost.ExecuteCommand(text);
|
||||
break;
|
||||
|
||||
public void SendMessage(string text, ChatSelectChannel channel)
|
||||
{
|
||||
var str = text.ToString();
|
||||
switch (channel)
|
||||
{
|
||||
case ChatSelectChannel.Console:
|
||||
// run locally
|
||||
_consoleHost.ExecuteCommand(text);
|
||||
break;
|
||||
case ChatSelectChannel.LOOC:
|
||||
_consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.LOOC:
|
||||
_consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
case ChatSelectChannel.OOC:
|
||||
_consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.OOC:
|
||||
_consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
case ChatSelectChannel.Admin:
|
||||
_consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Admin:
|
||||
_consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
case ChatSelectChannel.Emotes:
|
||||
_consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Emotes:
|
||||
_consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
case ChatSelectChannel.Dead:
|
||||
if (_systems.GetEntitySystemOrNull<GhostSystem>() is {IsGhost: true})
|
||||
goto case ChatSelectChannel.Local;
|
||||
|
||||
case ChatSelectChannel.Dead:
|
||||
if (_systems.GetEntitySystemOrNull<GhostSystem>() is {IsGhost: true})
|
||||
goto case ChatSelectChannel.Local;
|
||||
if (_adminMgr.HasFlag(AdminFlags.Admin))
|
||||
_consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(str)}\"");
|
||||
else
|
||||
_sawmill.Warning("Tried to speak on deadchat without being ghost or admin.");
|
||||
break;
|
||||
|
||||
if (_adminMgr.HasFlag(AdminFlags.Admin))
|
||||
_consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(str)}\"");
|
||||
else
|
||||
_sawmill.Warning("Tried to speak on deadchat without being ghost or admin.");
|
||||
break;
|
||||
// TODO sepearate radio and say into separate commands.
|
||||
case ChatSelectChannel.Radio:
|
||||
case ChatSelectChannel.Local:
|
||||
_consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
// TODO sepearate radio and say into separate commands.
|
||||
case ChatSelectChannel.Radio:
|
||||
case ChatSelectChannel.Local:
|
||||
_consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
case ChatSelectChannel.Whisper:
|
||||
_consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Whisper:
|
||||
_consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(channel), channel, null);
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(channel), channel, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Content.Client.Chat.UI
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] protected readonly IConfigurationManager ConfigManager = default!;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
|
||||
public enum SpeechType : byte
|
||||
{
|
||||
@@ -83,6 +84,7 @@ namespace Content.Client.Chat.UI
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_senderEntity = senderEntity;
|
||||
_transformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
|
||||
// Use text clipping so new messages don't overlap old ones being pushed up.
|
||||
RectClipContent = true;
|
||||
@@ -140,7 +142,7 @@ namespace Content.Client.Chat.UI
|
||||
}
|
||||
|
||||
var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -EntityVerticalOffset;
|
||||
var worldPos = xform.WorldPosition + offset;
|
||||
var worldPos = _transformSystem.GetWorldPosition(xform) + offset;
|
||||
|
||||
var lowerCenter = _eyeManager.WorldToScreen(worldPos) / UIScale;
|
||||
var screenPos = lowerCenter - new Vector2(ContentSize.X / 2, ContentSize.Y + _verticalOffsetAchieved);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.FixedPoint;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -9,11 +10,15 @@ namespace Content.Client.Chemistry.UI
|
||||
[UsedImplicitly]
|
||||
public sealed class TransferAmountBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private IEntityManager _entManager;
|
||||
private EntityUid _owner;
|
||||
[ViewVariables]
|
||||
private TransferAmountWindow? _window;
|
||||
|
||||
public TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
_owner = owner;
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
@@ -21,6 +26,9 @@ namespace Content.Client.Chemistry.UI
|
||||
base.Open();
|
||||
_window = this.CreateWindow<TransferAmountWindow>();
|
||||
|
||||
if (_entManager.TryGetComponent<SolutionTransferComponent>(_owner, out var comp))
|
||||
_window.SetBounds(comp.MinimumTransferAmount.Int(), comp.MaximumTransferAmount.Int());
|
||||
|
||||
_window.ApplyButton.OnPressed += _ =>
|
||||
{
|
||||
if (int.TryParse(_window.AmountLineEdit.Text, out var i))
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<LineEdit Name="AmountLineEdit" Access="Public" HorizontalExpand="True" PlaceHolder="{Loc 'ui-transfer-amount-line-edit-placeholder'}"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="MinimumAmount" Access="Public" HorizontalExpand="True" />
|
||||
<Label Name="MaximumAmount" Access="Public" />
|
||||
</BoxContainer>
|
||||
<Button Name="ApplyButton" Access="Public" Text="{Loc 'ui-transfer-amount-apply'}"/>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -8,9 +8,29 @@ namespace Content.Client.Chemistry.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class TransferAmountWindow : DefaultWindow
|
||||
{
|
||||
private int _max = Int32.MaxValue;
|
||||
private int _min = 1;
|
||||
|
||||
public TransferAmountWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
AmountLineEdit.OnTextChanged += OnValueChanged;
|
||||
}
|
||||
|
||||
public void SetBounds(int min, int max)
|
||||
{
|
||||
_min = min;
|
||||
_max = max;
|
||||
MinimumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-min", ("amount", _min));
|
||||
MaximumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-max", ("amount", _max));
|
||||
}
|
||||
|
||||
private void OnValueChanged(LineEdit.LineEditEventArgs args)
|
||||
{
|
||||
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount > _max || amount < _min)
|
||||
ApplyButton.Disabled = true;
|
||||
else
|
||||
ApplyButton.Disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ using Content.Client.Items.Systems;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Rounding;
|
||||
@@ -20,6 +22,7 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SolutionContainerVisualsComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SolutionContainerVisualsComponent, GetInhandVisualsEvent>(OnGetHeldVisuals);
|
||||
SubscribeLocalEvent<SolutionContainerVisualsComponent, GetEquipmentVisualsEvent>(OnGetClothingVisuals);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, SolutionContainerVisualsComponent component, MapInitEvent args)
|
||||
@@ -174,4 +177,41 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
|
||||
args.Layers.Add((key, layer));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetClothingVisuals(Entity<SolutionContainerVisualsComponent> ent, ref GetEquipmentVisualsEvent args)
|
||||
{
|
||||
if (ent.Comp.EquippedFillBaseName == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<AppearanceComponent>(ent, out var appearance))
|
||||
return;
|
||||
|
||||
if (!TryComp<ClothingComponent>(ent, out var clothing))
|
||||
return;
|
||||
|
||||
if (!AppearanceSystem.TryGetData<float>(ent, SolutionContainerVisuals.FillFraction, out var fraction, appearance))
|
||||
return;
|
||||
|
||||
var closestFillSprite = ContentHelpers.RoundToLevels(fraction, 1, ent.Comp.EquippedMaxFillLevels + 1);
|
||||
|
||||
if (closestFillSprite > 0)
|
||||
{
|
||||
var layer = new PrototypeLayerData();
|
||||
|
||||
var equippedPrefix = clothing.EquippedPrefix == null ? $"equipped-{args.Slot}" : $" {clothing.EquippedPrefix}-equipped-{args.Slot}";
|
||||
var key = equippedPrefix + ent.Comp.EquippedFillBaseName + closestFillSprite;
|
||||
|
||||
// Make sure the sprite state is valid so we don't show a big red error message
|
||||
// This saves us from having to make fill level sprites for every possible slot the item could be in (including pockets).
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite) || sprite.BaseRSI == null || !sprite.BaseRSI.TryGetState(key, out _))
|
||||
return;
|
||||
|
||||
layer.State = key;
|
||||
|
||||
if (ent.Comp.ChangeColor && AppearanceSystem.TryGetData<Color>(ent, SolutionContainerVisuals.Color, out var color, appearance))
|
||||
layer.Color = color;
|
||||
|
||||
args.Layers.Add((key, layer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,148 +1,17 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Graphics;
|
||||
using static Robust.Client.GameObjects.SpriteComponent;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
namespace Content.Client.Clickable;
|
||||
|
||||
namespace Content.Client.Clickable
|
||||
[RegisterComponent]
|
||||
public sealed partial class ClickableComponent : Component
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class ClickableComponent : Component
|
||||
[DataField] public DirBoundData? Bounds;
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class DirBoundData
|
||||
{
|
||||
[Dependency] private readonly IClickMapManager _clickMapManager = default!;
|
||||
|
||||
[DataField("bounds")] public DirBoundData? Bounds;
|
||||
|
||||
/// <summary>
|
||||
/// Used to check whether a click worked. Will first check if the click falls inside of some explicit bounding
|
||||
/// boxes (see <see cref="Bounds"/>). If that fails, attempts to use automatically generated click maps.
|
||||
/// </summary>
|
||||
/// <param name="worldPos">The world position that was clicked.</param>
|
||||
/// <param name="drawDepth">
|
||||
/// The draw depth for the sprite that captured the click.
|
||||
/// </param>
|
||||
/// <returns>True if the click worked, false otherwise.</returns>
|
||||
public bool CheckClick(SpriteComponent sprite, TransformComponent transform, EntityQuery<TransformComponent> xformQuery, Vector2 worldPos, IEye eye, out int drawDepth, out uint renderOrder, out float bottom)
|
||||
{
|
||||
if (!sprite.Visible)
|
||||
{
|
||||
drawDepth = default;
|
||||
renderOrder = default;
|
||||
bottom = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
drawDepth = sprite.DrawDepth;
|
||||
renderOrder = sprite.RenderOrder;
|
||||
var (spritePos, spriteRot) = transform.GetWorldPositionRotation(xformQuery);
|
||||
var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation);
|
||||
bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
|
||||
|
||||
Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix);
|
||||
|
||||
// This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites.
|
||||
var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive();
|
||||
|
||||
Angle cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero;
|
||||
|
||||
// First we get `localPos`, the clicked location in the sprite-coordinate frame.
|
||||
var entityXform = Matrix3Helpers.CreateInverseTransform(spritePos, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
|
||||
var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix);
|
||||
|
||||
// Check explicitly defined click-able bounds
|
||||
if (CheckDirBound(sprite, relativeRotation, localPos))
|
||||
return true;
|
||||
|
||||
// Next check each individual sprite layer using automatically computed click maps.
|
||||
foreach (var spriteLayer in sprite.AllLayers)
|
||||
{
|
||||
// TODO: Move this to a system and also use SpriteSystem.IsVisible instead.
|
||||
if (!spriteLayer.Visible || spriteLayer is not Layer layer || layer.CopyToShaderParameters != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check the layer's texture, if it has one
|
||||
if (layer.Texture != null)
|
||||
{
|
||||
// Convert to image coordinates
|
||||
var imagePos = (Vector2i) (localPos * EyeManager.PixelsPerMeter * new Vector2(1, -1) + layer.Texture.Size / 2f);
|
||||
|
||||
if (_clickMapManager.IsOccluding(layer.Texture, imagePos))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Either we weren't clicking on the texture, or there wasn't one. In which case: check the RSI next
|
||||
if (layer.ActualRsi is not { } rsi || !rsi.TryGetState(layer.State, out var rsiState))
|
||||
continue;
|
||||
|
||||
var dir = Layer.GetDirection(rsiState.RsiDirections, relativeRotation);
|
||||
|
||||
// convert to layer-local coordinates
|
||||
layer.GetLayerDrawMatrix(dir, out var matrix);
|
||||
Matrix3x2.Invert(matrix, out var inverseMatrix);
|
||||
var layerLocal = Vector2.Transform(localPos, inverseMatrix);
|
||||
|
||||
// Convert to image coordinates
|
||||
var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f);
|
||||
|
||||
// Next, to get the right click map we need the "direction" of this layer that is actually being used to draw the sprite on the screen.
|
||||
// This **can** differ from the dir defined before, but can also just be the same.
|
||||
if (sprite.EnableDirectionOverride)
|
||||
dir = sprite.DirectionOverride.Convert(rsiState.RsiDirections);
|
||||
dir = dir.OffsetRsiDir(layer.DirOffset);
|
||||
|
||||
if (_clickMapManager.IsOccluding(layer.ActualRsi!, layer.State, dir, layer.AnimationFrame, layerImagePos))
|
||||
return true;
|
||||
}
|
||||
|
||||
drawDepth = default;
|
||||
renderOrder = default;
|
||||
bottom = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CheckDirBound(SpriteComponent sprite, Angle relativeRotation, Vector2 localPos)
|
||||
{
|
||||
if (Bounds == null)
|
||||
return false;
|
||||
|
||||
// These explicit bounds only work for either 1 or 4 directional sprites.
|
||||
|
||||
// This would be the orientation of a 4-directional sprite.
|
||||
var direction = relativeRotation.GetCardinalDir();
|
||||
|
||||
var modLocalPos = sprite.NoRotation
|
||||
? localPos
|
||||
: direction.ToAngle().RotateVec(localPos);
|
||||
|
||||
// First, check the bounding box that is valid for all orientations
|
||||
if (Bounds.All.Contains(modLocalPos))
|
||||
return true;
|
||||
|
||||
// Next, get and check the appropriate bounding box for the current sprite orientation
|
||||
var boundsForDir = (sprite.EnableDirectionOverride ? sprite.DirectionOverride : direction) switch
|
||||
{
|
||||
Direction.East => Bounds.East,
|
||||
Direction.North => Bounds.North,
|
||||
Direction.South => Bounds.South,
|
||||
Direction.West => Bounds.West,
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
return boundsForDir.Contains(modLocalPos);
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class DirBoundData
|
||||
{
|
||||
[DataField("all")] public Box2 All;
|
||||
[DataField("north")] public Box2 North;
|
||||
[DataField("south")] public Box2 South;
|
||||
[DataField("east")] public Box2 East;
|
||||
[DataField("west")] public Box2 West;
|
||||
}
|
||||
[DataField] public Box2 All;
|
||||
[DataField] public Box2 North;
|
||||
[DataField] public Box2 South;
|
||||
[DataField] public Box2 East;
|
||||
[DataField] public Box2 West;
|
||||
}
|
||||
}
|
||||
|
||||
168
Content.Client/Clickable/ClickableSystem.cs
Normal file
168
Content.Client/Clickable/ClickableSystem.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Graphics;
|
||||
|
||||
namespace Content.Client.Clickable;
|
||||
|
||||
/// <summary>
|
||||
/// Handles click detection for sprites.
|
||||
/// </summary>
|
||||
public sealed class ClickableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IClickMapManager _clickMapManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transforms = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprites = default!;
|
||||
|
||||
private EntityQuery<ClickableComponent> _clickableQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_clickableQuery = GetEntityQuery<ClickableComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to check whether a click worked. Will first check if the click falls inside of some explicit bounding
|
||||
/// boxes (see <see cref="Bounds"/>). If that fails, attempts to use automatically generated click maps.
|
||||
/// </summary>
|
||||
/// <param name="worldPos">The world position that was clicked.</param>
|
||||
/// <param name="drawDepth">
|
||||
/// The draw depth for the sprite that captured the click.
|
||||
/// </param>
|
||||
/// <returns>True if the click worked, false otherwise.</returns>
|
||||
public bool CheckClick(Entity<ClickableComponent?, SpriteComponent, TransformComponent?> entity, Vector2 worldPos, IEye eye, out int drawDepth, out uint renderOrder, out float bottom)
|
||||
{
|
||||
if (!_clickableQuery.Resolve(entity.Owner, ref entity.Comp1, false))
|
||||
{
|
||||
drawDepth = default;
|
||||
renderOrder = default;
|
||||
bottom = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_xformQuery.Resolve(entity.Owner, ref entity.Comp3))
|
||||
{
|
||||
drawDepth = default;
|
||||
renderOrder = default;
|
||||
bottom = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var sprite = entity.Comp2;
|
||||
var transform = entity.Comp3;
|
||||
|
||||
if (!sprite.Visible)
|
||||
{
|
||||
drawDepth = default;
|
||||
renderOrder = default;
|
||||
bottom = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
drawDepth = sprite.DrawDepth;
|
||||
renderOrder = sprite.RenderOrder;
|
||||
var (spritePos, spriteRot) = _transforms.GetWorldPositionRotation(transform);
|
||||
var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation);
|
||||
bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
|
||||
|
||||
Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix);
|
||||
|
||||
// This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites.
|
||||
var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive();
|
||||
|
||||
var cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero;
|
||||
|
||||
// First we get `localPos`, the clicked location in the sprite-coordinate frame.
|
||||
var entityXform = Matrix3Helpers.CreateInverseTransform(spritePos, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
|
||||
var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix);
|
||||
|
||||
// Check explicitly defined click-able bounds
|
||||
if (CheckDirBound((entity.Owner, entity.Comp1, entity.Comp2), relativeRotation, localPos))
|
||||
return true;
|
||||
|
||||
// Next check each individual sprite layer using automatically computed click maps.
|
||||
foreach (var spriteLayer in sprite.AllLayers)
|
||||
{
|
||||
if (spriteLayer is not SpriteComponent.Layer layer || !_sprites.IsVisible(layer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check the layer's texture, if it has one
|
||||
if (layer.Texture != null)
|
||||
{
|
||||
// Convert to image coordinates
|
||||
var imagePos = (Vector2i) (localPos * EyeManager.PixelsPerMeter * new Vector2(1, -1) + layer.Texture.Size / 2f);
|
||||
|
||||
if (_clickMapManager.IsOccluding(layer.Texture, imagePos))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Either we weren't clicking on the texture, or there wasn't one. In which case: check the RSI next
|
||||
if (layer.ActualRsi is not { } rsi || !rsi.TryGetState(layer.State, out var rsiState))
|
||||
continue;
|
||||
|
||||
var dir = SpriteComponent.Layer.GetDirection(rsiState.RsiDirections, relativeRotation);
|
||||
|
||||
// convert to layer-local coordinates
|
||||
layer.GetLayerDrawMatrix(dir, out var matrix);
|
||||
Matrix3x2.Invert(matrix, out var inverseMatrix);
|
||||
var layerLocal = Vector2.Transform(localPos, inverseMatrix);
|
||||
|
||||
// Convert to image coordinates
|
||||
var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f);
|
||||
|
||||
// Next, to get the right click map we need the "direction" of this layer that is actually being used to draw the sprite on the screen.
|
||||
// This **can** differ from the dir defined before, but can also just be the same.
|
||||
if (sprite.EnableDirectionOverride)
|
||||
dir = sprite.DirectionOverride.Convert(rsiState.RsiDirections);
|
||||
dir = dir.OffsetRsiDir(layer.DirOffset);
|
||||
|
||||
if (_clickMapManager.IsOccluding(layer.ActualRsi!, layer.State, dir, layer.AnimationFrame, layerImagePos))
|
||||
return true;
|
||||
}
|
||||
|
||||
drawDepth = default;
|
||||
renderOrder = default;
|
||||
bottom = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CheckDirBound(Entity<ClickableComponent, SpriteComponent> entity, Angle relativeRotation, Vector2 localPos)
|
||||
{
|
||||
var clickable = entity.Comp1;
|
||||
var sprite = entity.Comp2;
|
||||
|
||||
if (clickable.Bounds == null)
|
||||
return false;
|
||||
|
||||
// These explicit bounds only work for either 1 or 4 directional sprites.
|
||||
|
||||
// This would be the orientation of a 4-directional sprite.
|
||||
var direction = relativeRotation.GetCardinalDir();
|
||||
|
||||
var modLocalPos = sprite.NoRotation
|
||||
? localPos
|
||||
: direction.ToAngle().RotateVec(localPos);
|
||||
|
||||
// First, check the bounding box that is valid for all orientations
|
||||
if (clickable.Bounds.All.Contains(modLocalPos))
|
||||
return true;
|
||||
|
||||
// Next, get and check the appropriate bounding box for the current sprite orientation
|
||||
var boundsForDir = (sprite.EnableDirectionOverride ? sprite.DirectionOverride : direction) switch
|
||||
{
|
||||
Direction.East => clickable.Bounds.East,
|
||||
Direction.North => clickable.Bounds.North,
|
||||
Direction.South => clickable.Bounds.South,
|
||||
Direction.West => clickable.Bounds.West,
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
return boundsForDir.Contains(modLocalPos);
|
||||
}
|
||||
}
|
||||
6
Content.Client/Clothing/Systems/CursedMaskSystem.cs
Normal file
6
Content.Client/Clothing/Systems/CursedMaskSystem.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using Content.Shared.Clothing;
|
||||
|
||||
namespace Content.Client.Clothing.Systems;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class CursedMaskSystem : SharedCursedMaskSystem;
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Mapping;
|
||||
using Content.Shared.Administration;
|
||||
@@ -61,27 +62,3 @@ public sealed class LoadActionsCommand : LocalizedCommands
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[AnyCommand]
|
||||
public sealed class LoadMappingActionsCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
public const string CommandName = "loadmapacts";
|
||||
|
||||
public override string Command => CommandName;
|
||||
|
||||
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
_entitySystemManager.GetEntitySystem<MappingSystem>().LoadMappingActions();
|
||||
}
|
||||
catch
|
||||
{
|
||||
shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed class HideMechanismsCommand : LocalizedCommands
|
||||
sprite.ContainerOccluded = false;
|
||||
|
||||
var tempParent = uid;
|
||||
while (containerSys.TryGetContainingContainer(tempParent, out var container))
|
||||
while (containerSys.TryGetContainingContainer((tempParent, null, null), out var container))
|
||||
{
|
||||
if (!container.ShowContents)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Mapping;
|
||||
using Content.Client.Markers;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Commands;
|
||||
@@ -10,6 +13,7 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly ILightManager _lightManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
|
||||
public override string Command => "mappingclientsidesetup";
|
||||
|
||||
@@ -21,8 +25,8 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
|
||||
{
|
||||
_entitySystemManager.GetEntitySystem<MarkerSystem>().MarkersVisible = true;
|
||||
_lightManager.Enabled = false;
|
||||
shell.ExecuteCommand(ShowSubFloorForever.CommandName);
|
||||
shell.ExecuteCommand(LoadMappingActionsCommand.CommandName);
|
||||
shell.ExecuteCommand("showsubfloorforever");
|
||||
_entitySystemManager.GetEntitySystem<ActionsSystem>().LoadActionAssignments("/mapping_actions.yml", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace Content.Client.Communications.UI
|
||||
|
||||
EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-recall-shuttle");
|
||||
var infoText = Loc.GetString($"comms-console-menu-time-remaining",
|
||||
("time", diff.TotalSeconds.ToString(CultureInfo.CurrentCulture)));
|
||||
("time", diff.ToString(@"hh\:mm\:ss", CultureInfo.CurrentCulture)));
|
||||
CountdownLabel.SetMessage(infoText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Numerics;
|
||||
using System.Threading;
|
||||
using Content.Client.CombatMode;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Mapping;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
@@ -16,7 +17,7 @@ namespace Content.Client.ContextMenu.UI
|
||||
/// <remarks>
|
||||
/// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements.
|
||||
/// </remarks>
|
||||
public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CombatModeSystem>
|
||||
public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CombatModeSystem>, IOnStateEntered<MappingState>, IOnStateExited<MappingState>
|
||||
{
|
||||
public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
|
||||
|
||||
@@ -42,18 +43,51 @@ namespace Content.Client.ContextMenu.UI
|
||||
public Action<ContextMenuElement>? OnSubMenuOpened;
|
||||
public Action<ContextMenuElement, GUIBoundKeyEventArgs>? OnContextKeyEvent;
|
||||
|
||||
private bool _setup;
|
||||
|
||||
public void OnStateEntered(GameplayState state)
|
||||
{
|
||||
Setup();
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
public void OnStateEntered(MappingState state)
|
||||
{
|
||||
Setup();
|
||||
}
|
||||
|
||||
public void OnStateExited(MappingState state)
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
if (_setup)
|
||||
return;
|
||||
|
||||
_setup = true;
|
||||
|
||||
RootMenu = new(this, null);
|
||||
RootMenu.OnPopupHide += Close;
|
||||
Menus.Push(RootMenu);
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
public void Shutdown()
|
||||
{
|
||||
if (!_setup)
|
||||
return;
|
||||
|
||||
_setup = false;
|
||||
|
||||
Close();
|
||||
RootMenu.OnPopupHide -= Close;
|
||||
RootMenu.Dispose();
|
||||
RootMenu = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -51,7 +51,7 @@ public sealed class CrewManifestSection : BoxContainer
|
||||
title.SetMessage(entry.JobTitle);
|
||||
|
||||
|
||||
if (prototypeManager.TryIndex<StatusIconPrototype>(entry.JobIcon, out var jobIcon))
|
||||
if (prototypeManager.TryIndex<JobIconPrototype>(entry.JobIcon, out var jobIcon))
|
||||
{
|
||||
var icon = new TextureRect()
|
||||
{
|
||||
|
||||
@@ -7,10 +7,12 @@ using Content.Shared.Security;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.CriminalRecords;
|
||||
|
||||
@@ -36,7 +38,6 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
public Action<SecurityStatus, string>? OnDialogConfirmed;
|
||||
|
||||
private uint _maxLength;
|
||||
private bool _isPopulating;
|
||||
private bool _access;
|
||||
private uint? _selectedKey;
|
||||
private CriminalRecord? _selectedRecord;
|
||||
@@ -74,7 +75,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
RecordListing.OnItemSelected += args =>
|
||||
{
|
||||
if (_isPopulating || RecordListing[args.ItemIndex].Metadata is not uint cast)
|
||||
if (RecordListing[args.ItemIndex].Metadata is not uint cast)
|
||||
return;
|
||||
|
||||
OnKeySelected?.Invoke(cast);
|
||||
@@ -82,8 +83,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
RecordListing.OnItemDeselected += _ =>
|
||||
{
|
||||
if (!_isPopulating)
|
||||
OnKeySelected?.Invoke(null);
|
||||
OnKeySelected?.Invoke(null);
|
||||
};
|
||||
|
||||
FilterType.OnItemSelected += eventArgs =>
|
||||
@@ -133,13 +133,8 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
FilterType.SelectId((int)_currentFilterType);
|
||||
|
||||
// set up the records listing panel
|
||||
RecordListing.Clear();
|
||||
|
||||
var hasRecords = state.RecordListing != null && state.RecordListing.Count > 0;
|
||||
NoRecords.Visible = !hasRecords;
|
||||
if (hasRecords)
|
||||
PopulateRecordListing(state.RecordListing!);
|
||||
NoRecords.Visible = state.RecordListing == null || state.RecordListing.Count == 0;
|
||||
PopulateRecordListing(state.RecordListing);
|
||||
|
||||
// set up the selected person's record
|
||||
var selected = _selectedKey != null;
|
||||
@@ -167,19 +162,59 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateRecordListing(Dictionary<uint, string> listing)
|
||||
private void PopulateRecordListing(Dictionary<uint, string>? listing)
|
||||
{
|
||||
_isPopulating = true;
|
||||
|
||||
foreach (var (key, name) in listing)
|
||||
if (listing == null)
|
||||
{
|
||||
var item = RecordListing.AddItem(name);
|
||||
item.Metadata = key;
|
||||
item.Selected = key == _selectedKey;
|
||||
RecordListing.Clear();
|
||||
return;
|
||||
}
|
||||
_isPopulating = false;
|
||||
|
||||
RecordListing.SortItemsByText();
|
||||
var entries = listing.ToList();
|
||||
entries.Sort((a, b) => string.Compare(a.Value, b.Value, StringComparison.Ordinal));
|
||||
// `entries` now contains the definitive list of items which should be in
|
||||
// our list of records and is in the order we want to present those items.
|
||||
|
||||
// Walk through the existing items in RecordListing and in the updated listing
|
||||
// in parallel to synchronize the items in RecordListing with `entries`.
|
||||
int i = RecordListing.Count - 1;
|
||||
int j = entries.Count - 1;
|
||||
while(i >= 0 && j >= 0)
|
||||
{
|
||||
var strcmp = string.Compare(RecordListing[i].Text, entries[j].Value, StringComparison.Ordinal);
|
||||
if (strcmp == 0)
|
||||
{
|
||||
// This item exists in both RecordListing and `entries`. Nothing to do.
|
||||
i--;
|
||||
j--;
|
||||
}
|
||||
else if (strcmp > 0)
|
||||
{
|
||||
// Item exists in RecordListing, but not in `entries`. Remove it.
|
||||
RecordListing.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
else if (strcmp < 0)
|
||||
{
|
||||
// A new entry which doesn't exist in RecordListing. Create it.
|
||||
RecordListing.Insert(i + 1, new ItemList.Item(RecordListing){Text = entries[j].Value, Metadata = entries[j].Key});
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
// Any remaining items in RecordListing don't exist in `entries`, so remove them
|
||||
while (i >= 0)
|
||||
{
|
||||
RecordListing.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
|
||||
// And finally, any remaining items in `entries`, don't exist in RecordListing. Create them.
|
||||
while (j >= 0)
|
||||
{
|
||||
RecordListing.Insert(0, new ItemList.Item(RecordListing){Text = entries[j].Value, Metadata = entries[j].Key});
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord)
|
||||
@@ -211,10 +246,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
private void FilterListingOfRecords(string text = "")
|
||||
{
|
||||
if (!_isPopulating)
|
||||
{
|
||||
OnFiltersChanged?.Invoke(_currentFilterType, text);
|
||||
}
|
||||
OnFiltersChanged?.Invoke(_currentFilterType, text);
|
||||
}
|
||||
|
||||
private void SetStatus(SecurityStatus status)
|
||||
|
||||
@@ -4,6 +4,7 @@ using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Decals.Overlays;
|
||||
|
||||
@@ -16,7 +17,7 @@ public sealed class DecalPlacementOverlay : Overlay
|
||||
private readonly SharedTransformSystem _transform;
|
||||
private readonly SpriteSystem _sprite;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities;
|
||||
|
||||
public DecalPlacementOverlay(DecalPlacementSystem placement, SharedTransformSystem transform, SpriteSystem sprite)
|
||||
{
|
||||
@@ -24,6 +25,7 @@ public sealed class DecalPlacementOverlay : Overlay
|
||||
_placement = placement;
|
||||
_transform = transform;
|
||||
_sprite = sprite;
|
||||
ZIndex = 1000;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
@@ -55,7 +57,7 @@ public sealed class DecalPlacementOverlay : Overlay
|
||||
|
||||
if (snap)
|
||||
{
|
||||
localPos = (Vector2) localPos.Floored() + grid.TileSizeHalfVector;
|
||||
localPos = localPos.Floored() + grid.TileSizeHalfVector;
|
||||
}
|
||||
|
||||
// Nothing uses snap cardinals so probably don't need preview?
|
||||
|
||||
80
Content.Client/Drowsiness/DrowsinessOverlay.cs
Normal file
80
Content.Client/Drowsiness/DrowsinessOverlay.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using Content.Shared.Drowsiness;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Drowsiness;
|
||||
|
||||
public sealed class DrowsinessOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
public override bool RequestScreenTexture => true;
|
||||
private readonly ShaderInstance _drowsinessShader;
|
||||
|
||||
public float CurrentPower = 0.0f;
|
||||
|
||||
private const float PowerDivisor = 250.0f;
|
||||
private const float Intensity = 0.2f; // for adjusting the visual scale
|
||||
private float _visualScale = 0; // between 0 and 1
|
||||
|
||||
public DrowsinessOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_drowsinessShader = _prototypeManager.Index<ShaderPrototype>("Drowsiness").InstanceUnique();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
var playerEntity = _playerManager.LocalEntity;
|
||||
|
||||
if (playerEntity == null)
|
||||
return;
|
||||
|
||||
if (!_entityManager.HasComponent<DrowsinessComponent>(playerEntity)
|
||||
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
|
||||
return;
|
||||
|
||||
var statusSys = _sysMan.GetEntitySystem<StatusEffectsSystem>();
|
||||
if (!statusSys.TryGetTime(playerEntity.Value, SharedDrowsinessSystem.DrowsinessKey, out var time, status))
|
||||
return;
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
var timeLeft = (float)(time.Value.Item2 - curTime).TotalSeconds;
|
||||
|
||||
CurrentPower += 8f * (0.5f * timeLeft - CurrentPower) * args.DeltaSeconds / (timeLeft + 1);
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
|
||||
return false;
|
||||
|
||||
if (args.Viewport.Eye != eyeComp.Eye)
|
||||
return false;
|
||||
|
||||
_visualScale = Math.Clamp(CurrentPower / PowerDivisor, 0.0f, 1.0f);
|
||||
return _visualScale > 0;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (ScreenTexture == null)
|
||||
return;
|
||||
|
||||
var handle = args.WorldHandle;
|
||||
_drowsinessShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
_drowsinessShader.SetParameter("Strength", _visualScale * Intensity);
|
||||
handle.UseShader(_drowsinessShader);
|
||||
handle.DrawRect(args.WorldBounds, Color.White);
|
||||
handle.UseShader(null);
|
||||
}
|
||||
}
|
||||
53
Content.Client/Drowsiness/DrowsinessSystem.cs
Normal file
53
Content.Client/Drowsiness/DrowsinessSystem.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Content.Shared.Drowsiness;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.Drowsiness;
|
||||
|
||||
public sealed class DrowsinessSystem : SharedDrowsinessSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
private DrowsinessOverlay _overlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DrowsinessComponent, ComponentInit>(OnDrowsinessInit);
|
||||
SubscribeLocalEvent<DrowsinessComponent, ComponentShutdown>(OnDrowsinessShutdown);
|
||||
|
||||
SubscribeLocalEvent<DrowsinessComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<DrowsinessComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
_overlay = new();
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(EntityUid uid, DrowsinessComponent component, LocalPlayerAttachedEvent args)
|
||||
{
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(EntityUid uid, DrowsinessComponent component, LocalPlayerDetachedEvent args)
|
||||
{
|
||||
_overlay.CurrentPower = 0;
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnDrowsinessInit(EntityUid uid, DrowsinessComponent component, ComponentInit args)
|
||||
{
|
||||
if (_player.LocalEntity == uid)
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnDrowsinessShutdown(EntityUid uid, DrowsinessComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (_player.LocalEntity == uid)
|
||||
{
|
||||
_overlay.CurrentPower = 0;
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,19 @@ public sealed class ExplosionOverlay : Overlay
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
private SharedAppearanceSystem _appearance;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||
|
||||
private ShaderInstance _shader;
|
||||
|
||||
public ExplosionOverlay()
|
||||
public ExplosionOverlay(SharedAppearanceSystem appearanceSystem)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _proto.Index<ShaderPrototype>("unshaded").Instance();
|
||||
_transformSystem = _entMan.System<SharedTransformSystem>();
|
||||
_appearance = appearanceSystem;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
@@ -33,15 +37,14 @@ public sealed class ExplosionOverlay : Overlay
|
||||
drawHandle.UseShader(_shader);
|
||||
|
||||
var xforms = _entMan.GetEntityQuery<TransformComponent>();
|
||||
var query = _entMan
|
||||
.EntityQuery<ExplosionVisualsComponent, ExplosionVisualsTexturesComponent, AppearanceComponent>(true);
|
||||
var query = _entMan.EntityQueryEnumerator<ExplosionVisualsComponent, ExplosionVisualsTexturesComponent>();
|
||||
|
||||
foreach (var (visuals, textures, appearance) in query)
|
||||
while (query.MoveNext(out var uid, out var visuals, out var textures))
|
||||
{
|
||||
if (visuals.Epicenter.MapId != args.MapId)
|
||||
continue;
|
||||
|
||||
if (!appearance.TryGetData(ExplosionAppearanceData.Progress, out int index))
|
||||
if (!_appearance.TryGetData(uid, ExplosionAppearanceData.Progress, out int index))
|
||||
continue;
|
||||
|
||||
index = Math.Min(index, visuals.Intensity.Count - 1);
|
||||
@@ -67,7 +70,7 @@ public sealed class ExplosionOverlay : Overlay
|
||||
continue;
|
||||
|
||||
var xform = xforms.GetComponent(gridId);
|
||||
var (_, _, worldMatrix, invWorldMatrix) = xform.GetWorldPositionRotationMatrixWithInv(xforms);
|
||||
var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(xform, xforms);
|
||||
|
||||
gridBounds = invWorldMatrix.TransformBox(worldBounds).Enlarged(grid.TileSize * 2);
|
||||
drawHandle.SetTransform(worldMatrix);
|
||||
|
||||
@@ -20,6 +20,7 @@ public sealed class ExplosionOverlaySystem : EntitySystem
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _lights = default!;
|
||||
[Dependency] private readonly IMapManager _mapMan = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -28,7 +29,7 @@ public sealed class ExplosionOverlaySystem : EntitySystem
|
||||
SubscribeLocalEvent<ExplosionVisualsComponent, ComponentInit>(OnExplosionInit);
|
||||
SubscribeLocalEvent<ExplosionVisualsComponent, ComponentRemove>(OnCompRemove);
|
||||
SubscribeLocalEvent<ExplosionVisualsComponent, ComponentHandleState>(OnExplosionHandleState);
|
||||
_overlayMan.AddOverlay(new ExplosionOverlay());
|
||||
_overlayMan.AddOverlay(new ExplosionOverlay(_appearance));
|
||||
}
|
||||
|
||||
private void OnExplosionHandleState(EntityUid uid, ExplosionVisualsComponent component, ref ComponentHandleState args)
|
||||
|
||||
8
Content.Client/Explosion/ExplosionSystem.cs
Normal file
8
Content.Client/Explosion/ExplosionSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Explosion.EntitySystems;
|
||||
|
||||
namespace Content.Client.Explosion.EntitySystems;
|
||||
|
||||
public sealed class ExplosionSystem : SharedExplosionSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -14,6 +14,7 @@ public sealed class PuddleOverlay : Overlay
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
private readonly PuddleDebugOverlaySystem _debugOverlaySystem;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
|
||||
private readonly Color _heavyPuddle = new(0, 255, 255, 50);
|
||||
private readonly Color _mediumPuddle = new(0, 150, 255, 50);
|
||||
@@ -29,6 +30,7 @@ public sealed class PuddleOverlay : Overlay
|
||||
_debugOverlaySystem = _entitySystemManager.GetEntitySystem<PuddleDebugOverlaySystem>();
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 8);
|
||||
_transformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
@@ -56,7 +58,7 @@ public sealed class PuddleOverlay : Overlay
|
||||
continue;
|
||||
|
||||
var gridXform = xformQuery.GetComponent(gridId);
|
||||
var (_, _, worldMatrix, invWorldMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv(xformQuery);
|
||||
var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform, xformQuery);
|
||||
gridBounds = invWorldMatrix.TransformBox(args.WorldBounds).Enlarged(mapGrid.TileSize * 2);
|
||||
drawHandle.SetTransform(worldMatrix);
|
||||
|
||||
@@ -89,7 +91,7 @@ public sealed class PuddleOverlay : Overlay
|
||||
continue;
|
||||
|
||||
var gridXform = xformQuery.GetComponent(gridId);
|
||||
var (_, _, matrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv(xformQuery);
|
||||
var (_, _, matrix, invMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform, xformQuery);
|
||||
var gridBounds = invMatrix.TransformBox(args.WorldBounds).Enlarged(mapGrid.TileSize * 2);
|
||||
|
||||
foreach (var debugOverlayData in _debugOverlaySystem.GetData(gridId))
|
||||
|
||||
@@ -54,10 +54,16 @@ namespace Content.Client.Forensics
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-dnas"));
|
||||
foreach (var dna in msg.DNAs)
|
||||
foreach (var dna in msg.TouchDNAs)
|
||||
{
|
||||
text.AppendLine(dna);
|
||||
}
|
||||
foreach (var dna in msg.SolutionDNAs)
|
||||
{
|
||||
if (msg.TouchDNAs.Contains(dna))
|
||||
continue;
|
||||
text.AppendLine(dna);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-residues"));
|
||||
foreach (var residue in msg.Residues)
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Clickable;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.Viewport;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.ComponentTrees;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -13,11 +14,13 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using YamlDotNet.Serialization.TypeInspectors;
|
||||
|
||||
namespace Content.Client.Gameplay
|
||||
{
|
||||
@@ -98,7 +101,15 @@ namespace Content.Client.Gameplay
|
||||
|
||||
public EntityUid? GetClickedEntity(MapCoordinates coordinates)
|
||||
{
|
||||
var first = GetClickableEntities(coordinates).FirstOrDefault();
|
||||
return GetClickedEntity(coordinates, _eyeManager.CurrentEye);
|
||||
}
|
||||
|
||||
public EntityUid? GetClickedEntity(MapCoordinates coordinates, IEye? eye)
|
||||
{
|
||||
if (eye == null)
|
||||
return null;
|
||||
|
||||
var first = GetClickableEntities(coordinates, eye).FirstOrDefault();
|
||||
return first.IsValid() ? first : null;
|
||||
}
|
||||
|
||||
@@ -110,6 +121,20 @@ namespace Content.Client.Gameplay
|
||||
|
||||
public IEnumerable<EntityUid> GetClickableEntities(MapCoordinates coordinates)
|
||||
{
|
||||
return GetClickableEntities(coordinates, _eyeManager.CurrentEye);
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetClickableEntities(MapCoordinates coordinates, IEye? eye)
|
||||
{
|
||||
/*
|
||||
* TODO:
|
||||
* 1. Stuff like MeleeWeaponSystem need an easy way to hook into viewport specific entities / entities under mouse
|
||||
* 2. Cleanup the mess around InteractionOutlineSystem + below the keybind click detection.
|
||||
*/
|
||||
|
||||
if (eye == null)
|
||||
return Array.Empty<EntityUid>();
|
||||
|
||||
// Find all the entities intersecting our click
|
||||
var spriteTree = _entityManager.EntitySysManager.GetEntitySystem<SpriteTreeSystem>();
|
||||
var entities = spriteTree.QueryAabb(coordinates.MapId, Box2.CenteredAround(coordinates.Position, new Vector2(1, 1)));
|
||||
@@ -117,15 +142,12 @@ namespace Content.Client.Gameplay
|
||||
// Check the entities against whether or not we can click them
|
||||
var foundEntities = new List<(EntityUid, int, uint, float)>(entities.Count);
|
||||
var clickQuery = _entityManager.GetEntityQuery<ClickableComponent>();
|
||||
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
// TODO: Smelly
|
||||
var eye = _eyeManager.CurrentEye;
|
||||
var clickables = _entityManager.System<ClickableSystem>();
|
||||
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (clickQuery.TryGetComponent(entity.Uid, out var component) &&
|
||||
component.CheckClick(entity.Component, entity.Transform, xformQuery, coordinates.Position, eye, out var drawDepthClicked, out var renderOrder, out var bottom))
|
||||
clickables.CheckClick((entity.Uid, component, entity.Component, entity.Transform), coordinates.Position, eye, out var drawDepthClicked, out var renderOrder, out var bottom))
|
||||
{
|
||||
foundEntities.Add((entity.Uid, drawDepthClicked, renderOrder, bottom));
|
||||
}
|
||||
@@ -188,7 +210,15 @@ namespace Content.Client.Gameplay
|
||||
if (args.Viewport is IViewportControl vp && kArgs.PointerLocation.IsValid)
|
||||
{
|
||||
var mousePosWorld = vp.PixelToMap(kArgs.PointerLocation.Position);
|
||||
entityToClick = GetClickedEntity(mousePosWorld);
|
||||
|
||||
if (vp is ScalingViewport svp)
|
||||
{
|
||||
entityToClick = GetClickedEntity(mousePosWorld, svp.Eye);
|
||||
}
|
||||
else
|
||||
{
|
||||
entityToClick = GetClickedEntity(mousePosWorld);
|
||||
}
|
||||
|
||||
coordinates = _mapManager.TryFindGridAt(mousePosWorld, out _, out var grid) ?
|
||||
grid.MapToGrid(mousePosWorld) :
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class GhostRoleRadioBoundUserInterface : BoundUserInterface
|
||||
_ghostRoleRadioMenu.SendGhostRoleRadioMessageAction += SendGhostRoleRadioMessage;
|
||||
}
|
||||
|
||||
public void SendGhostRoleRadioMessage(ProtoId<GhostRolePrototype> protoId)
|
||||
private void SendGhostRoleRadioMessage(ProtoId<GhostRolePrototype> protoId)
|
||||
{
|
||||
SendMessage(new GhostRoleRadioMessage(protoId));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Ghost.Roles;
|
||||
using Content.Shared.Ghost.Roles.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -33,7 +32,7 @@ public sealed partial class GhostRoleRadioMenu : RadialMenu
|
||||
|
||||
private void RefreshUI()
|
||||
{
|
||||
// The main control that will contain all of the clickable options
|
||||
// The main control that will contain all the clickable options
|
||||
var main = FindControl<RadialContainer>("Main");
|
||||
|
||||
// The purpose of this radial UI is for ghost role radios that allow you to select
|
||||
@@ -70,7 +69,7 @@ public sealed partial class GhostRoleRadioMenu : RadialMenu
|
||||
if (_prototypeManager.TryIndex(ghostRoleProto.IconPrototype, out var iconProto))
|
||||
entProtoView.SetPrototype(iconProto);
|
||||
else
|
||||
entProtoView.SetPrototype(comp.Prototype);
|
||||
entProtoView.SetPrototype(ghostRoleProto.EntityPrototype);
|
||||
|
||||
button.AddChild(entProtoView);
|
||||
main.AddChild(button);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalExpand="True"
|
||||
@@ -26,5 +27,18 @@
|
||||
VerticalAlignment="Center"
|
||||
Access="Public"
|
||||
Visible="False"/>
|
||||
<!-- CP14 random reactions begin -->
|
||||
<BoxContainer Name="RandomVariations"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left">
|
||||
<controls:SplitBar MinHeight="10">
|
||||
</controls:SplitBar>
|
||||
<Label Name="RandomVariationsLabel" Text="{Loc 'cp14-guidebook-random-variations-title'}" Visible="False"/>
|
||||
<controls:SplitBar MinHeight="10">
|
||||
</controls:SplitBar>
|
||||
</BoxContainer>
|
||||
<!-- CP14 random reactions end -->
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.UserInterface.Controls; // CP14 random reactions
|
||||
using Content.Client.UserInterface.ControlExtensions;
|
||||
using Content.Shared.Atmos.Prototypes;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
@@ -37,13 +38,36 @@ public sealed partial class GuideReagentReaction : BoxContainer, ISearchableCont
|
||||
var reactantsLabel = ReactantsLabel;
|
||||
SetReagents(prototype.Reactants, ref reactantsLabel, protoMan);
|
||||
var productLabel = ProductsLabel;
|
||||
var products = new Dictionary<string, FixedPoint2>(prototype.Products);
|
||||
var products = new Dictionary<string, FixedPoint2>(prototype._products); // CP14 random reactions
|
||||
foreach (var (reagent, reactantProto) in prototype.Reactants)
|
||||
{
|
||||
if (reactantProto.Catalyst)
|
||||
products.Add(reagent, reactantProto.Amount);
|
||||
}
|
||||
SetReagents(products, ref productLabel, protoMan);
|
||||
// CP14 random reagents begin
|
||||
foreach (var randomVariation in prototype.Cp14RandomProducts)
|
||||
{
|
||||
// If there aren't any variations, this label will be not visible
|
||||
RandomVariationsLabel.Visible = true;
|
||||
var randomProductLabel = new RichTextLabel {
|
||||
HorizontalAlignment=HAlignment.Left,
|
||||
VerticalAlignment=VAlignment.Center,
|
||||
};
|
||||
var randomProducts = new Dictionary<string, FixedPoint2>(randomVariation);
|
||||
RandomVariations.AddChild(randomProductLabel);
|
||||
RandomVariations.AddChild(new SplitBar
|
||||
{
|
||||
MinHeight = 10,
|
||||
});
|
||||
foreach (var (reagent, reactantProto) in prototype.Reactants)
|
||||
{
|
||||
if (reactantProto.Catalyst)
|
||||
randomProducts.Add(reagent, reactantProto.Amount);
|
||||
}
|
||||
SetReagents(randomProducts, ref randomProductLabel, protoMan);
|
||||
}
|
||||
// CP14 random reagents end
|
||||
|
||||
var mixingCategories = new List<MixingCategoryPrototype>();
|
||||
if (prototype.MixingCategories != null)
|
||||
|
||||
41
Content.Client/Guidebook/Controls/GuidebookError.xaml
Normal file
41
Content.Client/Guidebook/Controls/GuidebookError.xaml
Normal file
@@ -0,0 +1,41 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Margin="5 5 5 5"
|
||||
MinHeight="200">
|
||||
<PanelContainer HorizontalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BorderThickness="2" BorderColor="White" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BorderThickness="0 0 0 1" BackgroundColor="DarkRed" BorderColor="Black" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<Label Margin="5" StyleClasses="bold" Text="{Loc 'guidebook-parser-error'}" />
|
||||
</PanelContainer>
|
||||
|
||||
<OutputPanel Margin="5" MinHeight="75" VerticalExpand="True" Name="Original">
|
||||
<OutputPanel.StyleBoxOverride>
|
||||
<gfx:StyleBoxFlat BorderThickness="0 0 0 1" BorderColor="Gray"
|
||||
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"
|
||||
ContentMarginBottomOverride="3" ContentMarginTopOverride="3" />
|
||||
</OutputPanel.StyleBoxOverride>
|
||||
</OutputPanel>
|
||||
|
||||
<Collapsible Margin="5" MinHeight="75" VerticalExpand="True">
|
||||
<CollapsibleHeading Title="{Loc 'guidebook-error-message' }" />
|
||||
<CollapsibleBody VerticalExpand="True">
|
||||
<OutputPanel Name="Error" VerticalExpand="True" MinHeight="100">
|
||||
<OutputPanel.StyleBoxOverride>
|
||||
<gfx:StyleBoxFlat
|
||||
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"
|
||||
ContentMarginBottomOverride="3" ContentMarginTopOverride="3" />
|
||||
</OutputPanel.StyleBoxOverride>
|
||||
</OutputPanel>
|
||||
</CollapsibleBody>
|
||||
</Collapsible>
|
||||
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
23
Content.Client/Guidebook/Controls/GuidebookError.xaml.cs
Normal file
23
Content.Client/Guidebook/Controls/GuidebookError.xaml.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Guidebook.Controls;
|
||||
|
||||
[UsedImplicitly] [GenerateTypedNameReferences]
|
||||
public sealed partial class GuidebookError : BoxContainer
|
||||
{
|
||||
public GuidebookError()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public GuidebookError(string original, string? error) : this()
|
||||
{
|
||||
Original.AddText(original);
|
||||
|
||||
if (error is not null)
|
||||
Error.AddText(error);
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,10 @@ using Content.Client.UserInterface.ControlExtensions;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Controls.FancyTree;
|
||||
using Content.Client.UserInterface.Systems.Info;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -18,15 +16,18 @@ namespace Content.Client.Guidebook.Controls;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
|
||||
private Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> _entries = new();
|
||||
|
||||
private readonly ISawmill _sawmill;
|
||||
|
||||
public GuidebookWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_sawmill = Logger.GetSawmill("Guidebook");
|
||||
|
||||
Tree.OnSelectedItemChanged += OnSelectionChanged;
|
||||
|
||||
@@ -36,6 +37,20 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
};
|
||||
}
|
||||
|
||||
public void HandleClick(string link)
|
||||
{
|
||||
if (!_entries.TryGetValue(link, out var entry))
|
||||
return;
|
||||
|
||||
if (Tree.TryGetIndexFromMetadata(entry, out var index))
|
||||
{
|
||||
Tree.ExpandParentEntries(index.Value);
|
||||
Tree.SetSelectedIndex(index);
|
||||
}
|
||||
else
|
||||
ShowGuide(entry);
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(TreeItem? item)
|
||||
{
|
||||
if (item != null && item.Metadata is GuideEntry entry)
|
||||
@@ -71,8 +86,9 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
|
||||
if (!_parsingMan.TryAddMarkup(EntryContainer, file.ReadToEnd()))
|
||||
{
|
||||
EntryContainer.AddChild(new Label() { Text = "ERROR: Failed to parse document." });
|
||||
Logger.Error($"Failed to parse contents of guide document {entry.Id}.");
|
||||
// The guidebook will automatically display the in-guidebook error if it fails
|
||||
|
||||
_sawmill.Error($"Failed to parse contents of guide document {entry.Id}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +140,10 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
|
||||
entry.Children = sortedChildren;
|
||||
}
|
||||
|
||||
entries.ExceptWith(entry.Children);
|
||||
}
|
||||
|
||||
rootEntries = entries.ToList();
|
||||
}
|
||||
|
||||
@@ -135,22 +153,26 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
.ThenBy(rootEntry => Loc.GetString(rootEntry.Name));
|
||||
}
|
||||
|
||||
private void RepopulateTree(List<ProtoId<GuideEntryPrototype>>? roots = null, ProtoId<GuideEntryPrototype>? forcedRoot = null)
|
||||
private void RepopulateTree(List<ProtoId<GuideEntryPrototype>>? roots = null,
|
||||
ProtoId<GuideEntryPrototype>? forcedRoot = null)
|
||||
{
|
||||
Tree.Clear();
|
||||
|
||||
HashSet<ProtoId<GuideEntryPrototype>> addedEntries = new();
|
||||
|
||||
TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
|
||||
var parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
|
||||
foreach (var entry in GetSortedEntries(roots))
|
||||
{
|
||||
if (!entry.CrystallPunkAllowed) continue; //CrystallPunk guidebook filter
|
||||
AddEntry(entry.Id, parent, addedEntries);
|
||||
}
|
||||
|
||||
Tree.SetAllExpanded(true);
|
||||
}
|
||||
|
||||
private TreeItem? AddEntry(ProtoId<GuideEntryPrototype> id, TreeItem? parent, HashSet<ProtoId<GuideEntryPrototype>> addedEntries)
|
||||
private TreeItem? AddEntry(ProtoId<GuideEntryPrototype> id,
|
||||
TreeItem? parent,
|
||||
HashSet<ProtoId<GuideEntryPrototype>> addedEntries)
|
||||
{
|
||||
if (!_entries.TryGetValue(id, out var entry))
|
||||
return null;
|
||||
@@ -180,22 +202,6 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
return item;
|
||||
}
|
||||
|
||||
public void HandleClick(string link)
|
||||
{
|
||||
if (!_entries.TryGetValue(link, out var entry))
|
||||
return;
|
||||
|
||||
if (Tree.TryGetIndexFromMetadata(entry, out var index))
|
||||
{
|
||||
Tree.ExpandParentEntries(index.Value);
|
||||
Tree.SetSelectedIndex(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowGuide(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleFilter()
|
||||
{
|
||||
var emptySearch = SearchBar.Text.Trim().Length == 0;
|
||||
@@ -209,6 +215,5 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
element.SetHiddenState(true, SearchBar.Text.Trim());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Guidebook.Controls;
|
||||
using Content.Client.Guidebook.Richtext;
|
||||
using Content.Shared.Guidebook;
|
||||
using Pidgin;
|
||||
@@ -7,6 +8,7 @@ using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Sandboxing;
|
||||
using Robust.Shared.Utility;
|
||||
using static Pidgin.Parser;
|
||||
|
||||
namespace Content.Client.Guidebook;
|
||||
@@ -22,8 +24,10 @@ public sealed partial class DocumentParsingManager
|
||||
[Dependency] private readonly ISandboxHelper _sandboxHelper = default!;
|
||||
|
||||
private readonly Dictionary<string, Parser<char, Control>> _tagControlParsers = new();
|
||||
private Parser<char, Control> _tagParser = default!;
|
||||
private Parser<char, Control> _controlParser = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
private Parser<char, Control> _tagParser = default!;
|
||||
public Parser<char, IEnumerable<Control>> ControlParser = default!;
|
||||
|
||||
public void Initialize()
|
||||
@@ -32,7 +36,8 @@ public sealed partial class DocumentParsingManager
|
||||
.Assert(_tagControlParsers.ContainsKey, tag => $"unknown tag: {tag}")
|
||||
.Bind(tag => _tagControlParsers[tag]);
|
||||
|
||||
_controlParser = OneOf(_tagParser, TryHeaderControl, ListControlParser, TextControlParser).Before(SkipWhitespaces);
|
||||
_controlParser = OneOf(_tagParser, TryHeaderControl, ListControlParser, TextControlParser)
|
||||
.Before(SkipWhitespaces);
|
||||
|
||||
foreach (var typ in _reflectionManager.GetAllChildren<IDocumentTag>())
|
||||
{
|
||||
@@ -40,6 +45,8 @@ public sealed partial class DocumentParsingManager
|
||||
}
|
||||
|
||||
ControlParser = SkipWhitespaces.Then(_controlParser.Many());
|
||||
|
||||
_sawmill = Logger.GetSawmill("Guidebook");
|
||||
}
|
||||
|
||||
public bool TryAddMarkup(Control control, ProtoId<GuideEntryPrototype> entryId, bool log = true)
|
||||
@@ -68,37 +75,57 @@ public sealed partial class DocumentParsingManager
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (log)
|
||||
Logger.Error($"Encountered error while generating markup controls: {e}");
|
||||
_sawmill.Error($"Encountered error while generating markup controls: {e}");
|
||||
|
||||
control.AddChild(new GuidebookError(text, e.ToStringBetter()));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Parser<char, Control> CreateTagControlParser(string tagId, Type tagType, ISandboxHelper sandbox) => Map(
|
||||
(args, controls) =>
|
||||
{
|
||||
var tag = (IDocumentTag) sandbox.CreateInstance(tagType);
|
||||
if (!tag.TryParseTag(args, out var control))
|
||||
{
|
||||
Logger.Error($"Failed to parse {tagId} args");
|
||||
return new Control();
|
||||
}
|
||||
private Parser<char, Control> CreateTagControlParser(string tagId, Type tagType, ISandboxHelper sandbox)
|
||||
{
|
||||
return Map(
|
||||
(args, controls) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var tag = (IDocumentTag) sandbox.CreateInstance(tagType);
|
||||
if (!tag.TryParseTag(args, out var control))
|
||||
{
|
||||
_sawmill.Error($"Failed to parse {tagId} args");
|
||||
return new GuidebookError(args.ToString() ?? tagId, $"Failed to parse {tagId} args");
|
||||
}
|
||||
|
||||
foreach (var child in controls)
|
||||
{
|
||||
control.AddChild(child);
|
||||
}
|
||||
return control;
|
||||
},
|
||||
ParseTagArgs(tagId),
|
||||
TagContentParser(tagId)).Labelled($"{tagId} control");
|
||||
foreach (var child in controls)
|
||||
{
|
||||
control.AddChild(child);
|
||||
}
|
||||
|
||||
return control;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var output = args.Aggregate(string.Empty,
|
||||
(current, pair) => current + $"{pair.Key}=\"{pair.Value}\" ");
|
||||
|
||||
_sawmill.Error($"Tag: {tagId} \n Arguments: {output}/>");
|
||||
return new GuidebookError($"Tag: {tagId}\nArguments: {output}", e.ToString());
|
||||
}
|
||||
},
|
||||
ParseTagArgs(tagId),
|
||||
TagContentParser(tagId))
|
||||
.Labelled($"{tagId} control");
|
||||
}
|
||||
|
||||
// Parse a bunch of controls until we encounter a matching closing tag.
|
||||
private Parser<char, IEnumerable<Control>> TagContentParser(string tag) =>
|
||||
OneOf(
|
||||
Try(ImmediateTagEnd).ThenReturn(Enumerable.Empty<Control>()),
|
||||
TagEnd.Then(_controlParser.Until(TryTagTerminator(tag)).Labelled($"{tag} children"))
|
||||
);
|
||||
private Parser<char, IEnumerable<Control>> TagContentParser(string tag)
|
||||
{
|
||||
return OneOf(
|
||||
Try(ImmediateTagEnd).ThenReturn(Enumerable.Empty<Control>()),
|
||||
TagEnd.Then(_controlParser.Until(TryTagTerminator(tag)).Labelled($"{tag} children"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Guidebook.Controls;
|
||||
using Pidgin;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -14,92 +15,142 @@ public sealed partial class DocumentParsingManager
|
||||
{
|
||||
private const string ListBullet = " › ";
|
||||
|
||||
#region Text Parsing
|
||||
#region Basic Text Parsing
|
||||
// Try look for an escaped character. If found, skip the escaping slash and return the character.
|
||||
private static readonly Parser<char, char> TryEscapedChar = Try(Char('\\').Then(OneOf(
|
||||
Try(Char('<')),
|
||||
Try(Char('>')),
|
||||
Try(Char('\\')),
|
||||
Try(Char('-')),
|
||||
Try(Char('=')),
|
||||
Try(Char('"')),
|
||||
Try(Char(' ')),
|
||||
Try(Char('n')).ThenReturn('\n'),
|
||||
Try(Char('t')).ThenReturn('\t')
|
||||
)));
|
||||
// Parser that consumes a - and then just parses normal rich text with some prefix text (a bullet point).
|
||||
private static readonly Parser<char, char> TryEscapedChar = Try(Char('\\')
|
||||
.Then(OneOf(
|
||||
Try(Char('<')),
|
||||
Try(Char('>')),
|
||||
Try(Char('\\')),
|
||||
Try(Char('-')),
|
||||
Try(Char('=')),
|
||||
Try(Char('"')),
|
||||
Try(Char(' ')),
|
||||
Try(Char('n')).ThenReturn('\n'),
|
||||
Try(Char('t')).ThenReturn('\t')
|
||||
)));
|
||||
|
||||
private static readonly Parser<char, Unit> SkipNewline = Whitespace.SkipUntil(Char('\n'));
|
||||
|
||||
private static readonly Parser<char, char> TrySingleNewlineToSpace = Try(SkipNewline).Then(SkipWhitespaces).ThenReturn(' ');
|
||||
private static readonly Parser<char, char> TrySingleNewlineToSpace =
|
||||
Try(SkipNewline).Then(SkipWhitespaces).ThenReturn(' ');
|
||||
|
||||
private static readonly Parser<char, char> TextChar = OneOf(
|
||||
TryEscapedChar, // consume any backslashed being used to escape text
|
||||
TrySingleNewlineToSpace, // turn single newlines into spaces
|
||||
Any // just return the character.
|
||||
);
|
||||
);
|
||||
|
||||
// like TextChar, but not skipping whitespace around newlines
|
||||
private static readonly Parser<char, char> QuotedTextChar = OneOf(TryEscapedChar, Any);
|
||||
|
||||
private static readonly Parser<char, string> QuotedText =
|
||||
Char('"').Then(QuotedTextChar.Until(Try(Char('"'))).Select(string.Concat)).Labelled("quoted text");
|
||||
|
||||
private static readonly Parser<char, Unit> TryStartList =
|
||||
Try(SkipNewline.Then(SkipWhitespaces).Then(Char('-'))).Then(SkipWhitespaces);
|
||||
|
||||
private static readonly Parser<char, Unit> TryStartTag = Try(Char('<')).Then(SkipWhitespaces);
|
||||
|
||||
private static readonly Parser<char, Unit> TryStartParagraph =
|
||||
Try(SkipNewline.Then(SkipNewline)).Then(SkipWhitespaces);
|
||||
|
||||
private static readonly Parser<char, Unit> TryLookTextEnd =
|
||||
Lookahead(OneOf(TryStartTag, TryStartList, TryStartParagraph, Try(Whitespace.SkipUntil(End))));
|
||||
|
||||
private static readonly Parser<char, string> TextParser =
|
||||
TextChar.AtLeastOnceUntil(TryLookTextEnd).Select(string.Concat);
|
||||
|
||||
private static readonly Parser<char, Control> TextControlParser = Try(Map<char, string, Control>(text =>
|
||||
{
|
||||
var rt = new RichTextLabel
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Margin = new Thickness(0, 0, 0, 15.0f)
|
||||
};
|
||||
|
||||
var msg = new FormattedMessage();
|
||||
// THANK YOU RICHTEXT VERY COOL
|
||||
// (text doesn't default to white).
|
||||
msg.PushColor(Color.White);
|
||||
|
||||
// If the parsing fails, don't throw an error and instead make an inline error message
|
||||
string? error;
|
||||
if (!msg.TryAddMarkup(text, out error))
|
||||
{
|
||||
Logger.GetSawmill("Guidebook").Error("Failed to parse RichText in Guidebook");
|
||||
|
||||
return new GuidebookError(text, error);
|
||||
}
|
||||
|
||||
msg.Pop();
|
||||
rt.SetMessage(msg);
|
||||
return rt;
|
||||
},
|
||||
TextParser)
|
||||
.Cast<Control>())
|
||||
.Labelled("richtext");
|
||||
|
||||
private static readonly Parser<char, Control> HeaderControlParser = Try(Char('#'))
|
||||
.Then(SkipWhitespaces.Then(Map(text => new Label
|
||||
{
|
||||
Text = text,
|
||||
StyleClasses = { "LabelHeadingBigger" }
|
||||
},
|
||||
AnyCharExcept('\n').AtLeastOnceString())
|
||||
.Cast<Control>()))
|
||||
.Labelled("header");
|
||||
|
||||
private static readonly Parser<char, Control> SubHeaderControlParser = Try(String("##"))
|
||||
.Then(SkipWhitespaces.Then(Map(text => new Label
|
||||
{
|
||||
Text = text,
|
||||
StyleClasses = { "LabelHeading" }
|
||||
},
|
||||
AnyCharExcept('\n').AtLeastOnceString())
|
||||
.Cast<Control>()))
|
||||
.Labelled("subheader");
|
||||
|
||||
private static readonly Parser<char, Control> TryHeaderControl = OneOf(SubHeaderControlParser, HeaderControlParser);
|
||||
|
||||
private static readonly Parser<char, Control> ListControlParser = Try(Char('-'))
|
||||
.Then(SkipWhitespaces)
|
||||
.Then(Map(
|
||||
control => new BoxContainer
|
||||
{
|
||||
Children = { new Label { Text = ListBullet, VerticalAlignment = VAlignment.Top }, control },
|
||||
Orientation = LayoutOrientation.Horizontal
|
||||
},
|
||||
TextControlParser)
|
||||
.Cast<Control>())
|
||||
.Labelled("list");
|
||||
|
||||
#region Text Parsing
|
||||
|
||||
#region Basic Text Parsing
|
||||
|
||||
// Try look for an escaped character. If found, skip the escaping slash and return the character.
|
||||
|
||||
|
||||
// like TextChar, but not skipping whitespace around newlines
|
||||
|
||||
|
||||
// Quoted text
|
||||
private static readonly Parser<char, string> QuotedText = Char('"').Then(QuotedTextChar.Until(Try(Char('"'))).Select(string.Concat)).Labelled("quoted text");
|
||||
|
||||
#endregion
|
||||
|
||||
#region rich text-end markers
|
||||
private static readonly Parser<char, Unit> TryStartList = Try(SkipNewline.Then(SkipWhitespaces).Then(Char('-'))).Then(SkipWhitespaces);
|
||||
private static readonly Parser<char, Unit> TryStartTag = Try(Char('<')).Then(SkipWhitespaces);
|
||||
private static readonly Parser<char, Unit> TryStartParagraph = Try(SkipNewline.Then(SkipNewline)).Then(SkipWhitespaces);
|
||||
private static readonly Parser<char, Unit> TryLookTextEnd = Lookahead(OneOf(TryStartTag, TryStartList, TryStartParagraph, Try(Whitespace.SkipUntil(End))));
|
||||
|
||||
#endregion
|
||||
|
||||
// parses text characters until it hits a text-end
|
||||
private static readonly Parser<char, string> TextParser = TextChar.AtLeastOnceUntil(TryLookTextEnd).Select(string.Concat);
|
||||
|
||||
private static readonly Parser<char, Control> TextControlParser = Try(Map(text =>
|
||||
{
|
||||
var rt = new RichTextLabel()
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Margin = new Thickness(0, 0, 0, 15.0f),
|
||||
};
|
||||
|
||||
var msg = new FormattedMessage();
|
||||
// THANK YOU RICHTEXT VERY COOL
|
||||
// (text doesn't default to white).
|
||||
msg.PushColor(Color.White);
|
||||
msg.AddMarkup(text);
|
||||
msg.Pop();
|
||||
rt.SetMessage(msg);
|
||||
return rt;
|
||||
}, TextParser).Cast<Control>()).Labelled("richtext");
|
||||
#endregion
|
||||
|
||||
#region Headers
|
||||
private static readonly Parser<char, Control> HeaderControlParser = Try(Char('#')).Then(SkipWhitespaces.Then(Map(text => new Label()
|
||||
{
|
||||
Text = text,
|
||||
StyleClasses = { "LabelHeadingBigger" }
|
||||
}, AnyCharExcept('\n').AtLeastOnceString()).Cast<Control>())).Labelled("header");
|
||||
|
||||
private static readonly Parser<char, Control> SubHeaderControlParser = Try(String("##")).Then(SkipWhitespaces.Then(Map(text => new Label()
|
||||
{
|
||||
Text = text,
|
||||
StyleClasses = { "LabelHeading" }
|
||||
}, AnyCharExcept('\n').AtLeastOnceString()).Cast<Control>())).Labelled("subheader");
|
||||
|
||||
private static readonly Parser<char, Control> TryHeaderControl = OneOf(SubHeaderControlParser, HeaderControlParser);
|
||||
#endregion
|
||||
|
||||
// Parser that consumes a - and then just parses normal rich text with some prefix text (a bullet point).
|
||||
private static readonly Parser<char, Control> ListControlParser = Try(Char('-')).Then(SkipWhitespaces).Then(Map(
|
||||
control => new BoxContainer()
|
||||
{
|
||||
Children = { new Label() { Text = ListBullet, VerticalAlignment = VAlignment.Top, }, control },
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
}, TextControlParser).Cast<Control>()).Labelled("list");
|
||||
|
||||
#region Tag Parsing
|
||||
|
||||
// closing brackets for tags
|
||||
private static readonly Parser<char, Unit> TagEnd = Char('>').Then(SkipWhitespaces);
|
||||
private static readonly Parser<char, Unit> ImmediateTagEnd = String("/>").Then(SkipWhitespaces);
|
||||
@@ -107,20 +158,24 @@ public sealed partial class DocumentParsingManager
|
||||
private static readonly Parser<char, Unit> TryLookTagEnd = Lookahead(OneOf(Try(TagEnd), Try(ImmediateTagEnd)));
|
||||
|
||||
//parse tag argument key. any normal text character up until we hit a "="
|
||||
private static readonly Parser<char, string> TagArgKey = LetterOrDigit.Until(Char('=')).Select(string.Concat).Labelled("tag argument key");
|
||||
private static readonly Parser<char, string> TagArgKey =
|
||||
LetterOrDigit.Until(Char('=')).Select(string.Concat).Labelled("tag argument key");
|
||||
|
||||
// parser for a singular tag argument. Note that each TryQuoteOrChar will consume a whole quoted block before the Until() looks for whitespace
|
||||
private static readonly Parser<char, (string, string)> TagArgParser = Map((key, value) => (key, value), TagArgKey, QuotedText).Before(SkipWhitespaces);
|
||||
private static readonly Parser<char, (string, string)> TagArgParser =
|
||||
Map((key, value) => (key, value), TagArgKey, QuotedText).Before(SkipWhitespaces);
|
||||
|
||||
// parser for all tag arguments
|
||||
private static readonly Parser<char, IEnumerable<(string, string)>> TagArgsParser = TagArgParser.Until(TryLookTagEnd);
|
||||
private static readonly Parser<char, IEnumerable<(string, string)>> TagArgsParser =
|
||||
TagArgParser.Until(TryLookTagEnd);
|
||||
|
||||
// parser for an opening tag.
|
||||
private static readonly Parser<char, string> TryOpeningTag =
|
||||
Try(Char('<'))
|
||||
.Then(SkipWhitespaces)
|
||||
.Then(TextChar.Until(OneOf(Whitespace.SkipAtLeastOnce(), TryLookTagEnd)))
|
||||
.Select(string.Concat).Labelled($"opening tag");
|
||||
.Then(SkipWhitespaces)
|
||||
.Then(TextChar.Until(OneOf(Whitespace.SkipAtLeastOnce(), TryLookTagEnd)))
|
||||
.Select(string.Concat)
|
||||
.Labelled("opening tag");
|
||||
|
||||
private static Parser<char, Dictionary<string, string>> ParseTagArgs(string tag)
|
||||
{
|
||||
@@ -138,5 +193,6 @@ public sealed partial class DocumentParsingManager
|
||||
.Then(TagEnd)
|
||||
.Labelled($"closing {tag} tag");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ public sealed partial class SingleMarkingPicker : BoxContainer
|
||||
|
||||
for (var i = 0; i < PointsUsed; i++)
|
||||
{
|
||||
SlotSelector.AddItem($"Slot {i + 1}", i);
|
||||
SlotSelector.AddItem(Loc.GetString("marking-slot", ("number", $"{i + 1}")), i);
|
||||
|
||||
if (i == _slot)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace Content.Client.IconSmoothing
|
||||
[UsedImplicitly]
|
||||
public sealed partial class IconSmoothSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
|
||||
private readonly Queue<EntityUid> _dirtyEntities = new();
|
||||
private readonly Queue<EntityUid> _anchorChangedEntities = new();
|
||||
|
||||
@@ -46,7 +48,7 @@ namespace Content.Client.IconSmoothing
|
||||
if (xform.Anchored)
|
||||
{
|
||||
component.LastPosition = TryComp<MapGridComponent>(xform.GridUid, out var grid)
|
||||
? (xform.GridUid.Value, grid.TileIndicesFor(xform.Coordinates))
|
||||
? (xform.GridUid.Value, _mapSystem.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates))
|
||||
: (null, new Vector2i(0, 0));
|
||||
|
||||
DirtyNeighbours(uid, component);
|
||||
@@ -151,9 +153,12 @@ namespace Content.Client.IconSmoothing
|
||||
|
||||
Vector2i pos;
|
||||
|
||||
EntityUid entityUid;
|
||||
|
||||
if (transform.Anchored && TryComp<MapGridComponent>(transform.GridUid, out var grid))
|
||||
{
|
||||
pos = grid.CoordinatesToTile(transform.Coordinates);
|
||||
entityUid = transform.GridUid.Value;
|
||||
pos = _mapSystem.CoordinatesToTile(transform.GridUid.Value, grid, transform.Coordinates);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -164,21 +169,22 @@ namespace Content.Client.IconSmoothing
|
||||
if (!TryComp(gridId, out grid))
|
||||
return;
|
||||
|
||||
entityUid = gridId;
|
||||
pos = oldPos;
|
||||
}
|
||||
|
||||
// Yes, we updates ALL smoothing entities surrounding us even if they would never smooth with us.
|
||||
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(1, 0)));
|
||||
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(-1, 0)));
|
||||
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(0, 1)));
|
||||
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(0, -1)));
|
||||
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(1, 0)));
|
||||
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(-1, 0)));
|
||||
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(0, 1)));
|
||||
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(0, -1)));
|
||||
|
||||
if (comp.Mode is IconSmoothingMode.Corners or IconSmoothingMode.NoSprite or IconSmoothingMode.Diagonal)
|
||||
{
|
||||
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(1, 1)));
|
||||
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(-1, -1)));
|
||||
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(-1, 1)));
|
||||
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(1, -1)));
|
||||
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(1, 1)));
|
||||
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(-1, -1)));
|
||||
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(-1, 1)));
|
||||
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(1, -1)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +212,7 @@ namespace Content.Client.IconSmoothing
|
||||
IconSmoothComponent? smooth = null)
|
||||
{
|
||||
TransformComponent? xform;
|
||||
MapGridComponent? grid = null;
|
||||
Entity<MapGridComponent>? gridEntity = null;
|
||||
|
||||
// The generation check prevents updating an entity multiple times per tick.
|
||||
// As it stands now, it's totally possible for something to get queued twice.
|
||||
@@ -223,17 +229,20 @@ namespace Content.Client.IconSmoothing
|
||||
{
|
||||
var directions = DirectionFlag.None;
|
||||
|
||||
if (TryComp(xform.GridUid, out grid))
|
||||
if (TryComp(xform.GridUid, out MapGridComponent? grid))
|
||||
{
|
||||
var pos = grid.TileIndicesFor(xform.Coordinates);
|
||||
var gridUid = xform.GridUid.Value;
|
||||
var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates);
|
||||
|
||||
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.North)), smoothQuery))
|
||||
gridEntity = (gridUid, grid);
|
||||
|
||||
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)), smoothQuery))
|
||||
directions |= DirectionFlag.North;
|
||||
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.South)), smoothQuery))
|
||||
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)), smoothQuery))
|
||||
directions |= DirectionFlag.South;
|
||||
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.East)), smoothQuery))
|
||||
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)), smoothQuery))
|
||||
directions |= DirectionFlag.East;
|
||||
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.West)), smoothQuery))
|
||||
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)), smoothQuery))
|
||||
directions |= DirectionFlag.West;
|
||||
}
|
||||
|
||||
@@ -257,7 +266,11 @@ namespace Content.Client.IconSmoothing
|
||||
|
||||
if (xform.Anchored)
|
||||
{
|
||||
if (!TryComp(xform.GridUid, out grid))
|
||||
if (TryComp(xform.GridUid, out MapGridComponent? grid))
|
||||
{
|
||||
gridEntity = (xform.GridUid.Value, grid);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Failed to calculate IconSmoothComponent sprite in {uid} because grid {xform.GridUid} was missing.");
|
||||
return;
|
||||
@@ -267,28 +280,31 @@ namespace Content.Client.IconSmoothing
|
||||
switch (smooth.Mode)
|
||||
{
|
||||
case IconSmoothingMode.Corners:
|
||||
CalculateNewSpriteCorners(grid, smooth, spriteEnt, xform, smoothQuery);
|
||||
CalculateNewSpriteCorners(gridEntity, smooth, spriteEnt, xform, smoothQuery);
|
||||
break;
|
||||
case IconSmoothingMode.CardinalFlags:
|
||||
CalculateNewSpriteCardinal(grid, smooth, spriteEnt, xform, smoothQuery);
|
||||
CalculateNewSpriteCardinal(gridEntity, smooth, spriteEnt, xform, smoothQuery);
|
||||
break;
|
||||
case IconSmoothingMode.Diagonal:
|
||||
CalculateNewSpriteDiagonal(grid, smooth, spriteEnt, xform, smoothQuery);
|
||||
CalculateNewSpriteDiagonal(gridEntity, smooth, spriteEnt, xform, smoothQuery);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateNewSpriteDiagonal(MapGridComponent? grid, IconSmoothComponent smooth,
|
||||
private void CalculateNewSpriteDiagonal(Entity<MapGridComponent>? gridEntity, IconSmoothComponent smooth,
|
||||
Entity<SpriteComponent> sprite, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
|
||||
{
|
||||
if (grid == null)
|
||||
if (gridEntity == null)
|
||||
{
|
||||
sprite.Comp.LayerSetState(0, $"{smooth.StateBase}0");
|
||||
return;
|
||||
}
|
||||
|
||||
var gridUid = gridEntity.Value.Owner;
|
||||
var grid = gridEntity.Value.Comp;
|
||||
|
||||
var neighbors = new Vector2[]
|
||||
{
|
||||
new(1, 0),
|
||||
@@ -296,14 +312,14 @@ namespace Content.Client.IconSmoothing
|
||||
new(0, -1),
|
||||
};
|
||||
|
||||
var pos = grid.TileIndicesFor(xform.Coordinates);
|
||||
var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates);
|
||||
var rotation = xform.LocalRotation;
|
||||
var matching = true;
|
||||
|
||||
for (var i = 0; i < neighbors.Length; i++)
|
||||
{
|
||||
var neighbor = (Vector2i) rotation.RotateVec(neighbors[i]);
|
||||
matching = matching && MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos + neighbor), smoothQuery);
|
||||
var neighbor = (Vector2i)rotation.RotateVec(neighbors[i]);
|
||||
matching = matching && MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos + neighbor), smoothQuery);
|
||||
}
|
||||
|
||||
if (matching)
|
||||
@@ -316,27 +332,30 @@ namespace Content.Client.IconSmoothing
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateNewSpriteCardinal(MapGridComponent? grid, IconSmoothComponent smooth, Entity<SpriteComponent> sprite, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
|
||||
private void CalculateNewSpriteCardinal(Entity<MapGridComponent>? gridEntity, IconSmoothComponent smooth, Entity<SpriteComponent> sprite, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
|
||||
{
|
||||
var dirs = CardinalConnectDirs.None;
|
||||
|
||||
if (grid == null)
|
||||
if (gridEntity == null)
|
||||
{
|
||||
sprite.Comp.LayerSetState(0, $"{smooth.StateBase}{(int) dirs}");
|
||||
sprite.Comp.LayerSetState(0, $"{smooth.StateBase}{(int)dirs}");
|
||||
return;
|
||||
}
|
||||
|
||||
var pos = grid.TileIndicesFor(xform.Coordinates);
|
||||
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.North)), smoothQuery))
|
||||
var gridUid = gridEntity.Value.Owner;
|
||||
var grid = gridEntity.Value.Comp;
|
||||
|
||||
var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates);
|
||||
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)), smoothQuery))
|
||||
dirs |= CardinalConnectDirs.North;
|
||||
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.South)), smoothQuery))
|
||||
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)), smoothQuery))
|
||||
dirs |= CardinalConnectDirs.South;
|
||||
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.East)), smoothQuery))
|
||||
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)), smoothQuery))
|
||||
dirs |= CardinalConnectDirs.East;
|
||||
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.West)), smoothQuery))
|
||||
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)), smoothQuery))
|
||||
dirs |= CardinalConnectDirs.West;
|
||||
|
||||
sprite.Comp.LayerSetState(0, $"{smooth.StateBase}{(int) dirs}");
|
||||
sprite.Comp.LayerSetState(0, $"{smooth.StateBase}{(int)dirs}");
|
||||
|
||||
var directions = DirectionFlag.None;
|
||||
|
||||
@@ -367,11 +386,11 @@ namespace Content.Client.IconSmoothing
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CalculateNewSpriteCorners(MapGridComponent? grid, IconSmoothComponent smooth, Entity<SpriteComponent> spriteEnt, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
|
||||
private void CalculateNewSpriteCorners(Entity<MapGridComponent>? gridEntity, IconSmoothComponent smooth, Entity<SpriteComponent> spriteEnt, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
|
||||
{
|
||||
var (cornerNE, cornerNW, cornerSW, cornerSE) = grid == null
|
||||
var (cornerNE, cornerNW, cornerSW, cornerSE) = gridEntity == null
|
||||
? (CornerFill.None, CornerFill.None, CornerFill.None, CornerFill.None)
|
||||
: CalculateCornerFill(grid, smooth, xform, smoothQuery);
|
||||
: CalculateCornerFill(gridEntity.Value, smooth, xform, smoothQuery);
|
||||
|
||||
// TODO figure out a better way to set multiple sprite layers.
|
||||
// This will currently re-calculate the sprite bounding box 4 times.
|
||||
@@ -379,10 +398,10 @@ namespace Content.Client.IconSmoothing
|
||||
// At the very least each event currently only queues a sprite for updating.
|
||||
// Oh god sprite component is a mess.
|
||||
var sprite = spriteEnt.Comp;
|
||||
sprite.LayerSetState(CornerLayers.NE, $"{smooth.StateBase}{(int) cornerNE}");
|
||||
sprite.LayerSetState(CornerLayers.SE, $"{smooth.StateBase}{(int) cornerSE}");
|
||||
sprite.LayerSetState(CornerLayers.SW, $"{smooth.StateBase}{(int) cornerSW}");
|
||||
sprite.LayerSetState(CornerLayers.NW, $"{smooth.StateBase}{(int) cornerNW}");
|
||||
sprite.LayerSetState(CornerLayers.NE, $"{smooth.StateBase}{(int)cornerNE}");
|
||||
sprite.LayerSetState(CornerLayers.SE, $"{smooth.StateBase}{(int)cornerSE}");
|
||||
sprite.LayerSetState(CornerLayers.SW, $"{smooth.StateBase}{(int)cornerSW}");
|
||||
sprite.LayerSetState(CornerLayers.NW, $"{smooth.StateBase}{(int)cornerNW}");
|
||||
|
||||
var directions = DirectionFlag.None;
|
||||
|
||||
@@ -401,17 +420,20 @@ namespace Content.Client.IconSmoothing
|
||||
CalculateEdge(spriteEnt, directions, sprite);
|
||||
}
|
||||
|
||||
private (CornerFill ne, CornerFill nw, CornerFill sw, CornerFill se) CalculateCornerFill(MapGridComponent grid, IconSmoothComponent smooth, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
|
||||
private (CornerFill ne, CornerFill nw, CornerFill sw, CornerFill se) CalculateCornerFill(Entity<MapGridComponent> gridEntity, IconSmoothComponent smooth, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
|
||||
{
|
||||
var pos = grid.TileIndicesFor(xform.Coordinates);
|
||||
var n = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.North)), smoothQuery);
|
||||
var ne = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.NorthEast)), smoothQuery);
|
||||
var e = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.East)), smoothQuery);
|
||||
var se = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.SouthEast)), smoothQuery);
|
||||
var s = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.South)), smoothQuery);
|
||||
var sw = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.SouthWest)), smoothQuery);
|
||||
var w = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.West)), smoothQuery);
|
||||
var nw = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.NorthWest)), smoothQuery);
|
||||
var gridUid = gridEntity.Owner;
|
||||
var grid = gridEntity.Comp;
|
||||
|
||||
var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates);
|
||||
var n = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)), smoothQuery);
|
||||
var ne = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.NorthEast)), smoothQuery);
|
||||
var e = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)), smoothQuery);
|
||||
var se = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.SouthEast)), smoothQuery);
|
||||
var s = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)), smoothQuery);
|
||||
var sw = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.SouthWest)), smoothQuery);
|
||||
var w = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)), smoothQuery);
|
||||
var nw = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.NorthWest)), smoothQuery);
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
var cornerNE = CornerFill.None;
|
||||
|
||||
@@ -74,6 +74,9 @@ namespace Content.Client.Input
|
||||
human.AddFunction(ContentKeyFunctions.OpenBackpack);
|
||||
human.AddFunction(ContentKeyFunctions.OpenBelt);
|
||||
human.AddFunction(ContentKeyFunctions.MouseMiddle);
|
||||
human.AddFunction(ContentKeyFunctions.RotateObjectClockwise);
|
||||
human.AddFunction(ContentKeyFunctions.RotateObjectCounterclockwise);
|
||||
human.AddFunction(ContentKeyFunctions.FlipObject);
|
||||
human.AddFunction(ContentKeyFunctions.ArcadeUp);
|
||||
human.AddFunction(ContentKeyFunctions.ArcadeDown);
|
||||
human.AddFunction(ContentKeyFunctions.ArcadeLeft);
|
||||
|
||||
@@ -206,7 +206,7 @@ namespace Content.Client.Instruments.UI
|
||||
|
||||
var container = _entManager.System<SharedContainerSystem>();
|
||||
// If we're a handheld instrument, we might be in a container. Get it just in case.
|
||||
container.TryGetContainingContainer(Entity, out var conMan);
|
||||
container.TryGetContainingContainer((Entity, null, null), out var conMan);
|
||||
|
||||
// If the instrument is handheld and we're not holding it, we return.
|
||||
if (instrument.Handheld && (conMan == null || conMan.Owner != localEntity))
|
||||
|
||||
@@ -42,6 +42,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
|
||||
// how often to recheck possible targets (prevents calling expensive
|
||||
// check logic each update)
|
||||
@@ -89,7 +90,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
/// </summary>
|
||||
private bool _isReplaying;
|
||||
|
||||
private float _deadzone;
|
||||
public float Deadzone;
|
||||
|
||||
private DragState _state = DragState.NotDragging;
|
||||
|
||||
@@ -121,7 +122,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
|
||||
private void SetDeadZone(float deadZone)
|
||||
{
|
||||
_deadzone = deadZone;
|
||||
Deadzone = deadZone;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -211,7 +212,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
|
||||
_draggedEntity = entity;
|
||||
_state = DragState.MouseDown;
|
||||
_mouseDownScreenPos = _inputManager.MouseScreenPosition;
|
||||
_mouseDownScreenPos = args.ScreenCoordinates;
|
||||
_mouseDownTime = 0;
|
||||
|
||||
// don't want anything else to process the click,
|
||||
@@ -239,8 +240,13 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
|
||||
if (TryComp<SpriteComponent>(_draggedEntity, out var draggedSprite))
|
||||
{
|
||||
var screenPos = _inputManager.MouseScreenPosition;
|
||||
// No _draggedEntity in null window (Happens in tests)
|
||||
if (!screenPos.IsValid)
|
||||
return;
|
||||
|
||||
// pop up drag shadow under mouse
|
||||
var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition);
|
||||
var mousePos = _eyeManager.PixelToMap(screenPos);
|
||||
_dragShadow = EntityManager.SpawnEntity("dragshadow", mousePos);
|
||||
var dragSprite = Comp<SpriteComponent>(_dragShadow.Value);
|
||||
dragSprite.CopyFrom(draggedSprite);
|
||||
@@ -517,6 +523,9 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
if (dropEv2.Handled)
|
||||
return dropEv2.CanDrop;
|
||||
|
||||
if (dropEv.Handled && dropEv.CanDrop)
|
||||
return true;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -530,7 +539,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
case DragState.MouseDown:
|
||||
{
|
||||
var screenPos = _inputManager.MouseScreenPosition;
|
||||
if ((_mouseDownScreenPos!.Value.Position - screenPos.Position).Length() > _deadzone)
|
||||
if ((_mouseDownScreenPos!.Value.Position - screenPos.Position).Length() > Deadzone)
|
||||
{
|
||||
StartDrag();
|
||||
}
|
||||
@@ -551,7 +560,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
if (Exists(_dragShadow))
|
||||
{
|
||||
var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition);
|
||||
Transform(_dragShadow.Value).WorldPosition = mousePos.Position;
|
||||
_transformSystem.SetWorldPosition(_dragShadow.Value, mousePos.Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,23 @@ using Content.Client.Chat.Managers;
|
||||
using Content.Client.Clickable;
|
||||
using Content.Client.DebugMon;
|
||||
using Content.Client.Eui;
|
||||
using Content.Client.Fullscreen;
|
||||
using Content.Client.GhostKick;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Launcher;
|
||||
using Content.Client.Mapping;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Client.Players.PlayTimeTracking;
|
||||
using Content.Client.Replay;
|
||||
using Content.Client.Screenshot;
|
||||
using Content.Client.Fullscreen;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.Viewport;
|
||||
using Content.Client.Voting;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.Replay;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
|
||||
|
||||
namespace Content.Client.IoC
|
||||
{
|
||||
internal static class ClientContentIoC
|
||||
@@ -49,6 +49,7 @@ namespace Content.Client.IoC
|
||||
collection.Register<DocumentParsingManager>();
|
||||
collection.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
|
||||
collection.Register<ISharedPlaytimeManager, JobRequirementsManager>();
|
||||
collection.Register<MappingManager>();
|
||||
collection.Register<DebugMonitorManager>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed class ItemSystem : SharedItemSystem
|
||||
public override void VisualsChanged(EntityUid uid)
|
||||
{
|
||||
// if the item is in a container, it might be equipped to hands or inventory slots --> update visuals.
|
||||
if (Container.TryGetContainingContainer(uid, out var container))
|
||||
if (Container.TryGetContainingContainer((uid, null, null), out var container))
|
||||
RaiseLocalEvent(container.Owner, new VisualsChangedEvent(GetNetEntity(uid), container.ID));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.CrewManifest;
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.Players.PlayTimeTracking;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Client.Console;
|
||||
@@ -26,6 +28,7 @@ namespace Content.Client.LateJoin
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
|
||||
[Dependency] private readonly JobRequirementsManager _jobRequirements = default!;
|
||||
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
|
||||
|
||||
public event Action<(NetEntity, string)> SelectedId;
|
||||
|
||||
@@ -254,7 +257,7 @@ namespace Content.Client.LateJoin
|
||||
|
||||
jobButton.OnPressed += _ => SelectedId.Invoke((id, jobButton.JobId));
|
||||
|
||||
if (!_jobRequirements.IsAllowed(prototype, out var reason))
|
||||
if (!_jobRequirements.IsAllowed(prototype, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
|
||||
{
|
||||
jobButton.Disabled = true;
|
||||
|
||||
|
||||
@@ -22,21 +22,29 @@ public sealed class LatheSystem : SharedLatheSystem
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
// Lathe specific stuff
|
||||
if (_appearance.TryGetData<bool>(uid, LatheVisuals.IsRunning, out var isRunning, args.Component))
|
||||
{
|
||||
if (args.Sprite.LayerMapTryGet(LatheVisualLayers.IsRunning, out var runningLayer) &&
|
||||
component.RunningState != null &&
|
||||
component.IdleState != null)
|
||||
{
|
||||
var state = isRunning ? component.RunningState : component.IdleState;
|
||||
args.Sprite.LayerSetState(runningLayer, state);
|
||||
}
|
||||
}
|
||||
|
||||
if (_appearance.TryGetData<bool>(uid, PowerDeviceVisuals.Powered, out var powered, args.Component) &&
|
||||
args.Sprite.LayerMapTryGet(PowerDeviceVisualLayers.Powered, out var powerLayer))
|
||||
{
|
||||
args.Sprite.LayerSetVisible(powerLayer, powered);
|
||||
}
|
||||
|
||||
// Lathe specific stuff
|
||||
if (_appearance.TryGetData<bool>(uid, LatheVisuals.IsRunning, out var isRunning, args.Component) &&
|
||||
args.Sprite.LayerMapTryGet(LatheVisualLayers.IsRunning, out var runningLayer) &&
|
||||
component.RunningState != null &&
|
||||
component.IdleState != null)
|
||||
{
|
||||
var state = isRunning ? component.RunningState : component.IdleState;
|
||||
args.Sprite.LayerSetAnimationTime(runningLayer, 0f);
|
||||
args.Sprite.LayerSetState(runningLayer, state);
|
||||
if (component.UnlitIdleState != null &&
|
||||
component.UnlitRunningState != null)
|
||||
{
|
||||
var state = isRunning ? component.UnlitRunningState : component.UnlitIdleState;
|
||||
args.Sprite.LayerSetState(powerLayer, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,11 +100,9 @@
|
||||
Margin="5 0 0 0"
|
||||
Text="{Loc 'lathe-menu-fabricating-message'}">
|
||||
</Label>
|
||||
<EntityPrototypeView
|
||||
Name="FabricatingEntityProto"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="100 0 0 0"
|
||||
/>
|
||||
<BoxContainer Name="FabricatingDisplayContainer"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="100 0 0 0"/>
|
||||
<Label
|
||||
Name="NameLabel"
|
||||
RectClipContent="True"
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
using System.Buffers;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Content.Client.Materials;
|
||||
using Content.Shared.Lathe;
|
||||
using Content.Shared.Lathe.Prototypes;
|
||||
using Content.Shared.Materials;
|
||||
using Content.Shared.Research.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Lathe.UI;
|
||||
@@ -76,6 +73,16 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
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>
|
||||
@@ -92,7 +99,7 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
|
||||
if (SearchBar.Text.Trim().Length != 0)
|
||||
{
|
||||
if (proto.Name.ToLowerInvariant().Contains(SearchBar.Text.Trim().ToLowerInvariant()))
|
||||
if (_lathe.GetRecipeName(recipe).ToLowerInvariant().Contains(SearchBar.Text.Trim().ToLowerInvariant()))
|
||||
recipesToShow.Add(proto);
|
||||
}
|
||||
else
|
||||
@@ -104,19 +111,15 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
if (!int.TryParse(AmountLineEdit.Text, out var quantity) || quantity <= 0)
|
||||
quantity = 1;
|
||||
|
||||
var sortedRecipesToShow = recipesToShow.OrderBy(p => p.Name);
|
||||
var sortedRecipesToShow = recipesToShow.OrderBy(_lathe.GetRecipeName);
|
||||
RecipeList.Children.Clear();
|
||||
_entityManager.TryGetComponent(Entity, out LatheComponent? lathe);
|
||||
|
||||
foreach (var prototype in sortedRecipesToShow)
|
||||
{
|
||||
EntityPrototype? recipeProto = null;
|
||||
if (_prototypeManager.TryIndex(prototype.Result, out EntityPrototype? entityProto))
|
||||
recipeProto = entityProto;
|
||||
|
||||
var canProduce = _lathe.CanProduce(Entity, prototype, quantity, component: lathe);
|
||||
|
||||
var control = new RecipeControl(prototype, () => GenerateTooltipText(prototype), canProduce, recipeProto);
|
||||
var control = new RecipeControl(_lathe, prototype, () => GenerateTooltipText(prototype), canProduce, GetRecipeDisplayControl(prototype));
|
||||
control.OnButtonPressed += s =>
|
||||
{
|
||||
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
|
||||
@@ -132,9 +135,9 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
StringBuilder sb = new();
|
||||
var multiplier = _entityManager.GetComponent<LatheComponent>(Entity).MaterialUseMultiplier;
|
||||
|
||||
foreach (var (id, amount) in prototype.RequiredMaterials)
|
||||
foreach (var (id, amount) in prototype.Materials)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<MaterialPrototype>(id, out var proto))
|
||||
if (!_prototypeManager.TryIndex(id, out var proto))
|
||||
continue;
|
||||
|
||||
var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, multiplier);
|
||||
@@ -163,8 +166,9 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
sb.AppendLine(tooltipText);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(prototype.Description))
|
||||
sb.AppendLine(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description)));
|
||||
var desc = _lathe.GetRecipeDescription(prototype);
|
||||
if (!string.IsNullOrWhiteSpace(desc))
|
||||
sb.AppendLine(Loc.GetString("lathe-menu-description-display", ("description", desc)));
|
||||
|
||||
// Remove last newline
|
||||
if (sb.Length > 0)
|
||||
@@ -222,13 +226,10 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
var queuedRecipeBox = new BoxContainer();
|
||||
queuedRecipeBox.Orientation = BoxContainer.LayoutOrientation.Horizontal;
|
||||
|
||||
var queuedRecipeProto = new EntityPrototypeView();
|
||||
queuedRecipeBox.AddChild(queuedRecipeProto);
|
||||
if (_prototypeManager.TryIndex(recipe.Result, out EntityPrototype? entityProto) && entityProto != null)
|
||||
queuedRecipeProto.SetPrototype(entityProto);
|
||||
queuedRecipeBox.AddChild(GetRecipeDisplayControl(recipe));
|
||||
|
||||
var queuedRecipeLabel = new Label();
|
||||
queuedRecipeLabel.Text = $"{idx}. {recipe.Name}";
|
||||
queuedRecipeLabel.Text = $"{idx}. {_lathe.GetRecipeName(recipe)}";
|
||||
queuedRecipeBox.AddChild(queuedRecipeLabel);
|
||||
QueueList.AddChild(queuedRecipeBox);
|
||||
idx++;
|
||||
@@ -241,10 +242,29 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
if (recipe == null)
|
||||
return;
|
||||
|
||||
if (_prototypeManager.TryIndex(recipe.Result, out EntityPrototype? entityProto) && entityProto != null)
|
||||
FabricatingEntityProto.SetPrototype(entityProto);
|
||||
FabricatingDisplayContainer.Children.Clear();
|
||||
FabricatingDisplayContainer.AddChild(GetRecipeDisplayControl(recipe));
|
||||
|
||||
NameLabel.Text = $"{recipe.Name}";
|
||||
NameLabel.Text = _lathe.GetRecipeName(recipe);
|
||||
}
|
||||
|
||||
public Control GetRecipeDisplayControl(LatheRecipePrototype recipe)
|
||||
{
|
||||
if (recipe.Icon != null)
|
||||
{
|
||||
var textRect = new TextureRect();
|
||||
textRect.Texture = _spriteSystem.Frame0(recipe.Icon);
|
||||
return textRect;
|
||||
}
|
||||
|
||||
if (recipe.Result is { } result)
|
||||
{
|
||||
var entProtoView = new EntityPrototypeView();
|
||||
entProtoView.SetPrototype(result);
|
||||
return entProtoView;
|
||||
}
|
||||
|
||||
return new Control();
|
||||
}
|
||||
|
||||
private void OnItemSelected(OptionButton.ItemSelectedEventArgs obj)
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
Margin="0"
|
||||
StyleClasses="ButtonSquare">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<EntityPrototypeView
|
||||
Name="RecipePrototype"
|
||||
<BoxContainer
|
||||
Name="RecipeDisplayContainer"
|
||||
Margin="0 0 4 0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using Content.Shared.Research.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Lathe.UI;
|
||||
|
||||
@@ -14,13 +11,12 @@ public sealed partial class RecipeControl : Control
|
||||
public Action<string>? OnButtonPressed;
|
||||
public Func<string> TooltipTextSupplier;
|
||||
|
||||
public RecipeControl(LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, EntityPrototype? entityPrototype = null)
|
||||
public RecipeControl(LatheSystem latheSystem, LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, Control displayControl)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
RecipeName.Text = recipe.Name;
|
||||
if (entityPrototype != null)
|
||||
RecipePrototype.SetPrototype(entityPrototype);
|
||||
RecipeName.Text = latheSystem.GetRecipeName(recipe);
|
||||
RecipeDisplayContainer.AddChild(displayControl);
|
||||
Button.Disabled = !canProduce;
|
||||
TooltipTextSupplier = tooltipTextSupplier;
|
||||
Button.TooltipSupplier = SupplyTooltip;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Content.Shared.Light.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Animations;
|
||||
@@ -68,7 +68,7 @@ namespace Content.Client.Light.Components
|
||||
|
||||
if (MinDuration > 0)
|
||||
{
|
||||
MaxTime = (float) _random.NextDouble() * (MaxDuration - MinDuration) + MinDuration;
|
||||
MaxTime = (float)_random.NextDouble() * (MaxDuration - MinDuration) + MinDuration;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -192,11 +192,11 @@ namespace Content.Client.Light.Components
|
||||
{
|
||||
if (interpolateValue < 0.5f)
|
||||
{
|
||||
ApplyInterpolation(StartValue, EndValue, interpolateValue*2);
|
||||
ApplyInterpolation(StartValue, EndValue, interpolateValue * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyInterpolation(EndValue, StartValue, (interpolateValue-0.5f)*2);
|
||||
ApplyInterpolation(EndValue, StartValue, (interpolateValue - 0.5f) * 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -238,9 +238,9 @@ namespace Content.Client.Light.Components
|
||||
|
||||
public override void OnInitialize()
|
||||
{
|
||||
_randomValue1 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
|
||||
_randomValue2 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
|
||||
_randomValue3 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
|
||||
_randomValue1 = (float)InterpolateLinear(StartValue, EndValue, (float)_random.NextDouble());
|
||||
_randomValue2 = (float)InterpolateLinear(StartValue, EndValue, (float)_random.NextDouble());
|
||||
_randomValue3 = (float)InterpolateLinear(StartValue, EndValue, (float)_random.NextDouble());
|
||||
}
|
||||
|
||||
public override void OnStart()
|
||||
@@ -258,7 +258,7 @@ namespace Content.Client.Light.Components
|
||||
}
|
||||
|
||||
_randomValue3 = _randomValue4;
|
||||
_randomValue4 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
|
||||
_randomValue4 = (float)InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
|
||||
}
|
||||
|
||||
public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
|
||||
@@ -362,7 +362,7 @@ namespace Content.Client.Light.Components
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
private const string KeyPrefix = nameof(LightBehaviourComponent);
|
||||
public const string KeyPrefix = nameof(LightBehaviourComponent);
|
||||
|
||||
public sealed class AnimationContainer
|
||||
{
|
||||
@@ -387,7 +387,7 @@ namespace Content.Client.Light.Components
|
||||
public readonly List<AnimationContainer> Animations = new();
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
private Dictionary<string, object> _originalPropertyValues = new();
|
||||
public Dictionary<string, object> OriginalPropertyValues = new();
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
@@ -397,155 +397,12 @@ namespace Content.Client.Light.Components
|
||||
{
|
||||
var animation = new Animation()
|
||||
{
|
||||
AnimationTracks = {behaviour}
|
||||
AnimationTracks = { behaviour }
|
||||
};
|
||||
|
||||
Animations.Add(new AnimationContainer(key, animation, behaviour));
|
||||
key++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we disable all the light behaviours we want to be able to revert the light to its original state.
|
||||
/// </summary>
|
||||
private void CopyLightSettings(EntityUid uid, string property)
|
||||
{
|
||||
if (_entMan.TryGetComponent(uid, out PointLightComponent? light))
|
||||
{
|
||||
var propertyValue = AnimationHelper.GetAnimatableProperty(light, property);
|
||||
if (propertyValue != null)
|
||||
{
|
||||
_originalPropertyValues.Add(property, propertyValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning($"{_entMan.GetComponent<MetaDataComponent>(uid).EntityName} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start animating a light behaviour with the specified ID. If the specified ID is empty, it will start animating all light behaviour entries.
|
||||
/// If specified light behaviours are already animating, calling this does nothing.
|
||||
/// Multiple light behaviours can have the same ID.
|
||||
/// </summary>
|
||||
public void StartLightBehaviour(string id = "")
|
||||
{
|
||||
var uid = Owner;
|
||||
if (!_entMan.TryGetComponent(uid, out AnimationPlayerComponent? animation))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var animations = _entMan.System<AnimationPlayerSystem>();
|
||||
|
||||
foreach (var container in Animations)
|
||||
{
|
||||
if (container.LightBehaviour.ID == id || id == string.Empty)
|
||||
{
|
||||
if (!animations.HasRunningAnimation(uid, animation, KeyPrefix + container.Key))
|
||||
{
|
||||
CopyLightSettings(uid, container.LightBehaviour.Property);
|
||||
container.LightBehaviour.UpdatePlaybackValues(container.Animation);
|
||||
animations.Play(uid, animation, container.Animation, KeyPrefix + container.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If any light behaviour with the specified ID is animating, then stop it.
|
||||
/// If no ID is specified then all light behaviours will be stopped.
|
||||
/// Multiple light behaviours can have the same ID.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="removeBehaviour">Should the behaviour(s) also be removed permanently?</param>
|
||||
/// <param name="resetToOriginalSettings">Should the light have its original settings applied?</param>
|
||||
public void StopLightBehaviour(string id = "", bool removeBehaviour = false, bool resetToOriginalSettings = false)
|
||||
{
|
||||
var uid = Owner;
|
||||
if (!_entMan.TryGetComponent(uid, out AnimationPlayerComponent? animation))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var toRemove = new List<AnimationContainer>();
|
||||
var animations = _entMan.System<AnimationPlayerSystem>();
|
||||
|
||||
foreach (var container in Animations)
|
||||
{
|
||||
if (container.LightBehaviour.ID == id || id == string.Empty)
|
||||
{
|
||||
if (animations.HasRunningAnimation(uid, animation, KeyPrefix + container.Key))
|
||||
{
|
||||
animations.Stop(uid, animation, KeyPrefix + container.Key);
|
||||
}
|
||||
|
||||
if (removeBehaviour)
|
||||
{
|
||||
toRemove.Add(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var container in toRemove)
|
||||
{
|
||||
Animations.Remove(container);
|
||||
}
|
||||
|
||||
if (resetToOriginalSettings && _entMan.TryGetComponent(uid, out PointLightComponent? light))
|
||||
{
|
||||
foreach (var (property, value) in _originalPropertyValues)
|
||||
{
|
||||
AnimationHelper.SetAnimatableProperty(light, property, value);
|
||||
}
|
||||
}
|
||||
|
||||
_originalPropertyValues.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if at least one behaviour is running.
|
||||
/// </summary>
|
||||
/// <returns>Whether at least one behaviour is running, false if none is.</returns>
|
||||
public bool HasRunningBehaviours()
|
||||
{
|
||||
var uid = Owner;
|
||||
if (!_entMan.TryGetComponent(uid, out AnimationPlayerComponent? animation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var animations = _entMan.System<AnimationPlayerSystem>();
|
||||
return Animations.Any(container => animations.HasRunningAnimation(uid, animation, KeyPrefix + container.Key));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new light behaviour to the component and start it immediately unless otherwise specified.
|
||||
/// </summary>
|
||||
public void AddNewLightBehaviour(LightBehaviourAnimationTrack behaviour, bool playImmediately = true)
|
||||
{
|
||||
var key = 0;
|
||||
|
||||
while (Animations.Any(x => x.Key == key))
|
||||
{
|
||||
key++;
|
||||
}
|
||||
|
||||
var animation = new Animation()
|
||||
{
|
||||
AnimationTracks = {behaviour}
|
||||
};
|
||||
|
||||
behaviour.Initialize(Owner, _random, _entMan);
|
||||
|
||||
var container = new AnimationContainer(key, animation, behaviour);
|
||||
Animations.Add(container);
|
||||
|
||||
if (playImmediately)
|
||||
{
|
||||
StartLightBehaviour(behaviour.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ public sealed class ExpendableLightSystem : VisualizerSystem<ExpendableLightComp
|
||||
{
|
||||
[Dependency] private readonly PointLightSystem _pointLightSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly LightBehaviorSystem _lightBehavior = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -32,11 +33,11 @@ public sealed class ExpendableLightSystem : VisualizerSystem<ExpendableLightComp
|
||||
if (AppearanceSystem.TryGetData<string>(uid, ExpendableLightVisuals.Behavior, out var lightBehaviourID, args.Component)
|
||||
&& TryComp<LightBehaviourComponent>(uid, out var lightBehaviour))
|
||||
{
|
||||
lightBehaviour.StopLightBehaviour();
|
||||
_lightBehavior.StopLightBehaviour((uid, lightBehaviour));
|
||||
|
||||
if (!string.IsNullOrEmpty(lightBehaviourID))
|
||||
{
|
||||
lightBehaviour.StartLightBehaviour(lightBehaviourID);
|
||||
_lightBehavior.StartLightBehaviour((uid, lightBehaviour), lightBehaviourID);
|
||||
}
|
||||
else if (TryComp<PointLightComponent>(uid, out var light))
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Light.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Animations;
|
||||
|
||||
namespace Content.Client.Light.EntitySystems;
|
||||
|
||||
@@ -36,23 +38,163 @@ public sealed class LightBehaviorSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLightStartup(EntityUid uid, LightBehaviourComponent component, ComponentStartup args)
|
||||
private void OnLightStartup(Entity<LightBehaviourComponent> entity, ref ComponentStartup args)
|
||||
{
|
||||
// TODO: Do NOT ensure component here. And use eventbus events instead...
|
||||
EnsureComp<AnimationPlayerComponent>(uid);
|
||||
EnsureComp<AnimationPlayerComponent>(entity);
|
||||
|
||||
foreach (var container in component.Animations)
|
||||
foreach (var container in entity.Comp.Animations)
|
||||
{
|
||||
container.LightBehaviour.Initialize(uid, _random, EntityManager);
|
||||
container.LightBehaviour.Initialize(entity, _random, EntityManager);
|
||||
}
|
||||
|
||||
// we need to initialize all behaviours before starting any
|
||||
foreach (var container in component.Animations)
|
||||
foreach (var container in entity.Comp.Animations)
|
||||
{
|
||||
if (container.LightBehaviour.Enabled)
|
||||
{
|
||||
component.StartLightBehaviour(container.LightBehaviour.ID);
|
||||
StartLightBehaviour(entity, container.LightBehaviour.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we disable all the light behaviours we want to be able to revert the light to its original state.
|
||||
/// </summary>
|
||||
private void CopyLightSettings(Entity<LightBehaviourComponent> entity, string property)
|
||||
{
|
||||
if (EntityManager.TryGetComponent(entity, out PointLightComponent? light))
|
||||
{
|
||||
var propertyValue = AnimationHelper.GetAnimatableProperty(light, property);
|
||||
if (propertyValue != null)
|
||||
{
|
||||
entity.Comp.OriginalPropertyValues.Add(property, propertyValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"{EntityManager.GetComponent<MetaDataComponent>(entity).EntityName} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start animating a light behaviour with the specified ID. If the specified ID is empty, it will start animating all light behaviour entries.
|
||||
/// If specified light behaviours are already animating, calling this does nothing.
|
||||
/// Multiple light behaviours can have the same ID.
|
||||
/// </summary>
|
||||
public void StartLightBehaviour(Entity<LightBehaviourComponent> entity, string id = "")
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var container in entity.Comp.Animations)
|
||||
{
|
||||
if (container.LightBehaviour.ID == id || id == string.Empty)
|
||||
{
|
||||
if (!_player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key))
|
||||
{
|
||||
CopyLightSettings(entity, container.LightBehaviour.Property);
|
||||
container.LightBehaviour.UpdatePlaybackValues(container.Animation);
|
||||
_player.Play(entity, container.Animation, LightBehaviourComponent.KeyPrefix + container.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If any light behaviour with the specified ID is animating, then stop it.
|
||||
/// If no ID is specified then all light behaviours will be stopped.
|
||||
/// Multiple light behaviours can have the same ID.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="removeBehaviour">Should the behaviour(s) also be removed permanently?</param>
|
||||
/// <param name="resetToOriginalSettings">Should the light have its original settings applied?</param>
|
||||
public void StopLightBehaviour(Entity<LightBehaviourComponent> entity, string id = "", bool removeBehaviour = false, bool resetToOriginalSettings = false)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var comp = entity.Comp;
|
||||
|
||||
var toRemove = new List<LightBehaviourComponent.AnimationContainer>();
|
||||
|
||||
foreach (var container in comp.Animations)
|
||||
{
|
||||
if (container.LightBehaviour.ID == id || id == string.Empty)
|
||||
{
|
||||
if (_player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key))
|
||||
{
|
||||
_player.Stop(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key);
|
||||
}
|
||||
|
||||
if (removeBehaviour)
|
||||
{
|
||||
toRemove.Add(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var container in toRemove)
|
||||
{
|
||||
comp.Animations.Remove(container);
|
||||
}
|
||||
|
||||
if (resetToOriginalSettings && EntityManager.TryGetComponent(entity, out PointLightComponent? light))
|
||||
{
|
||||
foreach (var (property, value) in comp.OriginalPropertyValues)
|
||||
{
|
||||
AnimationHelper.SetAnimatableProperty(light, property, value);
|
||||
}
|
||||
}
|
||||
|
||||
comp.OriginalPropertyValues.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if at least one behaviour is running.
|
||||
/// </summary>
|
||||
/// <returns>Whether at least one behaviour is running, false if none is.</returns>
|
||||
public bool HasRunningBehaviours(Entity<LightBehaviourComponent> entity)
|
||||
{
|
||||
//var uid = Owner;
|
||||
if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return entity.Comp.Animations.Any(container => _player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new light behaviour to the component and start it immediately unless otherwise specified.
|
||||
/// </summary>
|
||||
public void AddNewLightBehaviour(Entity<LightBehaviourComponent> entity, LightBehaviourAnimationTrack behaviour, bool playImmediately = true)
|
||||
{
|
||||
var key = 0;
|
||||
var comp = entity.Comp;
|
||||
|
||||
while (comp.Animations.Any(x => x.Key == key))
|
||||
{
|
||||
key++;
|
||||
}
|
||||
|
||||
var animation = new Animation()
|
||||
{
|
||||
AnimationTracks = { behaviour }
|
||||
};
|
||||
|
||||
behaviour.Initialize(entity.Owner, _random, EntityManager);
|
||||
|
||||
var container = new LightBehaviourComponent.AnimationContainer(key, animation, behaviour);
|
||||
comp.Animations.Add(container);
|
||||
|
||||
if (playImmediately)
|
||||
{
|
||||
StartLightBehaviour(entity, behaviour.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@ using Content.Client.Light.Components;
|
||||
using Content.Shared.Light;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Toggleable;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
using Content.Client.Light.EntitySystems;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
public sealed class HandheldLightSystem : SharedHandheldLightSystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly LightBehaviorSystem _lightBehavior = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -41,9 +41,9 @@ public sealed class HandheldLightSystem : SharedHandheldLightSystem
|
||||
if (TryComp<LightBehaviourComponent>(uid, out var lightBehaviour))
|
||||
{
|
||||
// Reset any running behaviour to reset the animated properties back to the original value, to avoid conflicts between resets
|
||||
if (lightBehaviour.HasRunningBehaviours())
|
||||
if (_lightBehavior.HasRunningBehaviours((uid, lightBehaviour)))
|
||||
{
|
||||
lightBehaviour.StopLightBehaviour(resetToOriginalSettings: true);
|
||||
_lightBehavior.StopLightBehaviour((uid, lightBehaviour), resetToOriginalSettings: true);
|
||||
}
|
||||
|
||||
if (!enabled)
|
||||
@@ -56,10 +56,10 @@ public sealed class HandheldLightSystem : SharedHandheldLightSystem
|
||||
case HandheldLightPowerStates.FullPower:
|
||||
break; // We just needed to reset all behaviours
|
||||
case HandheldLightPowerStates.LowPower:
|
||||
lightBehaviour.StartLightBehaviour(component.RadiatingBehaviourId);
|
||||
_lightBehavior.StartLightBehaviour((uid, lightBehaviour), component.RadiatingBehaviourId);
|
||||
break;
|
||||
case HandheldLightPowerStates.Dying:
|
||||
lightBehaviour.StartLightBehaviour(component.BlinkingBehaviourId);
|
||||
_lightBehavior.StartLightBehaviour((uid, lightBehaviour), component.BlinkingBehaviourId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
|
||||
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
|
||||
[UISystemDependency] private readonly GuidebookSystem _guide = default!;
|
||||
[UISystemDependency] private readonly LoadoutSystem _loadouts = default!;
|
||||
|
||||
private CharacterSetupGui? _characterSetup;
|
||||
private HumanoidProfileEditor? _profileEditor;
|
||||
@@ -272,6 +273,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
_logManager,
|
||||
_playerManager,
|
||||
_prototypeManager,
|
||||
_resourceCache,
|
||||
_requirements,
|
||||
_markings);
|
||||
|
||||
@@ -364,7 +366,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
|
||||
continue;
|
||||
|
||||
_spawn.EquipStartingGear(uid, _prototypeManager.Index(loadoutProto.Equipment));
|
||||
_spawn.EquipStartingGear(uid, loadoutProto);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -387,36 +389,51 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
|
||||
continue;
|
||||
|
||||
// TODO: Need some way to apply starting gear to an entity coz holy fucking shit dude.
|
||||
var loadoutGear = _prototypeManager.Index(loadoutProto.Equipment);
|
||||
|
||||
// TODO: Need some way to apply starting gear to an entity and replace existing stuff coz holy fucking shit dude.
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
var itemType = loadoutGear.GetGear(slot.Name);
|
||||
|
||||
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
|
||||
// Try startinggear first
|
||||
if (_prototypeManager.TryIndex(loadoutProto.StartingGear, out var loadoutGear))
|
||||
{
|
||||
EntityManager.DeleteEntity(unequippedItem.Value);
|
||||
var itemType = ((IEquipmentLoadout) loadoutGear).GetGear(slot.Name);
|
||||
|
||||
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
|
||||
{
|
||||
EntityManager.DeleteEntity(unequippedItem.Value);
|
||||
}
|
||||
|
||||
if (itemType != string.Empty)
|
||||
{
|
||||
var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
|
||||
_inventory.TryEquip(dummy, item, slot.Name, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (itemType != string.Empty)
|
||||
else
|
||||
{
|
||||
var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
|
||||
_inventory.TryEquip(dummy, item, slot.Name, true, true);
|
||||
var itemType = ((IEquipmentLoadout) loadoutProto).GetGear(slot.Name);
|
||||
|
||||
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
|
||||
{
|
||||
EntityManager.DeleteEntity(unequippedItem.Value);
|
||||
}
|
||||
|
||||
if (itemType != string.Empty)
|
||||
{
|
||||
var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
|
||||
_inventory.TryEquip(dummy, item, slot.Name, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (job.StartingGear == null)
|
||||
if (!_prototypeManager.TryIndex(job.StartingGear, out var gear))
|
||||
return;
|
||||
|
||||
var gear = _prototypeManager.Index<StartingGearPrototype>(job.StartingGear);
|
||||
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
var itemType = gear.GetGear(slot.Name);
|
||||
var itemType = ((IEquipmentLoadout) gear).GetGear(slot.Name);
|
||||
|
||||
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
|
||||
{
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
<Button Name="ResetButton" Disabled="True" Text="{Loc 'humanoid-profile-editor-reset-button'}"/>
|
||||
<Button Name="ImportButton" Text="{Loc 'humanoid-profile-editor-import-button'}"/>
|
||||
<Button Name="ExportButton" Text="{Loc 'humanoid-profile-editor-export-button'}"/>
|
||||
<Button Name="ExportImageButton" Text="{Loc 'humanoid-profile-editor-export-image-button'}"/>
|
||||
<Button Name="OpenImagesButton" Text="{Loc 'humanoid-profile-editor-open-image-button'}"/>
|
||||
</BoxContainer>
|
||||
</ui:HighlightedContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Client.Lobby.UI.Loadouts;
|
||||
using Content.Client.Lobby.UI.Roles;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Players.PlayTimeTracking;
|
||||
using Content.Client.Sprite;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Systems.Guidebook;
|
||||
using Content.Shared._CP14.Humanoid;
|
||||
@@ -28,6 +29,7 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -44,6 +46,7 @@ namespace Content.Client.Lobby.UI
|
||||
private readonly IFileDialogManager _dialogManager;
|
||||
private readonly IPlayerManager _playerManager;
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
private readonly IResourceManager _resManager;
|
||||
private readonly MarkingManager _markingManager;
|
||||
private readonly JobRequirementsManager _requirements;
|
||||
private readonly LobbyUIController _controller;
|
||||
@@ -55,6 +58,7 @@ namespace Content.Client.Lobby.UI
|
||||
private LoadoutWindow? _loadoutWindow;
|
||||
|
||||
private bool _exporting;
|
||||
private bool _imaging;
|
||||
|
||||
/// <summary>
|
||||
/// If we're attempting to save.
|
||||
@@ -108,6 +112,7 @@ namespace Content.Client.Lobby.UI
|
||||
ILogManager logManager,
|
||||
IPlayerManager playerManager,
|
||||
IPrototypeManager prototypeManager,
|
||||
IResourceManager resManager,
|
||||
JobRequirementsManager requirements,
|
||||
MarkingManager markings)
|
||||
{
|
||||
@@ -120,6 +125,7 @@ namespace Content.Client.Lobby.UI
|
||||
_prototypeManager = prototypeManager;
|
||||
_markingManager = markings;
|
||||
_preferencesManager = preferencesManager;
|
||||
_resManager = resManager;
|
||||
_requirements = requirements;
|
||||
_controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
|
||||
@@ -133,6 +139,16 @@ namespace Content.Client.Lobby.UI
|
||||
ExportProfile();
|
||||
};
|
||||
|
||||
ExportImageButton.OnPressed += args =>
|
||||
{
|
||||
ExportImage();
|
||||
};
|
||||
|
||||
OpenImagesButton.OnPressed += args =>
|
||||
{
|
||||
_resManager.UserData.OpenOsWindow(ContentSpriteSystem.Exports);
|
||||
};
|
||||
|
||||
ResetButton.OnPressed += args =>
|
||||
{
|
||||
SetProfile((HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter, _preferencesManager.Preferences?.SelectedCharacterIndex);
|
||||
@@ -425,7 +441,6 @@ namespace Content.Client.Lobby.UI
|
||||
SpeciesInfoButton.OnPressed += OnSpeciesInfoButtonPressed;
|
||||
|
||||
UpdateSpeciesGuidebookIcon();
|
||||
ReloadPreview();
|
||||
IsDirty = false;
|
||||
}
|
||||
|
||||
@@ -635,7 +650,7 @@ namespace Content.Client.Lobby.UI
|
||||
selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
|
||||
|
||||
var requirements = _entManager.System<SharedRoleSystem>().GetAntagRequirement(antag);
|
||||
if (!_requirements.CheckRoleTime(requirements, out var reason))
|
||||
if (!_requirements.CheckRoleRequirements(requirements, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
|
||||
{
|
||||
selector.LockRequirements(reason);
|
||||
Profile = Profile?.WithAntagPreference(antag.ID, false);
|
||||
@@ -698,11 +713,12 @@ namespace Content.Client.Lobby.UI
|
||||
_entManager.DeleteEntity(PreviewDummy);
|
||||
PreviewDummy = EntityUid.Invalid;
|
||||
|
||||
if (Profile == null || !_prototypeManager.HasIndex<SpeciesPrototype>(Profile.Species))
|
||||
if (Profile == null || !_prototypeManager.HasIndex(Profile.Species))
|
||||
return;
|
||||
|
||||
PreviewDummy = _controller.LoadProfileEntity(Profile, JobOverride, ShowClothes.Pressed);
|
||||
SpriteView.SetEntity(PreviewDummy);
|
||||
_entManager.System<MetaDataSystem>().SetEntityName(PreviewDummy, Profile.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -888,7 +904,7 @@ namespace Content.Client.Lobby.UI
|
||||
icon.Texture = jobIcon.Icon.Frame0();
|
||||
selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon, job.Guides);
|
||||
|
||||
if (!_requirements.IsAllowed(job, out var reason))
|
||||
if (!_requirements.IsAllowed(job, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
|
||||
{
|
||||
selector.LockRequirements(reason);
|
||||
}
|
||||
@@ -1139,6 +1155,17 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
_loadoutWindow?.Dispose();
|
||||
_loadoutWindow = null;
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
base.EnteredTree();
|
||||
ReloadPreview();
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
_entManager.DeleteEntity(PreviewDummy);
|
||||
PreviewDummy = EntityUid.Invalid;
|
||||
}
|
||||
@@ -1199,6 +1226,11 @@ namespace Content.Client.Lobby.UI
|
||||
{
|
||||
Profile = Profile?.WithName(newName);
|
||||
SetDirty();
|
||||
|
||||
if (!IsDirty)
|
||||
return;
|
||||
|
||||
_entManager.System<MetaDataSystem>().SetEntityName(PreviewDummy, newName);
|
||||
}
|
||||
|
||||
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
|
||||
@@ -1543,6 +1575,19 @@ namespace Content.Client.Lobby.UI
|
||||
UpdateNameEdit();
|
||||
}
|
||||
|
||||
private async void ExportImage()
|
||||
{
|
||||
if (_imaging)
|
||||
return;
|
||||
|
||||
var dir = SpriteView.OverrideDirection ?? Direction.South;
|
||||
|
||||
// I tried disabling the button but it looks sorta goofy as it only takes a frame or two to save
|
||||
_imaging = true;
|
||||
await _entManager.System<ContentSpriteSystem>().Export(PreviewDummy, dir, includeId: false);
|
||||
_imaging = false;
|
||||
}
|
||||
|
||||
private async void ImportProfile()
|
||||
{
|
||||
if (_exporting || CharacterSlot == null || Profile == null)
|
||||
|
||||
8
Content.Client/Mapping/MappingActionsButton.xaml
Normal file
8
Content.Client/Mapping/MappingActionsButton.xaml
Normal file
@@ -0,0 +1,8 @@
|
||||
<mapping:MappingActionsButton
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:mapping="clr-namespace:Content.Client.Mapping"
|
||||
StyleClasses="ButtonSquare" ToggleMode="True" SetSize="32 32" Margin="0 0 5 0"
|
||||
TooltipDelay="0">
|
||||
<TextureRect Name="Texture" Access="Public" Stretch="Scale" SetSize="16 16"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</mapping:MappingActionsButton>
|
||||
15
Content.Client/Mapping/MappingActionsButton.xaml.cs
Normal file
15
Content.Client/Mapping/MappingActionsButton.xaml.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MappingActionsButton : Button
|
||||
{
|
||||
public MappingActionsButton()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
|
||||
4
Content.Client/Mapping/MappingDoNotMeasure.xaml
Normal file
4
Content.Client/Mapping/MappingDoNotMeasure.xaml
Normal file
@@ -0,0 +1,4 @@
|
||||
<mapping:MappingDoNotMeasure
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:mapping="clr-namespace:Content.Client.Mapping">
|
||||
</mapping:MappingDoNotMeasure>
|
||||
21
Content.Client/Mapping/MappingDoNotMeasure.xaml.cs
Normal file
21
Content.Client/Mapping/MappingDoNotMeasure.xaml.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MappingDoNotMeasure : Control
|
||||
{
|
||||
public MappingDoNotMeasure()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
69
Content.Client/Mapping/MappingManager.cs
Normal file
69
Content.Client/Mapping/MappingManager.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Mapping;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
public sealed class MappingManager : IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IFileDialogManager _file = default!;
|
||||
[Dependency] private readonly IClientNetManager _net = default!;
|
||||
|
||||
private Stream? _saveStream;
|
||||
private MappingMapDataMessage? _mapData;
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
_net.RegisterNetMessage<MappingSaveMapMessage>();
|
||||
_net.RegisterNetMessage<MappingSaveMapErrorMessage>(OnSaveError);
|
||||
_net.RegisterNetMessage<MappingMapDataMessage>(OnMapData);
|
||||
}
|
||||
|
||||
private void OnSaveError(MappingSaveMapErrorMessage message)
|
||||
{
|
||||
_saveStream?.DisposeAsync();
|
||||
_saveStream = null;
|
||||
}
|
||||
|
||||
private async void OnMapData(MappingMapDataMessage message)
|
||||
{
|
||||
if (_saveStream == null)
|
||||
{
|
||||
_mapData = message;
|
||||
return;
|
||||
}
|
||||
|
||||
await _saveStream.WriteAsync(Encoding.ASCII.GetBytes(message.Yml));
|
||||
await _saveStream.DisposeAsync();
|
||||
|
||||
_saveStream = null;
|
||||
_mapData = null;
|
||||
}
|
||||
|
||||
public async Task SaveMap()
|
||||
{
|
||||
if (_saveStream != null)
|
||||
await _saveStream.DisposeAsync();
|
||||
|
||||
var request = new MappingSaveMapMessage();
|
||||
_net.ClientSendMessage(request);
|
||||
|
||||
var path = await _file.SaveFile();
|
||||
if (path is not { fileStream: var stream })
|
||||
return;
|
||||
|
||||
if (_mapData != null)
|
||||
{
|
||||
await stream.WriteAsync(Encoding.ASCII.GetBytes(_mapData.Yml));
|
||||
_mapData = null;
|
||||
await stream.FlushAsync();
|
||||
await stream.DisposeAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
_saveStream = stream;
|
||||
}
|
||||
}
|
||||
84
Content.Client/Mapping/MappingOverlay.cs
Normal file
84
Content.Client/Mapping/MappingOverlay.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Client.Mapping.MappingState;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
public sealed class MappingOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
|
||||
// 1 off in case something else uses these colors since we use them to compare
|
||||
private static readonly Color PickColor = new(1, 255, 0);
|
||||
private static readonly Color DeleteColor = new(255, 1, 0);
|
||||
|
||||
private readonly Dictionary<EntityUid, Color> _oldColors = new();
|
||||
|
||||
private readonly MappingState _state;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public MappingOverlay(MappingState state)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_state = state;
|
||||
_shader = _prototypes.Index<ShaderPrototype>("unshaded").Instance();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
foreach (var (id, color) in _oldColors)
|
||||
{
|
||||
if (!_entities.TryGetComponent(id, out SpriteComponent? sprite))
|
||||
continue;
|
||||
|
||||
if (sprite.Color == DeleteColor || sprite.Color == PickColor)
|
||||
sprite.Color = color;
|
||||
}
|
||||
|
||||
_oldColors.Clear();
|
||||
|
||||
if (_player.LocalEntity == null)
|
||||
return;
|
||||
|
||||
var handle = args.WorldHandle;
|
||||
handle.UseShader(_shader);
|
||||
|
||||
switch (_state.State)
|
||||
{
|
||||
case CursorState.Pick:
|
||||
{
|
||||
if (_state.GetHoveredEntity() is { } entity &&
|
||||
_entities.TryGetComponent(entity, out SpriteComponent? sprite))
|
||||
{
|
||||
_oldColors[entity] = sprite.Color;
|
||||
sprite.Color = PickColor;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case CursorState.Delete:
|
||||
{
|
||||
if (_state.GetHoveredEntity() is { } entity &&
|
||||
_entities.TryGetComponent(entity, out SpriteComponent? sprite))
|
||||
{
|
||||
_oldColors[entity] = sprite.Color;
|
||||
sprite.Color = DeleteColor;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handle.UseShader(null);
|
||||
}
|
||||
}
|
||||
39
Content.Client/Mapping/MappingPrototype.cs
Normal file
39
Content.Client/Mapping/MappingPrototype.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
/// <summary>
|
||||
/// Used to represent a button's data in the mapping editor.
|
||||
/// </summary>
|
||||
public sealed class MappingPrototype
|
||||
{
|
||||
/// <summary>
|
||||
/// The prototype instance, if any.
|
||||
/// Can be one of <see cref="EntityPrototype"/>, <see cref="ContentTileDefinition"/> or <see cref="DecalPrototype"/>
|
||||
/// If null, this is a top-level button (such as Entities, Tiles or Decals)
|
||||
/// </summary>
|
||||
public readonly IPrototype? Prototype;
|
||||
|
||||
/// <summary>
|
||||
/// The text to display on the UI for this button.
|
||||
/// </summary>
|
||||
public readonly string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Which other prototypes (buttons) this one is nested inside of.
|
||||
/// </summary>
|
||||
public List<MappingPrototype>? Parents;
|
||||
|
||||
/// <summary>
|
||||
/// Which other prototypes (buttons) are nested inside this one.
|
||||
/// </summary>
|
||||
public List<MappingPrototype>? Children;
|
||||
|
||||
public MappingPrototype(IPrototype? prototype, string name)
|
||||
{
|
||||
Prototype = prototype;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
21
Content.Client/Mapping/MappingPrototypeList.xaml
Normal file
21
Content.Client/Mapping/MappingPrototypeList.xaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<mapping:MappingPrototypeList
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:mapping="clr-namespace:Content.Client.Mapping">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="CollapseAllButton" Access="Public" Text="-" SetSize="48 48"
|
||||
StyleClasses="ButtonSquare" ToolTip="Collapse All" TooltipDelay="0" />
|
||||
<LineEdit Name="SearchBar" SetHeight="48" HorizontalExpand="True" Access="Public" />
|
||||
<Button Name="ClearSearchButton" Access="Public" Text="X" SetSize="48 48"
|
||||
StyleClasses="ButtonSquare" />
|
||||
</BoxContainer>
|
||||
<ScrollContainer Name="ScrollContainer" Access="Public" VerticalExpand="True"
|
||||
ReserveScrollbarSpace="True">
|
||||
<BoxContainer Name="PrototypeList" Access="Public" Orientation="Vertical" />
|
||||
<PrototypeListContainer Name="SearchList" Access="Public" Visible="False" />
|
||||
</ScrollContainer>
|
||||
<mapping:MappingDoNotMeasure Visible="False">
|
||||
<mapping:MappingSpawnButton Name="MeasureButton" Access="Public" />
|
||||
</mapping:MappingDoNotMeasure>
|
||||
</BoxContainer>
|
||||
</mapping:MappingPrototypeList>
|
||||
170
Content.Client/Mapping/MappingPrototypeList.xaml.cs
Normal file
170
Content.Client/Mapping/MappingPrototypeList.xaml.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MappingPrototypeList : Control
|
||||
{
|
||||
private (int start, int end) _lastIndices;
|
||||
private readonly List<MappingPrototype> _prototypes = new();
|
||||
private readonly List<Texture> _insertTextures = new();
|
||||
private readonly List<MappingPrototype> _search = new();
|
||||
|
||||
public MappingSpawnButton? Selected;
|
||||
public Action<IPrototype, List<Texture>>? GetPrototypeData;
|
||||
public event Action<MappingSpawnButton, IPrototype?>? SelectionChanged;
|
||||
public event Action<MappingSpawnButton, ButtonToggledEventArgs>? CollapseToggled;
|
||||
|
||||
public MappingPrototypeList()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
MeasureButton.Measure(Vector2Helpers.Infinity);
|
||||
|
||||
ScrollContainer.OnScrolled += UpdateSearch;
|
||||
OnResized += UpdateSearch;
|
||||
}
|
||||
|
||||
public void UpdateVisible(List<MappingPrototype> prototypes)
|
||||
{
|
||||
_prototypes.Clear();
|
||||
|
||||
PrototypeList.DisposeAllChildren();
|
||||
|
||||
_prototypes.AddRange(prototypes);
|
||||
|
||||
Selected = null;
|
||||
ScrollContainer.SetScrollValue(new Vector2(0, 0));
|
||||
|
||||
foreach (var prototype in _prototypes)
|
||||
{
|
||||
Insert(PrototypeList, prototype, true);
|
||||
}
|
||||
}
|
||||
|
||||
public MappingSpawnButton Insert(Container list, MappingPrototype mapping, bool includeChildren)
|
||||
{
|
||||
var prototype = mapping.Prototype;
|
||||
|
||||
_insertTextures.Clear();
|
||||
|
||||
if (prototype != null)
|
||||
GetPrototypeData?.Invoke(prototype, _insertTextures);
|
||||
|
||||
var button = new MappingSpawnButton { Prototype = mapping };
|
||||
button.Label.Text = mapping.Name;
|
||||
|
||||
if (_insertTextures.Count > 0)
|
||||
{
|
||||
button.Texture.Textures.AddRange(_insertTextures);
|
||||
button.Texture.InvalidateMeasure();
|
||||
}
|
||||
else
|
||||
{
|
||||
button.Texture.Visible = false;
|
||||
}
|
||||
|
||||
if (prototype != null && button.Prototype == Selected?.Prototype)
|
||||
{
|
||||
Selected = button;
|
||||
button.Button.Pressed = true;
|
||||
}
|
||||
|
||||
list.AddChild(button);
|
||||
|
||||
button.Button.OnToggled += _ => SelectionChanged?.Invoke(button, prototype);
|
||||
|
||||
if (includeChildren && mapping.Children?.Count > 0)
|
||||
{
|
||||
button.CollapseButton.Visible = true;
|
||||
button.CollapseButton.OnToggled += args => CollapseToggled?.Invoke(button, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
button.CollapseButtonWrapper.Visible = false;
|
||||
button.CollapseButton.Visible = false;
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
public void Search(List<MappingPrototype> prototypes)
|
||||
{
|
||||
_search.Clear();
|
||||
SearchList.DisposeAllChildren();
|
||||
_lastIndices = (0, -1);
|
||||
|
||||
_search.AddRange(prototypes);
|
||||
SearchList.TotalItemCount = _search.Count;
|
||||
ScrollContainer.SetScrollValue(new Vector2(0, 0));
|
||||
|
||||
UpdateSearch();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a virtual list where not all buttons exist at one time, since there may be thousands of them.
|
||||
/// </summary>
|
||||
private void UpdateSearch()
|
||||
{
|
||||
if (!SearchList.Visible)
|
||||
return;
|
||||
|
||||
var height = MeasureButton.DesiredSize.Y + PrototypeListContainer.Separation;
|
||||
var offset = Math.Max(-SearchList.Position.Y, 0);
|
||||
var startIndex = (int) Math.Floor(offset / height);
|
||||
SearchList.ItemOffset = startIndex;
|
||||
|
||||
var (prevStart, prevEnd) = _lastIndices;
|
||||
var endIndex = startIndex - 1;
|
||||
var spaceUsed = -height;
|
||||
|
||||
// calculate how far down we are scrolled
|
||||
while (spaceUsed < SearchList.Parent!.Height)
|
||||
{
|
||||
spaceUsed += height;
|
||||
endIndex += 1;
|
||||
}
|
||||
|
||||
endIndex = Math.Min(endIndex, _search.Count - 1);
|
||||
|
||||
// nothing changed in terms of which buttons are visible now and before
|
||||
if (endIndex == prevEnd && startIndex == prevStart)
|
||||
return;
|
||||
|
||||
_lastIndices = (startIndex, endIndex);
|
||||
|
||||
// remove previously seen but now unseen buttons from the top
|
||||
for (var i = prevStart; i < startIndex && i <= prevEnd; i++)
|
||||
{
|
||||
var control = SearchList.GetChild(0);
|
||||
SearchList.RemoveChild(control);
|
||||
}
|
||||
|
||||
// remove previously seen but now unseen buttons from the bottom
|
||||
for (var i = prevEnd; i > endIndex && i >= prevStart; i--)
|
||||
{
|
||||
var control = SearchList.GetChild(SearchList.ChildCount - 1);
|
||||
SearchList.RemoveChild(control);
|
||||
}
|
||||
|
||||
// insert buttons that can now be seen, from the start
|
||||
for (var i = Math.Min(prevStart - 1, endIndex); i >= startIndex; i--)
|
||||
{
|
||||
Insert(SearchList, _search[i], false).SetPositionInParent(0);
|
||||
}
|
||||
|
||||
// insert buttons that can now be seen, from the end
|
||||
for (var i = Math.Max(prevEnd + 1, startIndex); i <= endIndex; i++)
|
||||
{
|
||||
Insert(SearchList, _search[i], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
85
Content.Client/Mapping/MappingScreen.xaml
Normal file
85
Content.Client/Mapping/MappingScreen.xaml
Normal file
@@ -0,0 +1,85 @@
|
||||
<mapping:MappingScreen
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Chat.Widgets"
|
||||
xmlns:hotbar="clr-namespace:Content.Client.UserInterface.Systems.Hotbar.Widgets"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:mapping="clr-namespace:Content.Client.Mapping"
|
||||
VerticalExpand="False"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Center">
|
||||
<controls:RecordedSplitContainer Name="ScreenContainer" HorizontalExpand="True"
|
||||
VerticalExpand="True" SplitWidth="0"
|
||||
StretchDirection="TopLeft">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" Name="SpawnContainer" MinWidth="200" SetWidth="600">
|
||||
<mapping:MappingPrototypeList Name="Prototypes" Access="Public" VerticalExpand="True" />
|
||||
<BoxContainer Name="DecalContainer" Access="Public" Orientation="Horizontal"
|
||||
Visible="False">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<ColorSelectorSliders Name="DecalColorPicker" IsAlphaVisible="True" />
|
||||
<Button Name="DecalPickerOpen" Text="{Loc decal-placer-window-palette}"
|
||||
StyleClasses="ButtonSquare" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<CheckBox Name="DecalEnableAuto" Margin="0 0 0 10"
|
||||
Text="{Loc decal-placer-window-enable-auto}" />
|
||||
<CheckBox Name="DecalEnableSnap"
|
||||
Text="{Loc decal-placer-window-enable-snap}" />
|
||||
<CheckBox Name="DecalEnableCleanable"
|
||||
Text="{Loc decal-placer-window-enable-cleanable}" />
|
||||
<BoxContainer Name="DecalSpinBoxContainer" Orientation="Horizontal">
|
||||
<Label Text="{Loc decal-placer-window-rotation}" Margin="0 0 0 1" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc decal-placer-window-zindex}" Margin="0 0 0 1" />
|
||||
<SpinBox Name="DecalZIndexSpinBox" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="EntityContainer" Access="Public" Orientation="Horizontal"
|
||||
Visible="False">
|
||||
<Button Name="EntityReplaceButton" Access="Public" ToggleMode="True"
|
||||
SetHeight="48"
|
||||
StyleClasses="ButtonSquare" Text="{Loc 'mapping-replace'}" HorizontalExpand="True" />
|
||||
<OptionButton Name="EntityPlacementMode" Access="Public"
|
||||
SetHeight="48"
|
||||
StyleClasses="ButtonSquare" TooltipDelay="0"
|
||||
ToolTip="{Loc entity-spawn-window-override-menu-tooltip}"
|
||||
HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="EraseEntityButton" Access="Public" HorizontalExpand="True"
|
||||
SetHeight="48"
|
||||
ToggleMode="True" Text="{Loc 'mapping-erase-entity'}" StyleClasses="ButtonSquare" />
|
||||
<Button Name="EraseDecalButton" Access="Public" HorizontalExpand="True"
|
||||
SetHeight="48"
|
||||
ToggleMode="True" Text="{Loc 'mapping-erase-decal'}" StyleClasses="ButtonSquare" />
|
||||
</BoxContainer>
|
||||
<widgets:ChatBox Visible="False" />
|
||||
</BoxContainer>
|
||||
<LayoutContainer Name="ViewportContainer" HorizontalExpand="True" VerticalExpand="True">
|
||||
<controls:MainViewport Name="MainViewport"/>
|
||||
<hotbar:HotbarGui Name="Hotbar" />
|
||||
<PanelContainer Name="Actions" VerticalExpand="True" HorizontalExpand="True"
|
||||
MaxHeight="48">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#222222AA" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Horizontal" Margin="15 10">
|
||||
<mapping:MappingActionsButton
|
||||
Name="Add" Access="Public" Disabled="True" ToolTip="" Visible="False" />
|
||||
<mapping:MappingActionsButton Name="Fill" Access="Public"
|
||||
ToolTip="" Visible="False" />
|
||||
<mapping:MappingActionsButton Name="Grab" Access="Public"
|
||||
ToolTip="" Visible="False" />
|
||||
<mapping:MappingActionsButton Name="Move" Access="Public"
|
||||
ToolTip="" Visible="False" />
|
||||
<mapping:MappingActionsButton Name="Pick" Access="Public"
|
||||
ToolTip="Pick (Hold 5)" />
|
||||
<mapping:MappingActionsButton Name="Delete" Access="Public"
|
||||
ToolTip="Delete (Hold 6)" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</LayoutContainer>
|
||||
</controls:RecordedSplitContainer>
|
||||
</mapping:MappingScreen>
|
||||
197
Content.Client/Mapping/MappingScreen.xaml.cs
Normal file
197
Content.Client/Mapping/MappingScreen.xaml.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Decals;
|
||||
using Content.Client.Decals.UI;
|
||||
using Content.Client.UserInterface.Screens;
|
||||
using Content.Client.UserInterface.Systems.Chat.Widgets;
|
||||
using Content.Shared.Decals;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MappingScreen : InGameScreen
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public DecalPlacementSystem DecalSystem = default!;
|
||||
|
||||
private PaletteColorPicker? _picker;
|
||||
|
||||
private ProtoId<DecalPrototype>? _id;
|
||||
private Color _decalColor = Color.White;
|
||||
private float _decalRotation;
|
||||
private bool _decalSnap;
|
||||
private int _decalZIndex;
|
||||
private bool _decalCleanable;
|
||||
|
||||
private bool _decalAuto;
|
||||
|
||||
public override ChatBox ChatBox => GetWidget<ChatBox>()!;
|
||||
|
||||
public event Func<MappingSpawnButton, bool>? IsDecalVisible;
|
||||
|
||||
public MappingScreen()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
AutoscaleMaxResolution = new Vector2i(1080, 770);
|
||||
|
||||
SetAnchorPreset(ScreenContainer, LayoutPreset.Wide);
|
||||
SetAnchorPreset(ViewportContainer, LayoutPreset.Wide);
|
||||
SetAnchorPreset(SpawnContainer, LayoutPreset.Wide);
|
||||
SetAnchorPreset(MainViewport, LayoutPreset.Wide);
|
||||
SetAnchorAndMarginPreset(Hotbar, LayoutPreset.BottomWide, margin: 5);
|
||||
SetAnchorAndMarginPreset(Actions, LayoutPreset.TopWide, margin: 5);
|
||||
|
||||
ScreenContainer.OnSplitResizeFinished += () =>
|
||||
OnChatResized?.Invoke(new Vector2(ScreenContainer.SplitFraction, 0));
|
||||
|
||||
var rotationSpinBox = new FloatSpinBox(90.0f, 0)
|
||||
{
|
||||
HorizontalExpand = true
|
||||
};
|
||||
DecalSpinBoxContainer.AddChild(rotationSpinBox);
|
||||
|
||||
DecalColorPicker.OnColorChanged += OnDecalColorPicked;
|
||||
DecalPickerOpen.OnPressed += OnDecalPickerOpenPressed;
|
||||
rotationSpinBox.OnValueChanged += args =>
|
||||
{
|
||||
_decalRotation = args.Value;
|
||||
UpdateDecal();
|
||||
};
|
||||
DecalEnableAuto.OnToggled += args =>
|
||||
{
|
||||
_decalAuto = args.Pressed;
|
||||
if (_id is { } id)
|
||||
SelectDecal(id);
|
||||
};
|
||||
DecalEnableSnap.OnToggled += args =>
|
||||
{
|
||||
_decalSnap = args.Pressed;
|
||||
UpdateDecal();
|
||||
};
|
||||
DecalEnableCleanable.OnToggled += args =>
|
||||
{
|
||||
_decalCleanable = args.Pressed;
|
||||
UpdateDecal();
|
||||
};
|
||||
DecalZIndexSpinBox.ValueChanged += args =>
|
||||
{
|
||||
_decalZIndex = args.Value;
|
||||
UpdateDecal();
|
||||
};
|
||||
|
||||
for (var i = 0; i < EntitySpawnWindow.InitOpts.Length; i++)
|
||||
{
|
||||
EntityPlacementMode.AddItem(EntitySpawnWindow.InitOpts[i], i);
|
||||
}
|
||||
|
||||
Pick.Texture.TexturePath = "/Textures/Interface/eyedropper.svg.png";
|
||||
Delete.Texture.TexturePath = "/Textures/Interface/eraser.svg.png";
|
||||
}
|
||||
|
||||
private void OnDecalColorPicked(Color color)
|
||||
{
|
||||
_decalColor = color;
|
||||
DecalColorPicker.Color = color;
|
||||
UpdateDecal();
|
||||
}
|
||||
|
||||
private void OnDecalPickerOpenPressed(ButtonEventArgs obj)
|
||||
{
|
||||
if (_picker == null)
|
||||
{
|
||||
_picker = new PaletteColorPicker();
|
||||
_picker.OpenToLeft();
|
||||
_picker.PaletteList.OnItemSelected += args =>
|
||||
{
|
||||
var color = ((Color?) args.ItemList.GetSelected().First().Metadata)!.Value;
|
||||
OnDecalColorPicked(color);
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_picker.IsOpen)
|
||||
_picker.Close();
|
||||
else
|
||||
_picker.Open();
|
||||
}
|
||||
|
||||
private void UpdateDecal()
|
||||
{
|
||||
if (_id is not { } id)
|
||||
return;
|
||||
|
||||
DecalSystem.UpdateDecalInfo(id, _decalColor, _decalRotation, _decalSnap, _decalZIndex, _decalCleanable);
|
||||
}
|
||||
|
||||
public void SelectDecal(string decalId)
|
||||
{
|
||||
if (!_prototype.TryIndex<DecalPrototype>(decalId, out var decal))
|
||||
return;
|
||||
|
||||
_id = decalId;
|
||||
|
||||
if (_decalAuto)
|
||||
{
|
||||
_decalColor = Color.White;
|
||||
_decalCleanable = decal.DefaultCleanable;
|
||||
_decalSnap = decal.DefaultSnap;
|
||||
|
||||
DecalColorPicker.Color = _decalColor;
|
||||
DecalEnableCleanable.Pressed = _decalCleanable;
|
||||
DecalEnableSnap.Pressed = _decalSnap;
|
||||
}
|
||||
|
||||
UpdateDecal();
|
||||
RefreshList();
|
||||
}
|
||||
|
||||
private void RefreshList()
|
||||
{
|
||||
foreach (var control in Prototypes.Children)
|
||||
{
|
||||
if (control is not MappingSpawnButton button ||
|
||||
button.Prototype?.Prototype is not DecalPrototype)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var child in button.Children)
|
||||
{
|
||||
if (child is not MappingSpawnButton { Prototype.Prototype: DecalPrototype } childButton)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
childButton.Texture.Modulate = _decalColor;
|
||||
childButton.Visible = IsDecalVisible?.Invoke(childButton) ?? true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetChatSize(Vector2 size)
|
||||
{
|
||||
ScreenContainer.DesiredSplitCenter = size.X;
|
||||
ScreenContainer.ResizeMode = SplitContainer.SplitResizeMode.RespectChildrenMinSize;
|
||||
}
|
||||
|
||||
public void UnPressActionsExcept(Control except)
|
||||
{
|
||||
Add.Pressed = Add == except;
|
||||
Fill.Pressed = Fill == except;
|
||||
Grab.Pressed = Grab == except;
|
||||
Move.Pressed = Move == except;
|
||||
Pick.Pressed = Pick == except;
|
||||
Delete.Pressed = Delete == except;
|
||||
}
|
||||
}
|
||||
26
Content.Client/Mapping/MappingSpawnButton.xaml
Normal file
26
Content.Client/Mapping/MappingSpawnButton.xaml
Normal file
@@ -0,0 +1,26 @@
|
||||
<mapping:MappingSpawnButton
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:mapping="clr-namespace:Content.Client.Mapping">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Control>
|
||||
<Button Name="Button" Access="Public" ToggleMode="True" StyleClasses="ButtonSquare" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<LayeredTextureRect Name="Texture" Access="Public" MinSize="48 48"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Stretch="KeepAspectCentered" CanShrink="True" />
|
||||
<Control SetSize="48 48" Access="Public" Name="CollapseButtonWrapper">
|
||||
<Button Name="CollapseButton" Access="Public" Text="▶"
|
||||
ToggleMode="True" StyleClasses="ButtonSquare" SetSize="48 48" />
|
||||
</Control>
|
||||
<Label Name="Label" Access="Public"
|
||||
VAlign="Center"
|
||||
VerticalExpand="True"
|
||||
MinHeight="48"
|
||||
Margin="5 0"
|
||||
HorizontalExpand="True" ClipText="True" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
<BoxContainer Name="ChildrenPrototypes" Access="Public" Orientation="Vertical"
|
||||
Margin="24 0 0 0" />
|
||||
</BoxContainer>
|
||||
</mapping:MappingSpawnButton>
|
||||
16
Content.Client/Mapping/MappingSpawnButton.xaml.cs
Normal file
16
Content.Client/Mapping/MappingSpawnButton.xaml.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MappingSpawnButton : Control
|
||||
{
|
||||
public MappingPrototype? Prototype;
|
||||
|
||||
public MappingSpawnButton()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
936
Content.Client/Mapping/MappingState.cs
Normal file
936
Content.Client/Mapping/MappingState.cs
Normal file
@@ -0,0 +1,936 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.ContextMenu.UI;
|
||||
using Content.Client.Decals;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Client.Verbs;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using static System.StringComparison;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
using static Robust.Client.UserInterface.Controls.LineEdit;
|
||||
using static Robust.Client.UserInterface.Controls.OptionButton;
|
||||
using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
public sealed class MappingState : GameplayStateBase
|
||||
{
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityNetworkManager _entityNetwork = default!;
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
[Dependency] private readonly IMapManager _mapMan = default!;
|
||||
[Dependency] private readonly MappingManager _mapping = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlays = default!;
|
||||
[Dependency] private readonly IPlacementManager _placement = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resources = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private EntityMenuUIController _entityMenuController = default!;
|
||||
|
||||
private DecalPlacementSystem _decal = default!;
|
||||
private SpriteSystem _sprite = default!;
|
||||
private TransformSystem _transform = default!;
|
||||
private VerbSystem _verbs = default!;
|
||||
|
||||
private readonly ISawmill _sawmill;
|
||||
private readonly GameplayStateLoadController _loadController;
|
||||
private bool _setup;
|
||||
private readonly List<MappingPrototype> _allPrototypes = new();
|
||||
private readonly Dictionary<IPrototype, MappingPrototype> _allPrototypesDict = new();
|
||||
private readonly Dictionary<Type, Dictionary<string, MappingPrototype>> _idDict = new();
|
||||
private readonly List<MappingPrototype> _prototypes = new();
|
||||
private (TimeSpan At, MappingSpawnButton Button)? _lastClicked;
|
||||
private Control? _scrollTo;
|
||||
private bool _updatePlacement;
|
||||
private bool _updateEraseDecal;
|
||||
|
||||
private MappingScreen Screen => (MappingScreen) UserInterfaceManager.ActiveScreen!;
|
||||
private MainViewport Viewport => UserInterfaceManager.ActiveScreen!.GetWidget<MainViewport>()!;
|
||||
|
||||
public CursorState State { get; set; }
|
||||
|
||||
public MappingState()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_sawmill = _log.GetSawmill("mapping");
|
||||
_loadController = UserInterfaceManager.GetUIController<GameplayStateLoadController>();
|
||||
}
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
EnsureSetup();
|
||||
base.Startup();
|
||||
|
||||
UserInterfaceManager.LoadScreen<MappingScreen>();
|
||||
_loadController.LoadScreen();
|
||||
|
||||
var context = _input.Contexts.GetContext("common");
|
||||
context.AddFunction(ContentKeyFunctions.MappingUnselect);
|
||||
context.AddFunction(ContentKeyFunctions.SaveMap);
|
||||
context.AddFunction(ContentKeyFunctions.MappingEnablePick);
|
||||
context.AddFunction(ContentKeyFunctions.MappingEnableDelete);
|
||||
context.AddFunction(ContentKeyFunctions.MappingPick);
|
||||
context.AddFunction(ContentKeyFunctions.MappingRemoveDecal);
|
||||
context.AddFunction(ContentKeyFunctions.MappingCancelEraseDecal);
|
||||
context.AddFunction(ContentKeyFunctions.MappingOpenContextMenu);
|
||||
|
||||
Screen.DecalSystem = _decal;
|
||||
Screen.Prototypes.SearchBar.OnTextChanged += OnSearch;
|
||||
Screen.Prototypes.CollapseAllButton.OnPressed += OnCollapseAll;
|
||||
Screen.Prototypes.ClearSearchButton.OnPressed += OnClearSearch;
|
||||
Screen.Prototypes.GetPrototypeData += OnGetData;
|
||||
Screen.Prototypes.SelectionChanged += OnSelected;
|
||||
Screen.Prototypes.CollapseToggled += OnCollapseToggled;
|
||||
Screen.Pick.OnPressed += OnPickPressed;
|
||||
Screen.Delete.OnPressed += OnDeletePressed;
|
||||
Screen.EntityReplaceButton.OnToggled += OnEntityReplacePressed;
|
||||
Screen.EntityPlacementMode.OnItemSelected += OnEntityPlacementSelected;
|
||||
Screen.EraseEntityButton.OnToggled += OnEraseEntityPressed;
|
||||
Screen.EraseDecalButton.OnToggled += OnEraseDecalPressed;
|
||||
_placement.PlacementChanged += OnPlacementChanged;
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.MappingUnselect, new PointerInputCmdHandler(HandleMappingUnselect, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.SaveMap, new PointerInputCmdHandler(HandleSaveMap, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.MappingEnablePick, new PointerStateInputCmdHandler(HandleEnablePick, HandleDisablePick, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.MappingEnableDelete, new PointerStateInputCmdHandler(HandleEnableDelete, HandleDisableDelete, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.MappingPick, new PointerInputCmdHandler(HandlePick, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.MappingRemoveDecal, new PointerInputCmdHandler(HandleEditorCancelPlace, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.MappingCancelEraseDecal, new PointerInputCmdHandler(HandleCancelEraseDecal, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.MappingOpenContextMenu, new PointerInputCmdHandler(HandleOpenContextMenu, outsidePrediction: true))
|
||||
.Register<MappingState>();
|
||||
|
||||
_overlays.AddOverlay(new MappingOverlay(this));
|
||||
|
||||
_prototypeManager.PrototypesReloaded += OnPrototypesReloaded;
|
||||
|
||||
Screen.Prototypes.UpdateVisible(_prototypes);
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
if (!obj.WasModified<EntityPrototype>() &&
|
||||
!obj.WasModified<ContentTileDefinition>() &&
|
||||
!obj.WasModified<DecalPrototype>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReloadPrototypes();
|
||||
}
|
||||
|
||||
private bool HandleOpenContextMenu(in PointerInputCmdArgs args)
|
||||
{
|
||||
Deselect();
|
||||
|
||||
var coords = args.Coordinates.ToMap(_entityManager, _transform);
|
||||
if (_verbs.TryGetEntityMenuEntities(coords, out var entities))
|
||||
_entityMenuController.OpenRootMenu(entities);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
CommandBinds.Unregister<MappingState>();
|
||||
|
||||
Screen.Prototypes.SearchBar.OnTextChanged -= OnSearch;
|
||||
Screen.Prototypes.CollapseAllButton.OnPressed -= OnCollapseAll;
|
||||
Screen.Prototypes.ClearSearchButton.OnPressed -= OnClearSearch;
|
||||
Screen.Prototypes.GetPrototypeData -= OnGetData;
|
||||
Screen.Prototypes.SelectionChanged -= OnSelected;
|
||||
Screen.Prototypes.CollapseToggled -= OnCollapseToggled;
|
||||
Screen.Pick.OnPressed -= OnPickPressed;
|
||||
Screen.Delete.OnPressed -= OnDeletePressed;
|
||||
Screen.EntityReplaceButton.OnToggled -= OnEntityReplacePressed;
|
||||
Screen.EntityPlacementMode.OnItemSelected -= OnEntityPlacementSelected;
|
||||
Screen.EraseEntityButton.OnToggled -= OnEraseEntityPressed;
|
||||
Screen.EraseDecalButton.OnToggled -= OnEraseDecalPressed;
|
||||
_placement.PlacementChanged -= OnPlacementChanged;
|
||||
_prototypeManager.PrototypesReloaded -= OnPrototypesReloaded;
|
||||
|
||||
UserInterfaceManager.ClearWindows();
|
||||
_loadController.UnloadScreen();
|
||||
UserInterfaceManager.UnloadScreen();
|
||||
|
||||
var context = _input.Contexts.GetContext("common");
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingUnselect);
|
||||
context.RemoveFunction(ContentKeyFunctions.SaveMap);
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingEnablePick);
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingEnableDelete);
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingPick);
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingRemoveDecal);
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingCancelEraseDecal);
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingOpenContextMenu);
|
||||
|
||||
_overlays.RemoveOverlay<MappingOverlay>();
|
||||
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private void EnsureSetup()
|
||||
{
|
||||
if (_setup)
|
||||
return;
|
||||
|
||||
_setup = true;
|
||||
|
||||
_entityMenuController = UserInterfaceManager.GetUIController<EntityMenuUIController>();
|
||||
|
||||
_decal = _entityManager.System<DecalPlacementSystem>();
|
||||
_sprite = _entityManager.System<SpriteSystem>();
|
||||
_transform = _entityManager.System<TransformSystem>();
|
||||
_verbs = _entityManager.System<VerbSystem>();
|
||||
ReloadPrototypes();
|
||||
}
|
||||
|
||||
private void ReloadPrototypes()
|
||||
{
|
||||
var entities = new MappingPrototype(null, Loc.GetString("mapping-entities")) { Children = new List<MappingPrototype>() };
|
||||
_prototypes.Add(entities);
|
||||
|
||||
var mappings = new Dictionary<string, MappingPrototype>();
|
||||
foreach (var entity in _prototypeManager.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
Register(entity, entity.ID, entities);
|
||||
}
|
||||
|
||||
Sort(mappings, entities);
|
||||
mappings.Clear();
|
||||
|
||||
var tiles = new MappingPrototype(null, Loc.GetString("mapping-tiles")) { Children = new List<MappingPrototype>() };
|
||||
_prototypes.Add(tiles);
|
||||
|
||||
foreach (var tile in _prototypeManager.EnumeratePrototypes<ContentTileDefinition>())
|
||||
{
|
||||
Register(tile, tile.ID, tiles);
|
||||
}
|
||||
|
||||
Sort(mappings, tiles);
|
||||
mappings.Clear();
|
||||
|
||||
var decals = new MappingPrototype(null, Loc.GetString("mapping-decals")) { Children = new List<MappingPrototype>() };
|
||||
_prototypes.Add(decals);
|
||||
|
||||
foreach (var decal in _prototypeManager.EnumeratePrototypes<DecalPrototype>())
|
||||
{
|
||||
Register(decal, decal.ID, decals);
|
||||
}
|
||||
|
||||
Sort(mappings, decals);
|
||||
mappings.Clear();
|
||||
}
|
||||
|
||||
private void Sort(Dictionary<string, MappingPrototype> prototypes, MappingPrototype topLevel)
|
||||
{
|
||||
static int Compare(MappingPrototype a, MappingPrototype b)
|
||||
{
|
||||
return string.Compare(a.Name, b.Name, OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
topLevel.Children ??= new List<MappingPrototype>();
|
||||
|
||||
foreach (var prototype in prototypes.Values)
|
||||
{
|
||||
if (prototype.Parents == null && prototype != topLevel)
|
||||
{
|
||||
prototype.Parents = new List<MappingPrototype> { topLevel };
|
||||
topLevel.Children.Add(prototype);
|
||||
}
|
||||
|
||||
prototype.Parents?.Sort(Compare);
|
||||
prototype.Children?.Sort(Compare);
|
||||
}
|
||||
|
||||
topLevel.Children.Sort(Compare);
|
||||
}
|
||||
|
||||
private MappingPrototype? Register<T>(T? prototype, string id, MappingPrototype topLevel) where T : class, IPrototype, IInheritingPrototype
|
||||
{
|
||||
{
|
||||
if (prototype == null &&
|
||||
_prototypeManager.TryIndex(id, out prototype) &&
|
||||
prototype is EntityPrototype entity)
|
||||
{
|
||||
if (entity.HideSpawnMenu || entity.Abstract)
|
||||
prototype = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (prototype == null)
|
||||
{
|
||||
if (!_prototypeManager.TryGetMapping(typeof(T), id, out var node))
|
||||
{
|
||||
_sawmill.Error($"No {nameof(T)} found with id {id}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var ids = _idDict.GetOrNew(typeof(T));
|
||||
if (ids.TryGetValue(id, out var mapping))
|
||||
{
|
||||
return mapping;
|
||||
}
|
||||
else
|
||||
{
|
||||
var name = node.TryGet("name", out ValueDataNode? nameNode)
|
||||
? nameNode.Value
|
||||
: id;
|
||||
|
||||
if (node.TryGet("suffix", out ValueDataNode? suffix))
|
||||
name = $"{name} [{suffix.Value}]";
|
||||
|
||||
mapping = new MappingPrototype(prototype, name);
|
||||
_allPrototypes.Add(mapping);
|
||||
ids.Add(id, mapping);
|
||||
|
||||
if (node.TryGet("parent", out ValueDataNode? parentValue))
|
||||
{
|
||||
var parent = Register<T>(null, parentValue.Value, topLevel);
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
mapping.Parents ??= new List<MappingPrototype>();
|
||||
mapping.Parents.Add(parent);
|
||||
parent.Children ??= new List<MappingPrototype>();
|
||||
parent.Children.Add(mapping);
|
||||
}
|
||||
}
|
||||
else if (node.TryGet("parent", out SequenceDataNode? parentSequence))
|
||||
{
|
||||
foreach (var parentNode in parentSequence.Cast<ValueDataNode>())
|
||||
{
|
||||
var parent = Register<T>(null, parentNode.Value, topLevel);
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
mapping.Parents ??= new List<MappingPrototype>();
|
||||
mapping.Parents.Add(parent);
|
||||
parent.Children ??= new List<MappingPrototype>();
|
||||
parent.Children.Add(mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
topLevel.Children ??= new List<MappingPrototype>();
|
||||
topLevel.Children.Add(mapping);
|
||||
mapping.Parents ??= new List<MappingPrototype>();
|
||||
mapping.Parents.Add(topLevel);
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var ids = _idDict.GetOrNew(typeof(T));
|
||||
if (ids.TryGetValue(id, out var mapping))
|
||||
{
|
||||
return mapping;
|
||||
}
|
||||
else
|
||||
{
|
||||
var entity = prototype as EntityPrototype;
|
||||
var name = entity?.Name ?? prototype.ID;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(entity?.EditorSuffix))
|
||||
name = $"{name} [{entity.EditorSuffix}]";
|
||||
|
||||
mapping = new MappingPrototype(prototype, name);
|
||||
_allPrototypes.Add(mapping);
|
||||
_allPrototypesDict.Add(prototype, mapping);
|
||||
ids.Add(prototype.ID, mapping);
|
||||
}
|
||||
|
||||
if (prototype.Parents == null)
|
||||
{
|
||||
topLevel.Children ??= new List<MappingPrototype>();
|
||||
topLevel.Children.Add(mapping);
|
||||
mapping.Parents ??= new List<MappingPrototype>();
|
||||
mapping.Parents.Add(topLevel);
|
||||
return mapping;
|
||||
}
|
||||
|
||||
foreach (var parentId in prototype.Parents)
|
||||
{
|
||||
var parent = Register<T>(null, parentId, topLevel);
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
mapping.Parents ??= new List<MappingPrototype>();
|
||||
mapping.Parents.Add(parent);
|
||||
parent.Children ??= new List<MappingPrototype>();
|
||||
parent.Children.Add(mapping);
|
||||
}
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlacementChanged(object? sender, EventArgs e)
|
||||
{
|
||||
_updatePlacement = true;
|
||||
}
|
||||
|
||||
protected override void OnKeyBindStateChanged(ViewportBoundKeyEventArgs args)
|
||||
{
|
||||
if (args.Viewport == null)
|
||||
base.OnKeyBindStateChanged(new ViewportBoundKeyEventArgs(args.KeyEventArgs, Viewport.Viewport));
|
||||
else
|
||||
base.OnKeyBindStateChanged(args);
|
||||
}
|
||||
|
||||
private void OnSearch(LineEditEventArgs args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(args.Text))
|
||||
{
|
||||
Screen.Prototypes.PrototypeList.Visible = true;
|
||||
Screen.Prototypes.SearchList.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var matches = new List<MappingPrototype>();
|
||||
foreach (var prototype in _allPrototypes)
|
||||
{
|
||||
if (prototype.Name.Contains(args.Text, OrdinalIgnoreCase))
|
||||
matches.Add(prototype);
|
||||
}
|
||||
|
||||
matches.Sort(static (a, b) => string.Compare(a.Name, b.Name, OrdinalIgnoreCase));
|
||||
|
||||
Screen.Prototypes.PrototypeList.Visible = false;
|
||||
Screen.Prototypes.SearchList.Visible = true;
|
||||
Screen.Prototypes.Search(matches);
|
||||
}
|
||||
|
||||
private void OnCollapseAll(ButtonEventArgs args)
|
||||
{
|
||||
foreach (var child in Screen.Prototypes.PrototypeList.Children)
|
||||
{
|
||||
if (child is not MappingSpawnButton button)
|
||||
continue;
|
||||
|
||||
Collapse(button);
|
||||
}
|
||||
|
||||
Screen.Prototypes.ScrollContainer.SetScrollValue(new Vector2(0, 0));
|
||||
}
|
||||
|
||||
private void OnClearSearch(ButtonEventArgs obj)
|
||||
{
|
||||
Screen.Prototypes.SearchBar.Text = string.Empty;
|
||||
OnSearch(new LineEditEventArgs(Screen.Prototypes.SearchBar, string.Empty));
|
||||
}
|
||||
|
||||
private void OnGetData(IPrototype prototype, List<Texture> textures)
|
||||
{
|
||||
switch (prototype)
|
||||
{
|
||||
case EntityPrototype entity:
|
||||
textures.AddRange(SpriteComponent.GetPrototypeTextures(entity, _resources).Select(t => t.Default));
|
||||
break;
|
||||
case DecalPrototype decal:
|
||||
textures.Add(_sprite.Frame0(decal.Sprite));
|
||||
break;
|
||||
case ContentTileDefinition tile:
|
||||
if (tile.Sprite?.ToString() is { } sprite)
|
||||
textures.Add(_resources.GetResource<TextureResource>(sprite).Texture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelected(MappingPrototype mapping)
|
||||
{
|
||||
if (mapping.Prototype == null)
|
||||
return;
|
||||
|
||||
var chain = new Stack<MappingPrototype>();
|
||||
chain.Push(mapping);
|
||||
|
||||
var parent = mapping.Parents?.FirstOrDefault();
|
||||
while (parent != null)
|
||||
{
|
||||
chain.Push(parent);
|
||||
parent = parent.Parents?.FirstOrDefault();
|
||||
}
|
||||
|
||||
_lastClicked = null;
|
||||
|
||||
Control? last = null;
|
||||
var children = Screen.Prototypes.PrototypeList.Children;
|
||||
foreach (var prototype in chain)
|
||||
{
|
||||
foreach (var child in children)
|
||||
{
|
||||
if (child is MappingSpawnButton button &&
|
||||
button.Prototype == prototype)
|
||||
{
|
||||
UnCollapse(button);
|
||||
OnSelected(button, prototype.Prototype);
|
||||
children = button.ChildrenPrototypes.Children;
|
||||
last = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (last != null && Screen.Prototypes.PrototypeList.Visible)
|
||||
_scrollTo = last;
|
||||
}
|
||||
|
||||
private void OnSelected(MappingSpawnButton button, IPrototype? prototype)
|
||||
{
|
||||
var time = _timing.CurTime;
|
||||
if (prototype is DecalPrototype)
|
||||
Screen.SelectDecal(prototype.ID);
|
||||
|
||||
// Double-click functionality if it's collapsible.
|
||||
if (_lastClicked is { } lastClicked &&
|
||||
lastClicked.Button == button &&
|
||||
lastClicked.At > time - TimeSpan.FromSeconds(0.333) &&
|
||||
string.IsNullOrEmpty(Screen.Prototypes.SearchBar.Text) &&
|
||||
button.CollapseButton.Visible)
|
||||
{
|
||||
button.CollapseButton.Pressed = !button.CollapseButton.Pressed;
|
||||
ToggleCollapse(button);
|
||||
button.Button.Pressed = true;
|
||||
Screen.Prototypes.Selected = button;
|
||||
_lastClicked = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle if it's the same button (at least if we just unclicked it).
|
||||
if (!button.Button.Pressed && button.Prototype?.Prototype != null && _lastClicked?.Button == button)
|
||||
{
|
||||
_lastClicked = null;
|
||||
Deselect();
|
||||
return;
|
||||
}
|
||||
|
||||
_lastClicked = (time, button);
|
||||
|
||||
if (button.Prototype == null)
|
||||
return;
|
||||
|
||||
if (Screen.Prototypes.Selected is { } oldButton &&
|
||||
oldButton != button)
|
||||
{
|
||||
Deselect();
|
||||
}
|
||||
|
||||
Screen.EntityContainer.Visible = false;
|
||||
Screen.DecalContainer.Visible = false;
|
||||
|
||||
switch (prototype)
|
||||
{
|
||||
case EntityPrototype entity:
|
||||
{
|
||||
var placementId = Screen.EntityPlacementMode.SelectedId;
|
||||
|
||||
var placement = new PlacementInformation
|
||||
{
|
||||
PlacementOption = placementId > 0 ? EntitySpawnWindow.InitOpts[placementId] : entity.PlacementMode,
|
||||
EntityType = entity.ID,
|
||||
IsTile = false
|
||||
};
|
||||
|
||||
Screen.EntityContainer.Visible = true;
|
||||
_decal.SetActive(false);
|
||||
_placement.BeginPlacing(placement);
|
||||
break;
|
||||
}
|
||||
case DecalPrototype decal:
|
||||
_placement.Clear();
|
||||
|
||||
_decal.SetActive(true);
|
||||
_decal.UpdateDecalInfo(decal.ID, Color.White, 0, true, 0, false);
|
||||
Screen.DecalContainer.Visible = true;
|
||||
break;
|
||||
case ContentTileDefinition tile:
|
||||
{
|
||||
var placement = new PlacementInformation
|
||||
{
|
||||
PlacementOption = "AlignTileAny",
|
||||
TileType = tile.TileId,
|
||||
IsTile = true
|
||||
};
|
||||
|
||||
_decal.SetActive(false);
|
||||
_placement.BeginPlacing(placement);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
_placement.Clear();
|
||||
break;
|
||||
}
|
||||
|
||||
Screen.Prototypes.Selected = button;
|
||||
|
||||
button.Button.Pressed = true;
|
||||
}
|
||||
|
||||
private void Deselect()
|
||||
{
|
||||
if (Screen.Prototypes.Selected is { } selected)
|
||||
{
|
||||
selected.Button.Pressed = false;
|
||||
Screen.Prototypes.Selected = null;
|
||||
|
||||
if (selected.Prototype?.Prototype is DecalPrototype)
|
||||
{
|
||||
_decal.SetActive(false);
|
||||
Screen.DecalContainer.Visible = false;
|
||||
}
|
||||
|
||||
if (selected.Prototype?.Prototype is EntityPrototype)
|
||||
{
|
||||
_placement.Clear();
|
||||
}
|
||||
|
||||
if (selected.Prototype?.Prototype is ContentTileDefinition)
|
||||
{
|
||||
_placement.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollapseToggled(MappingSpawnButton button, ButtonToggledEventArgs args)
|
||||
{
|
||||
ToggleCollapse(button);
|
||||
}
|
||||
|
||||
private void OnPickPressed(ButtonEventArgs args)
|
||||
{
|
||||
if (args.Button.Pressed)
|
||||
EnablePick();
|
||||
else
|
||||
DisablePick();
|
||||
}
|
||||
|
||||
private void OnDeletePressed(ButtonEventArgs obj)
|
||||
{
|
||||
if (obj.Button.Pressed)
|
||||
EnableDelete();
|
||||
else
|
||||
DisableDelete();
|
||||
}
|
||||
|
||||
private void OnEntityReplacePressed(ButtonToggledEventArgs args)
|
||||
{
|
||||
_placement.Replacement = args.Pressed;
|
||||
}
|
||||
|
||||
private void OnEntityPlacementSelected(ItemSelectedEventArgs args)
|
||||
{
|
||||
Screen.EntityPlacementMode.SelectId(args.Id);
|
||||
|
||||
if (_placement.CurrentMode != null)
|
||||
{
|
||||
var placement = new PlacementInformation
|
||||
{
|
||||
PlacementOption = EntitySpawnWindow.InitOpts[args.Id],
|
||||
EntityType = _placement.CurrentPermission!.EntityType,
|
||||
TileType = _placement.CurrentPermission.TileType,
|
||||
Range = 2,
|
||||
IsTile = _placement.CurrentPermission.IsTile,
|
||||
};
|
||||
|
||||
_placement.BeginPlacing(placement);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEraseEntityPressed(ButtonEventArgs args)
|
||||
{
|
||||
if (args.Button.Pressed == _placement.Eraser)
|
||||
return;
|
||||
|
||||
if (args.Button.Pressed)
|
||||
EnableEraser();
|
||||
else
|
||||
DisableEraser();
|
||||
}
|
||||
|
||||
private void OnEraseDecalPressed(ButtonToggledEventArgs args)
|
||||
{
|
||||
_placement.Clear();
|
||||
Deselect();
|
||||
Screen.EraseEntityButton.Pressed = false;
|
||||
_updatePlacement = true;
|
||||
_updateEraseDecal = args.Pressed;
|
||||
}
|
||||
|
||||
private void EnableEraser()
|
||||
{
|
||||
if (_placement.Eraser)
|
||||
return;
|
||||
|
||||
_placement.Clear();
|
||||
_placement.ToggleEraser();
|
||||
Screen.EntityPlacementMode.Disabled = true;
|
||||
Screen.EraseDecalButton.Pressed = false;
|
||||
Deselect();
|
||||
}
|
||||
|
||||
private void DisableEraser()
|
||||
{
|
||||
if (!_placement.Eraser)
|
||||
return;
|
||||
|
||||
_placement.ToggleEraser();
|
||||
Screen.EntityPlacementMode.Disabled = false;
|
||||
}
|
||||
|
||||
private void EnablePick()
|
||||
{
|
||||
Screen.UnPressActionsExcept(Screen.Pick);
|
||||
State = CursorState.Pick;
|
||||
}
|
||||
|
||||
private void DisablePick()
|
||||
{
|
||||
Screen.Pick.Pressed = false;
|
||||
State = CursorState.None;
|
||||
}
|
||||
|
||||
private void EnableDelete()
|
||||
{
|
||||
Screen.UnPressActionsExcept(Screen.Delete);
|
||||
State = CursorState.Delete;
|
||||
EnableEraser();
|
||||
}
|
||||
|
||||
private void DisableDelete()
|
||||
{
|
||||
Screen.Delete.Pressed = false;
|
||||
State = CursorState.None;
|
||||
DisableEraser();
|
||||
}
|
||||
|
||||
private bool HandleMappingUnselect(in PointerInputCmdArgs args)
|
||||
{
|
||||
if (Screen.Prototypes.Selected is not { Prototype.Prototype: DecalPrototype })
|
||||
return false;
|
||||
|
||||
Deselect();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleSaveMap(in PointerInputCmdArgs args)
|
||||
{
|
||||
#if FULL_RELEASE
|
||||
return false;
|
||||
#endif
|
||||
if (!_admin.IsAdmin(true) || !_admin.HasFlag(AdminFlags.Host))
|
||||
return false;
|
||||
|
||||
SaveMap();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleEnablePick(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
EnablePick();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleDisablePick(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
DisablePick();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleEnableDelete(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
EnableDelete();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleDisableDelete(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
DisableDelete();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandlePick(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
if (State != CursorState.Pick)
|
||||
return false;
|
||||
|
||||
MappingPrototype? button = null;
|
||||
|
||||
// Try and get tile under it
|
||||
// TODO: Separate mode for decals.
|
||||
if (!uid.IsValid())
|
||||
{
|
||||
var mapPos = _transform.ToMapCoordinates(coords);
|
||||
|
||||
if (_mapMan.TryFindGridAt(mapPos, out var gridUid, out var grid) &&
|
||||
_entityManager.System<SharedMapSystem>().TryGetTileRef(gridUid, grid, coords, out var tileRef) &&
|
||||
_allPrototypesDict.TryGetValue(tileRef.GetContentTileDefinition(), out button))
|
||||
{
|
||||
OnSelected(button);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (button == null)
|
||||
{
|
||||
if (uid == EntityUid.Invalid ||
|
||||
_entityManager.GetComponentOrNull<MetaDataComponent>(uid) is not { EntityPrototype: { } prototype } ||
|
||||
!_allPrototypesDict.TryGetValue(prototype, out button))
|
||||
{
|
||||
// we always block other input handlers if pick mode is enabled
|
||||
// this makes you not accidentally place something in space because you
|
||||
// miss-clicked while holding down the pick hotkey
|
||||
return true;
|
||||
}
|
||||
|
||||
// Selected an entity
|
||||
OnSelected(button);
|
||||
|
||||
// Match rotation
|
||||
_placement.Direction = _entityManager.GetComponent<TransformComponent>(uid).LocalRotation.GetDir();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleEditorCancelPlace(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
if (!Screen.EraseDecalButton.Pressed)
|
||||
return false;
|
||||
|
||||
_entityNetwork.SendSystemNetworkMessage(new RequestDecalRemovalEvent(_entityManager.GetNetCoordinates(coords)));
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleCancelEraseDecal(in PointerInputCmdArgs args)
|
||||
{
|
||||
if (!Screen.EraseDecalButton.Pressed)
|
||||
return false;
|
||||
|
||||
Screen.EraseDecalButton.Pressed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void SaveMap()
|
||||
{
|
||||
await _mapping.SaveMap();
|
||||
}
|
||||
|
||||
private void ToggleCollapse(MappingSpawnButton button)
|
||||
{
|
||||
if (button.CollapseButton.Pressed)
|
||||
{
|
||||
if (button.Prototype?.Children != null)
|
||||
{
|
||||
foreach (var child in button.Prototype.Children)
|
||||
{
|
||||
Screen.Prototypes.Insert(button.ChildrenPrototypes, child, true);
|
||||
}
|
||||
}
|
||||
|
||||
button.CollapseButton.Label.Text = "▼";
|
||||
}
|
||||
else
|
||||
{
|
||||
button.ChildrenPrototypes.DisposeAllChildren();
|
||||
button.CollapseButton.Label.Text = "▶";
|
||||
}
|
||||
}
|
||||
|
||||
private void Collapse(MappingSpawnButton button)
|
||||
{
|
||||
if (!button.CollapseButton.Pressed)
|
||||
return;
|
||||
|
||||
button.CollapseButton.Pressed = false;
|
||||
ToggleCollapse(button);
|
||||
}
|
||||
|
||||
|
||||
private void UnCollapse(MappingSpawnButton button)
|
||||
{
|
||||
if (button.CollapseButton.Pressed)
|
||||
return;
|
||||
|
||||
button.CollapseButton.Pressed = true;
|
||||
ToggleCollapse(button);
|
||||
}
|
||||
|
||||
public EntityUid? GetHoveredEntity()
|
||||
{
|
||||
if (UserInterfaceManager.CurrentlyHovered is not IViewportControl viewport ||
|
||||
_input.MouseScreenPosition is not { IsValid: true } position)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var mapPos = viewport.PixelToMap(position.Position);
|
||||
return GetClickedEntity(mapPos);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(FrameEventArgs e)
|
||||
{
|
||||
if (_updatePlacement)
|
||||
{
|
||||
_updatePlacement = false;
|
||||
|
||||
if (!_placement.IsActive && _decal.GetActiveDecal().Decal == null)
|
||||
Deselect();
|
||||
|
||||
Screen.EraseEntityButton.Pressed = _placement.Eraser;
|
||||
Screen.EraseDecalButton.Pressed = _updateEraseDecal;
|
||||
Screen.EntityPlacementMode.Disabled = _placement.Eraser;
|
||||
}
|
||||
|
||||
if (_scrollTo is not { } scrollTo)
|
||||
return;
|
||||
|
||||
// this is not ideal but we wait until the control's height is computed to use
|
||||
// its position to scroll to
|
||||
if (scrollTo.Height > 0 && Screen.Prototypes.PrototypeList.Visible)
|
||||
{
|
||||
var y = scrollTo.GlobalPosition.Y - Screen.Prototypes.ScrollContainer.Height / 2 + scrollTo.Height;
|
||||
var scroll = Screen.Prototypes.ScrollContainer;
|
||||
scroll.SetScrollValue(scroll.GetScrollValue() + new Vector2(0, y));
|
||||
_scrollTo = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO this doesn't handle pressing down multiple state hotkeys at the moment
|
||||
public enum CursorState
|
||||
{
|
||||
None,
|
||||
Pick,
|
||||
Delete
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ public sealed partial class MappingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlacementManager _placementMan = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileMan = default!;
|
||||
[Dependency] private readonly ActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
|
||||
/// <summary>
|
||||
@@ -26,8 +25,6 @@ public sealed partial class MappingSystem : EntitySystem
|
||||
/// </summary>
|
||||
private readonly SpriteSpecifier _deleteIcon = new Texture(new ("Interface/VerbIcons/delete.svg.192dpi.png"));
|
||||
|
||||
public string DefaultMappingActions = "/mapping_actions.yml";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -36,11 +33,6 @@ public sealed partial class MappingSystem : EntitySystem
|
||||
SubscribeLocalEvent<StartPlacementActionEvent>(OnStartPlacementAction);
|
||||
}
|
||||
|
||||
public void LoadMappingActions()
|
||||
{
|
||||
_actionsSystem.LoadActionAssignments(DefaultMappingActions, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This checks if the placement manager is currently active, and attempts to copy the placement information for
|
||||
/// some entity or tile into an action. This is somewhat janky, but it seem to work well enough. Though I'd
|
||||
|
||||
@@ -18,6 +18,7 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly InputSystem _inputSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
@@ -62,11 +63,11 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
|
||||
if (_dragging == null) return;
|
||||
|
||||
if (_lastMousePosition != null && TryComp(_dragging.Value, out TransformComponent? xform) &&
|
||||
TryComp<PhysicsComponent>(_dragging.Value, out var body) &&
|
||||
TryComp<PhysicsComponent>(_dragging.Value, out _) &&
|
||||
xform.MapID == _lastMousePosition.Value.MapId)
|
||||
{
|
||||
var tickTime = _gameTiming.TickPeriod;
|
||||
var distance = _lastMousePosition.Value.Position - xform.WorldPosition;
|
||||
var distance = _lastMousePosition.Value.Position - _transformSystem.GetWorldPosition(xform);
|
||||
RaiseNetworkEvent(new GridDragVelocityRequest()
|
||||
{
|
||||
Grid = GetNetEntity(_dragging.Value),
|
||||
|
||||
27
Content.Client/Materials/RecyclerVisualizerSystem.cs
Normal file
27
Content.Client/Materials/RecyclerVisualizerSystem.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Content.Shared.Conveyor;
|
||||
using Content.Shared.Materials;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Materials;
|
||||
|
||||
public sealed class RecyclerVisualizerSystem : VisualizerSystem<RecyclerVisualsComponent>
|
||||
{
|
||||
protected override void OnAppearanceChange(EntityUid uid, RecyclerVisualsComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null || !args.Sprite.LayerMapTryGet(RecyclerVisualLayers.Main, out var layer))
|
||||
return;
|
||||
|
||||
AppearanceSystem.TryGetData<ConveyorState>(uid, ConveyorVisuals.State, out var running);
|
||||
AppearanceSystem.TryGetData<bool>(uid, RecyclerVisuals.Bloody, out var bloody);
|
||||
AppearanceSystem.TryGetData<bool>(uid, RecyclerVisuals.Broken, out var broken);
|
||||
|
||||
var activityState = running == ConveyorState.Off ? 0 : 1;
|
||||
if (broken) //breakage overrides activity
|
||||
activityState = 2;
|
||||
|
||||
var bloodyKey = bloody ? component.BloodyKey : string.Empty;
|
||||
|
||||
var state = $"{component.BaseKey}{activityState}{bloodyKey}";
|
||||
args.Sprite.LayerSetState(layer, state);
|
||||
}
|
||||
}
|
||||
17
Content.Client/Materials/RecyclerVisualsComponent.cs
Normal file
17
Content.Client/Materials/RecyclerVisualsComponent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Content.Client.Materials;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class RecyclerVisualsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Key appended to state string if bloody.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string BloodyKey = "bld";
|
||||
|
||||
/// <summary>
|
||||
/// Base key for the visual state.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string BaseKey = "grinder-o";
|
||||
}
|
||||
@@ -257,7 +257,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
mainContainer.AddChild(jobContainer);
|
||||
|
||||
// Job icon
|
||||
if (_prototypeManager.TryIndex<StatusIconPrototype>(sensor.JobIcon, out var proto))
|
||||
if (_prototypeManager.TryIndex<JobIconPrototype>(sensor.JobIcon, out var proto))
|
||||
{
|
||||
var jobIcon = new TextureRect()
|
||||
{
|
||||
|
||||
@@ -64,15 +64,15 @@ public sealed class JetpackSystem : SharedJetpackSystem
|
||||
|
||||
private void CreateParticles(EntityUid uid)
|
||||
{
|
||||
var uidXform = Transform(uid);
|
||||
// Don't show particles unless the user is moving.
|
||||
if (Container.TryGetContainingContainer(uid, out var container) &&
|
||||
if (Container.TryGetContainingContainer((uid, uidXform, null), out var container) &&
|
||||
TryComp<PhysicsComponent>(container.Owner, out var body) &&
|
||||
body.LinearVelocity.LengthSquared() < 1f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var uidXform = Transform(uid);
|
||||
var coordinates = uidXform.Coordinates;
|
||||
var gridUid = _transform.GetGrid(coordinates);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed class SpriteMovementSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var oldMoving = (SharedMoverController.GetNormalizedMovement(args.OldMovement) & MoveButtons.AnyDirection) != MoveButtons.None;
|
||||
var moving = (SharedMoverController.GetNormalizedMovement(args.Component.HeldMoveButtons) & MoveButtons.AnyDirection) != MoveButtons.None;
|
||||
var moving = (SharedMoverController.GetNormalizedMovement(args.Entity.Comp.HeldMoveButtons) & MoveButtons.AnyDirection) != MoveButtons.None;
|
||||
|
||||
if (oldMoving == moving || !_spriteQuery.TryGetComponent(uid, out var sprite))
|
||||
return;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user