Compare commits
1106 Commits
0.1.0
...
archive/cu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
493b80095d | ||
|
|
73a9b2af89 | ||
|
|
b167107c8b | ||
|
|
972d601664 | ||
|
|
7bc40ab13d | ||
|
|
5243530d81 | ||
|
|
e3108261ab | ||
|
|
883c465a14 | ||
|
|
1d98152953 | ||
|
|
a6f8ee317f | ||
|
|
4315618782 | ||
|
|
b71f39cfb4 | ||
|
|
2ec493e2af | ||
|
|
7e43d574d8 | ||
|
|
01a0a376e3 | ||
|
|
59500e5278 | ||
|
|
5390a9f375 | ||
|
|
1412cd5277 | ||
|
|
ca57749a3b | ||
|
|
6706ff23ce | ||
|
|
daf3c28929 | ||
|
|
821058740f | ||
|
|
fbe7533d4b | ||
|
|
4a833e82cd | ||
|
|
a692899f5b | ||
|
|
a86363a6d2 | ||
|
|
4ab7f1dcb3 | ||
|
|
0f1cee44a3 | ||
|
|
d16fe5376d | ||
|
|
514d05b237 | ||
|
|
f95c5b7921 | ||
|
|
1dd4a5b48b | ||
|
|
7bf06a59d4 | ||
|
|
fd759e4a9d | ||
|
|
11d47cc67a | ||
|
|
46ce6bf45e | ||
|
|
5af5a02e31 | ||
|
|
664acb140e | ||
|
|
09a27df6db | ||
|
|
1996893a26 | ||
|
|
86d1f808af | ||
|
|
aaa4329d8c | ||
|
|
5a5e8f0e31 | ||
|
|
83b2e59910 | ||
|
|
8f04ce894f | ||
|
|
2260d19364 | ||
|
|
90409b0b3d | ||
|
|
559367ee55 | ||
|
|
cab3688890 | ||
|
|
3294634d24 | ||
|
|
36cf1c3179 | ||
|
|
930fb331db | ||
|
|
eb7f592154 | ||
|
|
aa77b017e8 | ||
|
|
09900a08e4 | ||
|
|
9beb7e48d4 | ||
|
|
5481959018 | ||
|
|
32fae60930 | ||
|
|
d564d3bc39 | ||
|
|
6537aead64 | ||
|
|
2848da0b8e | ||
|
|
923c5698b5 | ||
|
|
8d3bccbd56 | ||
|
|
44b2b1b958 | ||
|
|
eadb661515 | ||
|
|
42066fc8a1 | ||
|
|
ba88b2b1da | ||
|
|
f550ba67aa | ||
|
|
bdc637d3af | ||
|
|
73693b88f6 | ||
|
|
77fcc4a673 | ||
|
|
54f5f0ac08 | ||
|
|
425b85d5a7 | ||
|
|
f6fe9ce85c | ||
|
|
75aa9541e0 | ||
|
|
03bfb22559 | ||
|
|
05ff4e0956 | ||
|
|
959bf7c477 | ||
|
|
7c562af0aa | ||
|
|
57c3f63a26 | ||
|
|
f08455073a | ||
|
|
812654fe32 | ||
|
|
aaf0b7a645 | ||
|
|
e64d80d02e | ||
|
|
ac9d236955 | ||
|
|
fc2d53eb4f | ||
|
|
77367345b6 | ||
|
|
02fbc5938b | ||
|
|
ce794c4dac | ||
|
|
edf280e2df | ||
|
|
1bd17f73b1 | ||
|
|
511741d11a | ||
|
|
f86ad6175e | ||
|
|
afef34a648 | ||
|
|
802fda3cfd | ||
|
|
b5feb0db2a | ||
|
|
4b60c03688 | ||
|
|
e0aaab56e3 | ||
|
|
9a76c70b37 | ||
|
|
e619b3026c | ||
|
|
54b7d3f229 | ||
|
|
a4b0c4e213 | ||
|
|
47f33e002d | ||
|
|
a4e369e629 | ||
|
|
d03da83fda | ||
|
|
c20ba98a1e | ||
|
|
c4ea6e53e8 | ||
|
|
1856cb079c | ||
|
|
56f1233967 | ||
|
|
da932c5caa | ||
|
|
bf6e38703a | ||
|
|
b5af7b1c3e | ||
|
|
e0a4735fe2 | ||
|
|
9c0a670525 | ||
|
|
ca01e245cb | ||
|
|
92da411ea5 | ||
|
|
f60b0fce7d | ||
|
|
3cceb35445 | ||
|
|
ca58afd81b | ||
|
|
32103979ed | ||
|
|
51f7f14c08 | ||
|
|
0db46da30e | ||
|
|
411c23c46e | ||
|
|
e984fc24b6 | ||
|
|
33782ed7f3 | ||
|
|
e31078d8ef | ||
|
|
39d99485eb | ||
|
|
f73824adcb | ||
|
|
8a49546add | ||
|
|
89745202f5 | ||
|
|
d98ce413bb | ||
|
|
e2e5982d68 | ||
|
|
4773aad40c | ||
|
|
b44ca8df8d | ||
|
|
02a655d005 | ||
|
|
1da8e66281 | ||
|
|
301cebc254 | ||
|
|
26c8995dad | ||
|
|
6b99946fd8 | ||
|
|
4ffaf3fbe6 | ||
|
|
43cb54bb21 | ||
|
|
498c248c24 | ||
|
|
4f3f82f27f | ||
|
|
0c9a95bc54 | ||
|
|
f19795edaf | ||
|
|
c1b9bb348d | ||
|
|
ece6e0a833 | ||
|
|
c2c512a7e3 | ||
|
|
690e9dbfe8 | ||
|
|
1e696edcff | ||
|
|
1fe09c580c | ||
|
|
12261c5b56 | ||
|
|
72cff220cf | ||
|
|
e0cf442041 | ||
|
|
a9f148c04e | ||
|
|
a652e39b1c | ||
|
|
79ee241bb7 | ||
|
|
63b98f26a6 | ||
|
|
95fe7fdd25 | ||
|
|
9c9984a40a | ||
|
|
7f188b0f44 | ||
|
|
023c76db59 | ||
|
|
672482194c | ||
|
|
689d16ee65 | ||
|
|
a912c999a9 | ||
|
|
26da24c3c5 | ||
|
|
4cf8e18d1f | ||
|
|
6df5028d7a | ||
|
|
e179e89c03 | ||
|
|
a7f86a4333 | ||
|
|
aea14074cc | ||
|
|
fac91af34d | ||
|
|
7c54a3c923 | ||
|
|
542428df32 | ||
|
|
adaf0ade52 | ||
|
|
4265fac7b8 | ||
|
|
8a90e5d186 | ||
|
|
c213fa8cdf | ||
|
|
45567c7acc | ||
|
|
d549c44f95 | ||
|
|
9cfa0d447a | ||
|
|
590cb1e85c | ||
|
|
fedc0ad71c | ||
|
|
ce54c489eb | ||
|
|
d63c879404 | ||
|
|
35f9de3366 | ||
|
|
17b31c417b | ||
|
|
421847e9d3 | ||
|
|
3a7a3a89ba | ||
|
|
b89615342e | ||
|
|
4198b6dc4e | ||
|
|
0cf34d2d26 | ||
|
|
d5a9747712 | ||
|
|
45767d881d | ||
|
|
2d4f1780bf | ||
|
|
ca8609f42c | ||
|
|
1580750606 | ||
|
|
58709d2d26 | ||
|
|
94c00dda95 | ||
|
|
0595088409 | ||
|
|
3ab8036363 | ||
|
|
1f177a044d | ||
|
|
96b8ded8af | ||
|
|
32bd23f85e | ||
|
|
447db2e458 | ||
|
|
480d3b26c4 | ||
|
|
8b1be6edee | ||
|
|
b2e2aef78d | ||
|
|
62b31eee00 | ||
|
|
774f5f0db7 | ||
|
|
f8ff829e27 | ||
|
|
34083d3e8d | ||
|
|
173329de8f | ||
|
|
ef2b665ff0 | ||
|
|
841bb101c5 | ||
|
|
7198a7c78d | ||
|
|
de148fc98f | ||
|
|
6de5c01afb | ||
|
|
0d30bc2676 | ||
|
|
e5150d3714 | ||
|
|
7032c8a92e | ||
|
|
51359cf77b | ||
|
|
6529542277 | ||
|
|
63611cef80 | ||
|
|
0b5759abe6 | ||
|
|
1e425f3c28 | ||
|
|
4720353fa2 | ||
|
|
e8679d9308 | ||
|
|
808f35adc6 | ||
|
|
7d307832a0 | ||
|
|
12cf5559c2 | ||
|
|
6497cdf8ff | ||
|
|
e4f3ea7798 | ||
|
|
10ac4418e4 | ||
|
|
3a0856505d | ||
|
|
8a6751711a | ||
|
|
50755a040b | ||
|
|
69796bf1bc | ||
|
|
9ac0e02574 | ||
|
|
738fbdd376 | ||
|
|
0e1eb71149 | ||
|
|
f5cbbb5c84 | ||
|
|
9a1e4450d8 | ||
|
|
563dda69d4 | ||
|
|
44c9feaebf | ||
|
|
c457a2603a | ||
|
|
981c36dbdb | ||
|
|
6630e454c6 | ||
|
|
9c60d4936d | ||
|
|
e2511f8ad5 | ||
|
|
19379decd5 | ||
|
|
743ede2243 | ||
|
|
0edccd8934 | ||
|
|
6f704f0320 | ||
|
|
62db0573bd | ||
|
|
4d5c34bd58 | ||
|
|
f0fb3eb434 | ||
|
|
7de97eeb2c | ||
|
|
def32d80dd | ||
|
|
e4bba4cb6f | ||
|
|
9a38577a18 | ||
|
|
f3f05b0396 | ||
|
|
fd109436e5 | ||
|
|
d629dc449f | ||
|
|
5db8cda0b6 | ||
|
|
d113a738de | ||
|
|
bd3fe8f86b | ||
|
|
445e88cce8 | ||
|
|
33e11c0c3a | ||
|
|
370f4e140d | ||
|
|
b556fd0019 | ||
|
|
be9dc90738 | ||
|
|
8896f46ef3 | ||
|
|
dcffe0ef04 | ||
|
|
a5b19b10e0 | ||
|
|
d6e378c3bf | ||
|
|
6893541f9c | ||
|
|
74dd24f39c | ||
|
|
3634b17be4 | ||
|
|
006341daf7 | ||
|
|
427836fec9 | ||
|
|
bd5a4e33ab | ||
|
|
d7360f8709 | ||
|
|
6249a129c0 | ||
|
|
21612794c5 | ||
|
|
fa2d633313 | ||
|
|
09ca46fbd0 | ||
|
|
cf97ef7ad1 | ||
|
|
963bb28f0f | ||
|
|
0cc980b26a | ||
|
|
a2d8fc1ef9 | ||
|
|
18392610c9 | ||
|
|
fc13e21e73 | ||
|
|
a346eb3e12 | ||
|
|
ef99cac28f | ||
|
|
02d509fc5f | ||
|
|
ac55ccf46e | ||
|
|
bd96190bbc | ||
|
|
61e516c4ac | ||
|
|
66c327805f | ||
|
|
31487c1cf1 | ||
|
|
3e972b501a | ||
|
|
59c758fa1c | ||
|
|
10ca375284 | ||
|
|
76f732058a | ||
|
|
a4aab7dbd1 | ||
|
|
0090af6b3b | ||
|
|
de141c49c5 | ||
|
|
892d0ee162 | ||
|
|
e8485ee6c5 | ||
|
|
e668bbbade | ||
|
|
cbea6bf41f | ||
|
|
35e88ea62c | ||
|
|
6c97b63e59 | ||
|
|
ed1271c30b | ||
|
|
b496e8ef29 | ||
|
|
375813e5e2 | ||
|
|
dd06c71735 | ||
|
|
364279e0f7 | ||
|
|
415ac8fa46 | ||
|
|
fc5d7835c0 | ||
|
|
b55d6cbf75 | ||
|
|
d5283fca09 | ||
|
|
311f843ea1 | ||
|
|
3f4c9a8326 | ||
|
|
e570f10d69 | ||
|
|
97e18b9ed4 | ||
|
|
c56d56c2d1 | ||
|
|
4e2f694a0d | ||
|
|
892b9d041d | ||
|
|
ea58ebed50 | ||
|
|
adb7e1b598 | ||
|
|
b9f6ea68ce | ||
|
|
63bb4c4d0a | ||
|
|
5c40300474 | ||
|
|
6ca9e16670 | ||
|
|
deb2b984eb | ||
|
|
0d7674b934 | ||
|
|
fc1dd51e63 | ||
|
|
0815050b2a | ||
|
|
36078382e4 | ||
|
|
34f4731c9b | ||
|
|
6e2799f048 | ||
|
|
ba8b495ec0 | ||
|
|
b62fb4a318 | ||
|
|
9353a060f2 | ||
|
|
5aafe89d95 | ||
|
|
7a55c07e22 | ||
|
|
9ffbb51fd1 | ||
|
|
f3b460c8b4 | ||
|
|
0329150109 | ||
|
|
264a63b7f6 | ||
|
|
70e3cffa90 | ||
|
|
fc046fb8ca | ||
|
|
9acf37e99d | ||
|
|
1645cb2dd0 | ||
|
|
12e635a411 | ||
|
|
b40a96f545 | ||
|
|
64f148cb6e | ||
|
|
4d31537add | ||
|
|
ecd77d6c48 | ||
|
|
5716671dcf | ||
|
|
6bdd26506c | ||
|
|
5858de0b08 | ||
|
|
ffc9f10399 | ||
|
|
0f0a3eb822 | ||
|
|
9ba5f9f2a3 | ||
|
|
ecb7cd3c66 | ||
|
|
293f88599d | ||
|
|
55f9411493 | ||
|
|
4232397aa8 | ||
|
|
ee029de5e7 | ||
|
|
34e7edb5f5 | ||
|
|
03ac153417 | ||
|
|
8a88ee7f90 | ||
|
|
d6113b6147 | ||
|
|
7306bcc35a | ||
|
|
77216af44e | ||
|
|
ffbff0d765 | ||
|
|
9431011ba5 | ||
|
|
44fdf4022e | ||
|
|
09b1066122 | ||
|
|
ecbf9a7706 | ||
|
|
ab1108b731 | ||
|
|
f0053f15bf | ||
|
|
4175f891fb | ||
|
|
c207297c57 | ||
|
|
6ca70b0156 | ||
|
|
6e6aef62c6 | ||
|
|
27d7337abf | ||
|
|
66c5affa65 | ||
|
|
be4197351a | ||
|
|
4dcbf28714 | ||
|
|
4c67856dd6 | ||
|
|
1c81f6097f | ||
|
|
7f5654bb06 | ||
|
|
534af65f7c | ||
|
|
73dc55d8ef | ||
|
|
225bc86d73 | ||
|
|
3b48926d75 | ||
|
|
b38a014b02 | ||
|
|
aed1a0d985 | ||
|
|
9cb37a6376 | ||
|
|
6be135a137 | ||
|
|
20c387158a | ||
|
|
88920696f3 | ||
|
|
3c476d2b40 | ||
|
|
72ba0d9458 | ||
|
|
c95f17f54b | ||
|
|
f116e887ea | ||
|
|
d2a1309cb6 | ||
|
|
73ab543cc2 | ||
|
|
96eb4afdf6 | ||
|
|
3a25395ff9 | ||
|
|
afd1215b03 | ||
|
|
d4384aef73 | ||
|
|
a2e6500d54 | ||
|
|
418522b714 | ||
|
|
e142155b01 | ||
|
|
844518d921 | ||
|
|
589d52158c | ||
|
|
e5090cd10d | ||
|
|
0071321c53 | ||
|
|
7b9038e6da | ||
|
|
b59c1a2b0f | ||
|
|
8b593d28c6 | ||
|
|
4b30c7e710 | ||
|
|
d7505ca8b5 | ||
|
|
831af2f157 | ||
|
|
ab954c9f53 | ||
|
|
7c3cf945cf | ||
|
|
b38e780f01 | ||
|
|
89c485200b | ||
|
|
c759d35dea | ||
|
|
81fd26f5db | ||
|
|
7753ae7c3a | ||
|
|
41cb27dd67 | ||
|
|
d3947c73ab | ||
|
|
4cd99fc624 | ||
|
|
3c09c18943 | ||
|
|
6079950220 | ||
|
|
8c59d2e3b9 | ||
|
|
fb9dbd8e16 | ||
|
|
7454c62ff2 | ||
|
|
cf234fe66f | ||
|
|
ca9cc36a93 | ||
|
|
7422d9148a | ||
|
|
6c76c5d917 | ||
|
|
687d22188a | ||
|
|
b996466b3d | ||
|
|
02da078baf | ||
|
|
121d440ac9 | ||
|
|
a246d7e48d | ||
|
|
ed60c41c35 | ||
|
|
3488ca0173 | ||
|
|
de334904b4 | ||
|
|
8cf5195db6 | ||
|
|
041038fa1b | ||
|
|
151d3a3672 | ||
|
|
1d9d01b355 | ||
|
|
d5ec234fd3 | ||
|
|
e95bf0a642 | ||
|
|
a7f1520d1f | ||
|
|
ceb8cc8421 | ||
|
|
a90d7a645c | ||
|
|
2ea8bbf4eb | ||
|
|
41b72d5aa2 | ||
|
|
b6ab0298f4 | ||
|
|
ec771abfaa | ||
|
|
ad9d7573d6 | ||
|
|
0086e60b6a | ||
|
|
388cc8fdde | ||
|
|
211dd56f94 | ||
|
|
1df5be6570 | ||
|
|
ffee6bbd35 | ||
|
|
1132e5b6a7 | ||
|
|
15d81c1876 | ||
|
|
29b00fc633 | ||
|
|
e00a737285 | ||
|
|
dc6e65559b | ||
|
|
ad695702ac | ||
|
|
d9628d39eb | ||
|
|
bb20243b05 | ||
|
|
e5d9634cfe | ||
|
|
930cb61af8 | ||
|
|
d3053c3c8c | ||
|
|
2e230c089b | ||
|
|
aea7d01eaa | ||
|
|
448ee88357 | ||
|
|
c635aeba79 | ||
|
|
ff90bb4802 | ||
|
|
d906bcda03 | ||
|
|
a162564516 | ||
|
|
232e4951b0 | ||
|
|
824f5f6755 | ||
|
|
248e3f686b | ||
|
|
69f5600a51 | ||
|
|
1fbb5915aa | ||
|
|
4d202a7678 | ||
|
|
c7e9c4e857 | ||
|
|
409a7d9e01 | ||
|
|
5abcb680ab | ||
|
|
c675886713 | ||
|
|
f5d319bc3a | ||
|
|
5f276cd39d | ||
|
|
b34a68a519 | ||
|
|
ff3c1e6e82 | ||
|
|
18efec6472 | ||
|
|
1ba460e1a6 | ||
|
|
8cb3b9b2e6 | ||
|
|
c0717ed6a3 | ||
|
|
14d538997e | ||
|
|
42a41036ad | ||
|
|
1a92d08399 | ||
|
|
1f320eccd7 | ||
|
|
52a6d9ff43 | ||
|
|
8621d61278 | ||
|
|
96516fa288 | ||
|
|
90173f6147 | ||
|
|
22c5b6eb31 | ||
|
|
a3b0127328 | ||
|
|
b281cc28a1 | ||
|
|
40eca95661 | ||
|
|
6de6c6534c | ||
|
|
068c997519 | ||
|
|
c529f38517 | ||
|
|
76c5c911d9 | ||
|
|
d9077bde74 | ||
|
|
92668432a7 | ||
|
|
ce1eab9181 | ||
|
|
a9a99c1821 | ||
|
|
73511d9d8f | ||
|
|
4b9c4022b8 | ||
|
|
69f9da944d | ||
|
|
391d4a6c67 | ||
|
|
73e54d201d | ||
|
|
fb46949ea3 | ||
|
|
806e95b382 | ||
|
|
cdcafbada6 | ||
|
|
837a906538 | ||
|
|
4540034e26 | ||
|
|
546770ee70 | ||
|
|
b7d5297e9f | ||
|
|
08d0663307 | ||
|
|
715ddbaea2 | ||
|
|
b9215d214e | ||
|
|
0a6e5c6d01 | ||
|
|
558f6ab8a5 | ||
|
|
8ff4c22f42 | ||
|
|
215885a436 | ||
|
|
90701573ac | ||
|
|
4221fecff6 | ||
|
|
c4523a956d | ||
|
|
338f456c50 | ||
|
|
b9e4410c93 | ||
|
|
2c7c0b905f | ||
|
|
7cf2239eff | ||
|
|
6d3613672b | ||
|
|
f97977323a | ||
|
|
e8498d1bb2 | ||
|
|
ff4190b4e1 | ||
|
|
f81ca7ceba | ||
|
|
955f92298d | ||
|
|
10801af2f7 | ||
|
|
f1aeaaa640 | ||
|
|
f551bd32d6 | ||
|
|
4631e3c79a | ||
|
|
c156af34c4 | ||
|
|
1cfdfa12a0 | ||
|
|
9f58d8cc74 | ||
|
|
4a4fcad7ef | ||
|
|
50bc1bff48 | ||
|
|
dd13d969b2 | ||
|
|
400778eb73 | ||
|
|
da35a0f3c9 | ||
|
|
1426e516b5 | ||
|
|
e07c3c368f | ||
|
|
56bccdbc3e | ||
|
|
740c62330e | ||
|
|
de3d306487 | ||
|
|
32f4f99441 | ||
|
|
8bb4f526f5 | ||
|
|
93a34186cc | ||
|
|
e9de8ff9dd | ||
|
|
9c3587b00e | ||
|
|
996b45a04f | ||
|
|
241aa353e9 | ||
|
|
2afb515a8a | ||
|
|
9ae2cf6db4 | ||
|
|
ce9c8785d6 | ||
|
|
02e13d0182 | ||
|
|
fe483897a6 | ||
|
|
54449038ef | ||
|
|
b9f7c65c05 | ||
|
|
338a8e463e | ||
|
|
9c9a9db9a0 | ||
|
|
51c323963c | ||
|
|
7e703760c8 | ||
|
|
48b6a8c725 | ||
|
|
96daace170 | ||
|
|
196ec030f5 | ||
|
|
377bd1890e | ||
|
|
e40f9b20e3 | ||
|
|
ce163bdd80 | ||
|
|
d52e5ccbfb | ||
|
|
f489bee686 | ||
|
|
8c6f6f3fb0 | ||
|
|
877dcc996c | ||
|
|
7e30ffe007 | ||
|
|
916b1521a0 | ||
|
|
bc3942127b | ||
|
|
4bb02d64fb | ||
|
|
e40b775f7c | ||
|
|
ddcdeca4ea | ||
|
|
d81254e389 | ||
|
|
b64643ecd6 | ||
|
|
c197278c6f | ||
|
|
1a5c4ad83c | ||
|
|
818ee83440 | ||
|
|
91de6f80b1 | ||
|
|
792b219b04 | ||
|
|
2b0ca2dd8b | ||
|
|
a903ffb105 | ||
|
|
bec187d997 | ||
|
|
2917a87ce7 | ||
|
|
3fa2e0c976 | ||
|
|
51eae9d30c | ||
|
|
e68a5c8402 | ||
|
|
304b4d8542 | ||
|
|
cbca48ebbb | ||
|
|
a78a07d0c4 | ||
|
|
ad3b3c9f4f | ||
|
|
96fbde3413 | ||
|
|
88c29abe5e | ||
|
|
331cfaa1c5 | ||
|
|
9a1f37d476 | ||
|
|
57bcb2a16d | ||
|
|
fa8fbc49f5 | ||
|
|
c0edf2aeed | ||
|
|
e0e5f6355c | ||
|
|
eacfa34c99 | ||
|
|
296288e99d | ||
|
|
0ccefebc52 | ||
|
|
29304a7714 | ||
|
|
d15998ed16 | ||
|
|
90620db5f8 | ||
|
|
a014b40972 | ||
|
|
41b5c4dba2 | ||
|
|
1fa63179d1 | ||
|
|
e35d5390db | ||
|
|
030f1f2a57 | ||
|
|
61eb340945 | ||
|
|
b7d30f0870 | ||
|
|
23d8b92f94 | ||
|
|
5a5e201c7c | ||
|
|
1d3f61de3c | ||
|
|
c8cdde5d40 | ||
|
|
deaae06787 | ||
|
|
ff0f309f31 | ||
|
|
89b77bbed8 | ||
|
|
ec1a4c6601 | ||
|
|
71e2e2c768 | ||
|
|
dc2d3f94cb | ||
|
|
ea38075feb | ||
|
|
e00d4f7b26 | ||
|
|
cb7e5d4214 | ||
|
|
186138e9b5 | ||
|
|
50780f5cf0 | ||
|
|
f249c19eaf | ||
|
|
77d92675e2 | ||
|
|
02e35502a1 | ||
|
|
dc3369d198 | ||
|
|
25b50ebb27 | ||
|
|
6e5680b3c2 | ||
|
|
fe0414eda7 | ||
|
|
092539ae59 | ||
|
|
b16768fd0b | ||
|
|
8655dcaaf6 | ||
|
|
66621de270 | ||
|
|
a75ec704e6 | ||
|
|
5f00394f4f | ||
|
|
d3daa83b82 | ||
|
|
50f42d71a2 | ||
|
|
88d099a571 | ||
|
|
3e1f7d6d92 | ||
|
|
4c08c31149 | ||
|
|
29caf4cf53 | ||
|
|
0598578ed9 | ||
|
|
747cb15888 | ||
|
|
903961771b | ||
|
|
1fe24eeb12 | ||
|
|
8cd7e6e141 | ||
|
|
95c44d2a35 | ||
|
|
f0fd3186cd | ||
|
|
a18692bc46 | ||
|
|
b879418fa3 | ||
|
|
9b6acf6202 | ||
|
|
b1c81ed6e6 | ||
|
|
52af7d27da | ||
|
|
51caae7ebe | ||
|
|
9d7345892f | ||
|
|
6f032f678a | ||
|
|
d86020ceae | ||
|
|
2c7a66d8ec | ||
|
|
4eef8b9bd8 | ||
|
|
5251b30591 | ||
|
|
c283634efb | ||
|
|
e4676395c0 | ||
|
|
8c7e7e5510 | ||
|
|
69c1eff34e | ||
|
|
c3192e187c | ||
|
|
abd2c7642e | ||
|
|
49845d736b | ||
|
|
35f3cbe3f9 | ||
|
|
50433c7ab6 | ||
|
|
9f44870eb9 | ||
|
|
77753debeb | ||
|
|
9f1dd9f876 | ||
|
|
73ab6716a5 | ||
|
|
0deaa483dd | ||
|
|
f6af1a4ad5 | ||
|
|
43cef42fba | ||
|
|
66344c3ac7 | ||
|
|
c90d54664b | ||
|
|
7d85141c9b | ||
|
|
9b2be2ba50 | ||
|
|
ddde077d16 | ||
|
|
495315565a | ||
|
|
ee7a29326d | ||
|
|
6f298cab62 | ||
|
|
f5bb790edb | ||
|
|
85241a7dce | ||
|
|
f08d4bf7eb | ||
|
|
8ff58821ac | ||
|
|
ea581e67c8 | ||
|
|
0fe1407214 | ||
|
|
b8e7ae925f | ||
|
|
bdb6bf35bf | ||
|
|
9f3f09871e | ||
|
|
3b0ec7f695 | ||
|
|
fe01a7ccfc | ||
|
|
bd5f920151 | ||
|
|
b52b8d495e | ||
|
|
1af1ee2ad4 | ||
|
|
b94e015314 | ||
|
|
04942442aa | ||
|
|
eef0db6001 | ||
|
|
0e1b3607a4 | ||
|
|
89f175c73b | ||
|
|
25f680267c | ||
|
|
c06c9e92a5 | ||
|
|
d4fb4e8822 | ||
|
|
b23f3a119e | ||
|
|
1da3e89c7b | ||
|
|
bc98d3cef8 | ||
|
|
2bfb03cd29 | ||
|
|
2dbbd72a22 | ||
|
|
5391221391 | ||
|
|
1ad7b03023 | ||
|
|
66cec2a4bf | ||
|
|
a73c76cd02 | ||
|
|
e381920e86 | ||
|
|
5eefc7fd45 | ||
|
|
8792c06d87 | ||
|
|
0869b05ffd | ||
|
|
717cc575d1 | ||
|
|
ffd346af4b | ||
|
|
13274d63b0 | ||
|
|
72c856722b | ||
|
|
b69db1ccbf | ||
|
|
d2f01004a2 | ||
|
|
41357d44c1 | ||
|
|
56a7b9a305 | ||
|
|
12e4cdc43b | ||
|
|
d090e98bd4 | ||
|
|
8926669f3a | ||
|
|
3fb42ca4ee | ||
|
|
880d4b7249 | ||
|
|
00c59290fc | ||
|
|
b152832bf6 | ||
|
|
cd85e486e3 | ||
|
|
575b12dcdc | ||
|
|
c0caaaa8e5 | ||
|
|
c5e077efc1 | ||
|
|
842cb44cd2 | ||
|
|
de26699cfe | ||
|
|
38d11d0684 | ||
|
|
adf1c4d79e | ||
|
|
5b95aacb45 | ||
|
|
7be6a61b75 | ||
|
|
0577c90f81 | ||
|
|
995bb2f067 | ||
|
|
feccff9036 | ||
|
|
3b83ba9f65 | ||
|
|
068e8949c2 | ||
|
|
93eaff3d15 | ||
|
|
e3a39a21a2 | ||
|
|
a41bd181e1 | ||
|
|
4a5168f14c | ||
|
|
9a224398d3 | ||
|
|
e39ca8d8d2 | ||
|
|
b9e18b43e7 | ||
|
|
6d0c2ed362 | ||
|
|
d9ff72c907 | ||
|
|
03856b79b4 | ||
|
|
dcd74e8305 | ||
|
|
4f2ae14b3f | ||
|
|
5c234c1de4 | ||
|
|
0882435293 | ||
|
|
f0aec83be4 | ||
|
|
b3aa1f6dcd | ||
|
|
58e8aef5d8 | ||
|
|
5b0f49bab7 | ||
|
|
c27be2bf95 | ||
|
|
62eb7db0c7 | ||
|
|
1cfed8b6c7 | ||
|
|
1a33bb2d69 | ||
|
|
e2576e1770 | ||
|
|
40ce38770a | ||
|
|
1d0ed83967 | ||
|
|
6649d06fd8 | ||
|
|
4a40b5112c | ||
|
|
250a9646da | ||
|
|
4ba8848383 | ||
|
|
45b803ce41 | ||
|
|
e1ca54fc2e | ||
|
|
3b40b4aafa | ||
|
|
96868625cb | ||
|
|
5a9cc75ab9 | ||
|
|
fad429c922 | ||
|
|
8b694b1899 | ||
|
|
75614db5ce | ||
|
|
34f4c12e00 | ||
|
|
a7e63329b4 | ||
|
|
c17bc612ff | ||
|
|
d5559e44bb | ||
|
|
f83876e8f8 | ||
|
|
5797c6cb66 | ||
|
|
0a605781e9 | ||
|
|
d7ef38701b | ||
|
|
5e7b03d251 | ||
|
|
33a915fc67 | ||
|
|
83147922f4 | ||
|
|
c182ea944f | ||
|
|
e1f6a2bbd5 | ||
|
|
8aefe6c615 | ||
|
|
8e6c9e7c47 | ||
|
|
f0176c2801 | ||
|
|
9c4832bfbe | ||
|
|
6c66817502 | ||
|
|
694d6e5b33 | ||
|
|
f779755c11 | ||
|
|
53b59bd6f3 | ||
|
|
2c0d61111f | ||
|
|
0e686285f8 | ||
|
|
23975c100c | ||
|
|
84aa369809 | ||
|
|
1b3d3debc1 | ||
|
|
c1c4728961 | ||
|
|
7606c656a0 | ||
|
|
da3a7e47e3 | ||
|
|
43c0b512bd | ||
|
|
11eadd91af | ||
|
|
2234641e4a | ||
|
|
ccaa995702 | ||
|
|
0bcb6adf6a | ||
|
|
0030da78a6 | ||
|
|
5436d6831e | ||
|
|
7664fcf1f4 | ||
|
|
dfcfd9b04f | ||
|
|
9a938185d9 | ||
|
|
1ee556091c | ||
|
|
eee4b64c64 | ||
|
|
89a4b60f7e | ||
|
|
20846ef446 | ||
|
|
fd9db21ad7 | ||
|
|
3516392f06 | ||
|
|
54e0818fef | ||
|
|
f628fb3fd2 | ||
|
|
1116a0a647 | ||
|
|
f7be2c16c0 | ||
|
|
54cc252bd7 | ||
|
|
69ee137cea | ||
|
|
415b7e96fd | ||
|
|
e8e1c9dd1f | ||
|
|
474c4910e2 | ||
|
|
0e51d98c85 | ||
|
|
7ca90d11b3 | ||
|
|
69946c79d8 | ||
|
|
37df61113e | ||
|
|
b8becf4a56 | ||
|
|
5b7ff8ddee | ||
|
|
ef3007a603 | ||
|
|
0230323563 | ||
|
|
845d0f9182 | ||
|
|
f887d22a16 | ||
|
|
9780cf9062 | ||
|
|
10120bb0b6 | ||
|
|
574512f1bf | ||
|
|
744af3be3e | ||
|
|
2d07d89a73 | ||
|
|
f91488fa27 | ||
|
|
b0f212bad5 | ||
|
|
8038ebe37d | ||
|
|
4720182cf4 | ||
|
|
51bc17f76d | ||
|
|
d186f18c20 | ||
|
|
fc7493e149 | ||
|
|
3dc3031054 | ||
|
|
6cc88115b5 | ||
|
|
c33daddda2 | ||
|
|
952c58c226 | ||
|
|
49ea084336 | ||
|
|
1fe7e90da8 | ||
|
|
b92a9b6d1a | ||
|
|
43d5be40f0 | ||
|
|
2b026daf67 | ||
|
|
74541e23a4 | ||
|
|
c612806ef1 | ||
|
|
097882f658 | ||
|
|
24c9cf54af | ||
|
|
73712b4e39 | ||
|
|
2dd3f24fb2 | ||
|
|
7a91fb7512 | ||
|
|
1dabe49234 | ||
|
|
06ea07eca5 | ||
|
|
d414ea55f5 | ||
|
|
bb5a278fdb | ||
|
|
ef506006b3 | ||
|
|
7b7b5cc59f | ||
|
|
5ce370f18b | ||
|
|
73f4442410 | ||
|
|
ed39649721 | ||
|
|
cd4442b81e | ||
|
|
e8c4dc9a34 | ||
|
|
edb3eb5b2e | ||
|
|
ce5760d46c | ||
|
|
189a52c6d7 | ||
|
|
fe1252cca7 | ||
|
|
7b3fcb1d54 | ||
|
|
fa594fb3e9 | ||
|
|
830159390b | ||
|
|
b0a3f294c5 | ||
|
|
40ed16e118 | ||
|
|
6c5a0786c2 | ||
|
|
adda9136d0 | ||
|
|
cb224a973d | ||
|
|
d7074bf74f | ||
|
|
f051078c79 | ||
|
|
74b38c49fb | ||
|
|
cb439ba39a | ||
|
|
b34591ab59 | ||
|
|
8c874c76dc | ||
|
|
18ed1041ba | ||
|
|
ad5c82fec9 | ||
|
|
7629d447aa | ||
|
|
806addd3ae | ||
|
|
ad0c547f08 | ||
|
|
d92d4c2fa2 | ||
|
|
f163602942 | ||
|
|
2d23c6a45e | ||
|
|
86e600bec7 | ||
|
|
e4c378cb8f | ||
|
|
10999dc905 | ||
|
|
9b251d70e2 | ||
|
|
df538aa4d6 | ||
|
|
cd1d731b73 | ||
|
|
c355031a2c | ||
|
|
bd529a0478 | ||
|
|
7fcd424c71 | ||
|
|
439b8d772f | ||
|
|
d8540cdbd9 | ||
|
|
0dad2c863b | ||
|
|
179b2534d2 | ||
|
|
ff0b1bfd05 | ||
|
|
097c876578 | ||
|
|
d5ce4c6b7f | ||
|
|
7e5c764b50 | ||
|
|
77222824c3 | ||
|
|
e36033a3ac | ||
|
|
89d8208abd | ||
|
|
ab91d358ef | ||
|
|
147aad5064 | ||
|
|
f1ec10e3e1 | ||
|
|
b13d100107 | ||
|
|
13611e04ff | ||
|
|
92b7386e57 | ||
|
|
74367b1b74 | ||
|
|
dee2b12955 | ||
|
|
f6b4f23362 | ||
|
|
85b4be3ff4 | ||
|
|
20cf3a6a9b | ||
|
|
205a4fc530 | ||
|
|
2d9285f790 | ||
|
|
3915b735ae | ||
|
|
61a1e769d7 | ||
|
|
2ba705ffe9 | ||
|
|
78fa747973 | ||
|
|
c33c227d95 | ||
|
|
ea05c593aa | ||
|
|
74193d1182 | ||
|
|
41bda76980 | ||
|
|
ed411fa944 | ||
|
|
d6661e4558 | ||
|
|
9989d6db75 | ||
|
|
4a5a2ceb31 | ||
|
|
6704245a41 | ||
|
|
128728bfcb | ||
|
|
63b5d83728 | ||
|
|
bddd31b1b0 | ||
|
|
071ed3f1ed | ||
|
|
98bd1552b9 | ||
|
|
7554d51b0a | ||
|
|
3f89f3f0f7 | ||
|
|
b005d661f8 | ||
|
|
05d4b2793b | ||
|
|
cf29e4b7bd | ||
|
|
3dfce70fdc | ||
|
|
059832d324 | ||
|
|
1452502fbf | ||
|
|
1f22f8ab6a | ||
|
|
2eb30c9ba9 | ||
|
|
988b2b2bc5 | ||
|
|
5ee56a4cd3 | ||
|
|
37091d1254 | ||
|
|
68f4fd995e | ||
|
|
ba152fc61d | ||
|
|
074e6d8b62 | ||
|
|
283ed302db | ||
|
|
4b345c0ea6 | ||
|
|
fd4584519e | ||
|
|
de19cf652a | ||
|
|
7c840d71e2 | ||
|
|
0d0e6b2feb | ||
|
|
984617eed8 | ||
|
|
2d16205091 | ||
|
|
c41b867c7f | ||
|
|
7f196fc415 | ||
|
|
ec3e7968a6 | ||
|
|
82fbec02f0 | ||
|
|
9bd6200625 | ||
|
|
dac4f9cb3f | ||
|
|
8a11d2c649 | ||
|
|
2c99c8cb86 | ||
|
|
b6eb825c5c | ||
|
|
3601245d6f | ||
|
|
de46359742 | ||
|
|
de4583d14b | ||
|
|
60f975f0dc | ||
|
|
d38cc0ee82 | ||
|
|
36141f3b1a | ||
|
|
6f89d0672d | ||
|
|
7597cd9172 | ||
|
|
8c1fa84c6e | ||
|
|
7bc132bc0d | ||
|
|
ec4c864354 | ||
|
|
26e9c37be1 | ||
|
|
4c4687eb8e | ||
|
|
b8e01208af | ||
|
|
c7de994183 | ||
|
|
b0b220f085 | ||
|
|
5fd0e31920 | ||
|
|
bf10d34a8d | ||
|
|
1b44e84ef3 | ||
|
|
66ec5864d0 | ||
|
|
f40cffa746 | ||
|
|
4caa13f1c0 | ||
|
|
6864a457fe | ||
|
|
d345316db8 | ||
|
|
a2399f8842 | ||
|
|
92947a507c | ||
|
|
4f2d059de4 | ||
|
|
a464acf354 | ||
|
|
c85436118a | ||
|
|
fc1c29d14f | ||
|
|
17ea432604 | ||
|
|
3e0bcddd4d | ||
|
|
73ae408e20 | ||
|
|
66483bdd72 | ||
|
|
f6e265bccb | ||
|
|
60ff292f14 | ||
|
|
b2806dcb75 | ||
|
|
55b9e367b3 | ||
|
|
2d91344a5b | ||
|
|
eee05e1451 | ||
|
|
6d28ce7641 | ||
|
|
4e48f2d581 | ||
|
|
6d4e532ebe | ||
|
|
73c02f74f2 | ||
|
|
7add727db0 | ||
|
|
44cdd5ed55 | ||
|
|
7fda197391 | ||
|
|
8ca10e4f0b | ||
|
|
8a43a7022d | ||
|
|
e46e4a7993 | ||
|
|
3097106fe4 | ||
|
|
50cb6b4e30 | ||
|
|
e569eb276e | ||
|
|
17cd8c1d61 | ||
|
|
090fb0f417 | ||
|
|
e0941e2f45 | ||
|
|
b593220805 | ||
|
|
2b8d2d9c83 | ||
|
|
780fecff3b | ||
|
|
23381d936f | ||
|
|
a55ab49b6d |
23
.appveyor.yml
Normal file
23
.appveyor.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
version: 1.0.{build}
|
||||||
|
image: Visual Studio 2019
|
||||||
|
|
||||||
|
platform: x64
|
||||||
|
configuration: Debug
|
||||||
|
|
||||||
|
build:
|
||||||
|
project: SpaceStation.sln
|
||||||
|
parallel: false
|
||||||
|
verbosity: minimal
|
||||||
|
|
||||||
|
before_build:
|
||||||
|
- cmd: py -3 -m pip install --user requests
|
||||||
|
- cmd: py -3 RUN_THIS.py --no-prompt
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- ps: dotnet build SpaceStation14.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" /p:AppVeyor=yes
|
||||||
|
|
||||||
|
test:
|
||||||
|
assemblies:
|
||||||
|
only:
|
||||||
|
- bin/Content.IntegrationTests/Content.IntegrationTests.dll
|
||||||
|
- bin/Content.Tests/Content.Tests.dll
|
||||||
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
|||||||
* @PJB3005
|
* @PJB3005 @Silvertorch5
|
||||||
|
|||||||
20
.gitignore
vendored
20
.gitignore
vendored
@@ -261,4 +261,22 @@ __pycache__/
|
|||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
# Visual Studio Code workspace settings.
|
# Visual Studio Code workspace settings.
|
||||||
.vscode/
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# Release package files go here:
|
||||||
|
release/
|
||||||
|
|
||||||
|
# Apple please go.
|
||||||
|
.DS_Store
|
||||||
|
# KDE, come in.
|
||||||
|
.directory
|
||||||
|
|
||||||
|
BuildFiles/Mac/Space Station 14.app/Contents/MacOS/Godot
|
||||||
|
BuildFiles/Mac/Space Station 14.app/Contents/MacOS/GodotSharpTools.dll
|
||||||
|
BuildFiles/Mac/Space Station 14.app/Contents/MacOS/mscorlib.dll
|
||||||
|
BuildFiles/Mac/Space Station 14.app/Contents/MacOS/libmonosgen-2.0.dylib
|
||||||
|
BuildFiles/Windows/Godot/*
|
||||||
|
|
||||||
|
# Working on the tools scripts is getting annoying okay?
|
||||||
|
.mypy_cache/
|
||||||
|
|||||||
7
.gitmodules
vendored
7
.gitmodules
vendored
@@ -1,3 +1,4 @@
|
|||||||
[submodule "engine"]
|
[submodule "RobustToolbox"]
|
||||||
path = engine
|
path = RobustToolbox
|
||||||
url = https://github.com/space-wizards/space-station-14.git
|
url = https://github.com/space-wizards/RobustToolbox.git
|
||||||
|
branch = master
|
||||||
40
.travis.yml
Normal file
40
.travis.yml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
language: csharp
|
||||||
|
|
||||||
|
dist: trusty
|
||||||
|
sudo: false
|
||||||
|
mono: none
|
||||||
|
# dotnet: 3.1.100 # Travis is shitting itself right now and it can't locate .NET Core packages.
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
#- osx
|
||||||
|
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
#sources:
|
||||||
|
#- deadsnakes
|
||||||
|
|
||||||
|
packages:
|
||||||
|
- python3.5
|
||||||
|
- python3-pip
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- packages/
|
||||||
|
- RobustToolbox/Dependencies/
|
||||||
|
|
||||||
|
install:
|
||||||
|
- curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version 3.1.100
|
||||||
|
|
||||||
|
#before_install:
|
||||||
|
# - if [ $TRAVIS_OS_NAME = osx ]; then brew update && brew upgrade python; fi
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
#- "if [ $TRAVIS_OS_NAME = linux ]; then pyenv shell 3.6; fi"
|
||||||
|
- "python3.5 -m pip install --user requests"
|
||||||
|
- "python3.5 RUN_THIS.py --no-prompt"
|
||||||
|
|
||||||
|
script:
|
||||||
|
- "Tools/run_travis.sh"
|
||||||
|
|
||||||
|
|
||||||
6
.vscode/extensions.json
vendored
Normal file
6
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"ms-vscode.csharp",
|
||||||
|
"editorconfig.editorconfig"
|
||||||
|
]
|
||||||
|
}
|
||||||
4
BuildChecker/.gitignore
vendored
4
BuildChecker/.gitignore
vendored
@@ -1 +1,5 @@
|
|||||||
INSTALLED_HOOKS_VERSION
|
INSTALLED_HOOKS_VERSION
|
||||||
|
DISABLE_SUBMODULE_AUTOUPDATE
|
||||||
|
*.nuget*
|
||||||
|
project.assets.json
|
||||||
|
project.packagespec.json
|
||||||
@@ -16,12 +16,27 @@ https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Python>python3</Python>
|
<Python>python3</Python>
|
||||||
<Python Condition="'$(OS)'=='Windows_NT' Or '$(OS)'=='Windows'">py -3</Python>
|
<Python Condition="'$(OS)'=='Windows_NT' Or '$(OS)'=='Windows'">py -3</Python>
|
||||||
|
<ProjectGuid>{C899FCA4-7037-4E49-ABC2-44DE72487110}</ProjectGuid>
|
||||||
|
<TargetFrameworkMoniker>.NETFramework, Version=v4.7.2</TargetFrameworkMoniker>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<StartupObject />
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Target Name="Build">
|
<Target Name="Build">
|
||||||
<Exec Command="$(Python) git_helper.py" CustomErrorRegularExpression="^Error"/>
|
<Exec Command="$(Python) git_helper.py" CustomErrorRegularExpression="^Error" />
|
||||||
</Target>
|
</Target>
|
||||||
<Target Name="Rebuild" DependsOnTargets="Build" />
|
<Target Name="Rebuild" DependsOnTargets="Build" />
|
||||||
<Target Name="Clean">
|
<Target Name="Clean">
|
||||||
<Message Importance="low" Text="Ignoring 'Clean' target." />
|
<Message Importance="low" Text="Ignoring 'Clean' target." />
|
||||||
</Target>
|
</Target>
|
||||||
|
<Target Name="Compile">
|
||||||
|
</Target>
|
||||||
|
<Target Name="CoreCompile">
|
||||||
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import shutil
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
SOLUTION_PATH = Path("..") / "SpaceStation14Content.sln"
|
SOLUTION_PATH = Path("..") / "SpaceStation14.sln"
|
||||||
CURRENT_HOOKS_VERSION = "1" # If this doesn't match the saved version we overwrite them all.
|
# If this doesn't match the saved version we overwrite them all.
|
||||||
|
CURRENT_HOOKS_VERSION = "2"
|
||||||
QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"
|
QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"
|
||||||
|
|
||||||
|
|
||||||
def run_command(command: List[str], capture: bool = False) -> subprocess.CompletedProcess:
|
def run_command(command: List[str], capture: bool = False) -> subprocess.CompletedProcess:
|
||||||
"""
|
"""
|
||||||
Runs a command with pretty output.
|
Runs a command with pretty output.
|
||||||
@@ -40,6 +42,9 @@ def update_submodules():
|
|||||||
Updates all submodules.
|
Updates all submodules.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if os.path.isfile("DISABLE_SUBMODULE_AUTOUPDATE"):
|
||||||
|
return
|
||||||
|
|
||||||
# If the status doesn't match, force VS to reload the solution.
|
# If the status doesn't match, force VS to reload the solution.
|
||||||
# status = run_command(["git", "submodule", "status"], capture=True)
|
# status = run_command(["git", "submodule", "status"], capture=True)
|
||||||
run_command(["git", "submodule", "update", "--init", "--recursive"])
|
run_command(["git", "submodule", "update", "--init", "--recursive"])
|
||||||
@@ -50,6 +55,7 @@ def update_submodules():
|
|||||||
# print("Git submodules changed. Reloading solution.")
|
# print("Git submodules changed. Reloading solution.")
|
||||||
# reset_solution()
|
# reset_solution()
|
||||||
|
|
||||||
|
|
||||||
def install_hooks():
|
def install_hooks():
|
||||||
"""
|
"""
|
||||||
Installs the necessary git hooks into .git/hooks.
|
Installs the necessary git hooks into .git/hooks.
|
||||||
@@ -77,7 +83,8 @@ def install_hooks():
|
|||||||
|
|
||||||
for filename in os.listdir(str(hooks_source_dir)):
|
for filename in os.listdir(str(hooks_source_dir)):
|
||||||
print("Copying hook {}".format(filename))
|
print("Copying hook {}".format(filename))
|
||||||
shutil.copyfile(str(hooks_source_dir/filename), str(hooks_target_dir/filename))
|
shutil.copy2(str(hooks_source_dir/filename),
|
||||||
|
str(hooks_target_dir/filename))
|
||||||
|
|
||||||
|
|
||||||
def reset_solution():
|
def reset_solution():
|
||||||
@@ -91,6 +98,7 @@ def reset_solution():
|
|||||||
with SOLUTION_PATH.open("w") as f:
|
with SOLUTION_PATH.open("w") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
install_hooks()
|
install_hooks()
|
||||||
update_submodules()
|
update_submodules()
|
||||||
|
|||||||
0
BuildChecker/hooks/post-checkout
Normal file → Executable file
0
BuildChecker/hooks/post-checkout
Normal file → Executable file
0
BuildChecker/hooks/post-merge
Normal file → Executable file
0
BuildChecker/hooks/post-merge
Normal file → Executable file
20
BuildFiles/Mac/Space Station 14.app/Contents/Info.plist
Normal file
20
BuildFiles/Mac/Space Station 14.app/Contents/Info.plist
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>SS14</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Space Station 14</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>SS14</string>
|
||||||
|
<!--
|
||||||
|
Just a note about this icon.
|
||||||
|
MacOS seems REALLY iffy about this and even when the file is correct,
|
||||||
|
it can take forever before it decides to actually update it and display it.
|
||||||
|
TL;DR Apple is stupid.
|
||||||
|
-->
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>ss14</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
8
BuildFiles/Mac/Space Station 14.app/Contents/MacOS/SS14
Executable file
8
BuildFiles/Mac/Space Station 14.app/Contents/MacOS/SS14
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# cd to file containing script or something?
|
||||||
|
BASEDIR=$(dirname "$0")
|
||||||
|
echo "$BASEDIR"
|
||||||
|
cd "$BASEDIR"
|
||||||
|
|
||||||
|
exec ../Resources/Robust.Client "$@"
|
||||||
BIN
BuildFiles/Mac/Space Station 14.app/Contents/Resources/ss14.icns
Normal file
BIN
BuildFiles/Mac/Space Station 14.app/Contents/Resources/ss14.icns
Normal file
Binary file not shown.
166
Content.Benchmarks/ColorInterpolateBenchmark.cs
Normal file
166
Content.Benchmarks/ColorInterpolateBenchmark.cs
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#if NETCOREAPP
|
||||||
|
using System.Runtime.Intrinsics;
|
||||||
|
using System.Runtime.Intrinsics.X86;
|
||||||
|
#endif
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using SysVector4 = System.Numerics.Vector4;
|
||||||
|
|
||||||
|
namespace Content.Benchmarks
|
||||||
|
{
|
||||||
|
[DisassemblyDiagnoser]
|
||||||
|
public class ColorInterpolateBenchmark
|
||||||
|
{
|
||||||
|
#if NETCOREAPP
|
||||||
|
private const MethodImplOptions AggressiveOpt = MethodImplOptions.AggressiveOptimization;
|
||||||
|
#else
|
||||||
|
private const MethodImplOptions AggressiveOpt = default;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private (Color, Color)[] _colors;
|
||||||
|
private Color[] _output;
|
||||||
|
|
||||||
|
[Params(100)] public int N { get; set; }
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
var random = new Random(3005);
|
||||||
|
|
||||||
|
_colors = new (Color, Color)[N];
|
||||||
|
_output = new Color[N];
|
||||||
|
|
||||||
|
for (var i = 0; i < N; i++)
|
||||||
|
{
|
||||||
|
var r1 = random.NextFloat();
|
||||||
|
var g1 = random.NextFloat();
|
||||||
|
var b1 = random.NextFloat();
|
||||||
|
var a1 = random.NextFloat();
|
||||||
|
|
||||||
|
var r2 = random.NextFloat();
|
||||||
|
var g2 = random.NextFloat();
|
||||||
|
var b2 = random.NextFloat();
|
||||||
|
var a2 = random.NextFloat();
|
||||||
|
|
||||||
|
_colors[i] = (new Color(r1, g1, b1, a1), new Color(r2, g2, b2, a2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchSimple()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < N; i++)
|
||||||
|
{
|
||||||
|
ref var tuple = ref _colors[i];
|
||||||
|
_output[i] = InterpolateSimple(tuple.Item1, tuple.Item2, 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchSysVector4In()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < N; i++)
|
||||||
|
{
|
||||||
|
ref var tuple = ref _colors[i];
|
||||||
|
_output[i] = InterpolateSysVector4In(tuple.Item1, tuple.Item2, 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchSysVector4()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < N; i++)
|
||||||
|
{
|
||||||
|
ref var tuple = ref _colors[i];
|
||||||
|
_output[i] = InterpolateSysVector4(tuple.Item1, tuple.Item2, 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if NETCOREAPP
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchSimd()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < N; i++)
|
||||||
|
{
|
||||||
|
ref var tuple = ref _colors[i];
|
||||||
|
_output[i] = InterpolateSimd(tuple.Item1, tuple.Item2, 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchSimdIn()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < N; i++)
|
||||||
|
{
|
||||||
|
ref var tuple = ref _colors[i];
|
||||||
|
_output[i] = InterpolateSimdIn(tuple.Item1, tuple.Item2, 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[MethodImpl(AggressiveOpt)]
|
||||||
|
public static Color InterpolateSimple(Color a, Color b, float lambda)
|
||||||
|
{
|
||||||
|
return new Color(
|
||||||
|
a.R + (b.R - a.R) * lambda,
|
||||||
|
a.G + (b.G - a.G) * lambda,
|
||||||
|
a.B + (b.G - a.B) * lambda,
|
||||||
|
a.A + (b.A - a.A) * lambda
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(AggressiveOpt)]
|
||||||
|
public static Color InterpolateSysVector4(Color a, Color b,
|
||||||
|
float lambda)
|
||||||
|
{
|
||||||
|
ref var sva = ref Unsafe.As<Color, SysVector4>(ref a);
|
||||||
|
ref var svb = ref Unsafe.As<Color, SysVector4>(ref b);
|
||||||
|
|
||||||
|
var res = SysVector4.Lerp(sva, svb, lambda);
|
||||||
|
|
||||||
|
return Unsafe.As<SysVector4, Color>(ref res);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(AggressiveOpt)]
|
||||||
|
public static Color InterpolateSysVector4In(in Color endPoint1, in Color endPoint2,
|
||||||
|
float lambda)
|
||||||
|
{
|
||||||
|
ref var sva = ref Unsafe.As<Color, SysVector4>(ref Unsafe.AsRef(endPoint1));
|
||||||
|
ref var svb = ref Unsafe.As<Color, SysVector4>(ref Unsafe.AsRef(endPoint2));
|
||||||
|
|
||||||
|
var res = SysVector4.Lerp(svb, sva, lambda);
|
||||||
|
|
||||||
|
return Unsafe.As<SysVector4, Color>(ref res);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if NETCOREAPP
|
||||||
|
[MethodImpl(AggressiveOpt)]
|
||||||
|
public static Color InterpolateSimd(Color a, Color b,
|
||||||
|
float lambda)
|
||||||
|
{
|
||||||
|
var vecA = Unsafe.As<Color, Vector128<float>>(ref a);
|
||||||
|
var vecB = Unsafe.As<Color, Vector128<float>>(ref b);
|
||||||
|
|
||||||
|
vecB = Fma.MultiplyAdd(Sse.Subtract(vecB, vecA), Vector128.Create(lambda), vecA);
|
||||||
|
|
||||||
|
return Unsafe.As<Vector128<float>, Color>(ref vecB);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(AggressiveOpt)]
|
||||||
|
public static Color InterpolateSimdIn(in Color a, in Color b,
|
||||||
|
float lambda)
|
||||||
|
{
|
||||||
|
var vecA = Unsafe.As<Color, Vector128<float>>(ref Unsafe.AsRef(a));
|
||||||
|
var vecB = Unsafe.As<Color, Vector128<float>>(ref Unsafe.AsRef(b));
|
||||||
|
|
||||||
|
vecB = Fma.MultiplyAdd(Sse.Subtract(vecB, vecA), Vector128.Create(lambda), vecA);
|
||||||
|
|
||||||
|
return Unsafe.As<Vector128<float>, Color>(ref vecB);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
73
Content.Benchmarks/ComponentManagerGetAllComponents.cs
Normal file
73
Content.Benchmarks/ComponentManagerGetAllComponents.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Moq;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Benchmarks
|
||||||
|
{
|
||||||
|
public class ComponentManagerGetAllComponents
|
||||||
|
{
|
||||||
|
private readonly List<IEntity> _entities = new List<IEntity>();
|
||||||
|
|
||||||
|
private IComponentManager _componentManager;
|
||||||
|
|
||||||
|
[Params(500, 1000, 5000)] public int N { get; set; }
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
// Initialize component manager.
|
||||||
|
IoCManager.InitThread();
|
||||||
|
|
||||||
|
IoCManager.Register<IComponentManager, ComponentManager>();
|
||||||
|
|
||||||
|
var dummyReg = new Mock<IComponentRegistration>();
|
||||||
|
dummyReg.SetupGet(p => p.Name).Returns("Dummy");
|
||||||
|
dummyReg.SetupGet(p => p.Type).Returns(typeof(DummyComponent));
|
||||||
|
dummyReg.SetupGet(p => p.NetID).Returns((uint?) null);
|
||||||
|
dummyReg.SetupGet(p => p.NetworkSynchronizeExistence).Returns(false);
|
||||||
|
dummyReg.SetupGet(p => p.References).Returns(new [] {typeof(DummyComponent)});
|
||||||
|
|
||||||
|
var componentFactory = new Mock<IComponentFactory>();
|
||||||
|
componentFactory.Setup(p => p.GetComponent<DummyComponent>()).Returns(new DummyComponent());
|
||||||
|
componentFactory.Setup(p => p.GetRegistration(It.IsAny<DummyComponent>())).Returns(dummyReg.Object);
|
||||||
|
|
||||||
|
IoCManager.RegisterInstance<IComponentFactory>(componentFactory.Object);
|
||||||
|
|
||||||
|
IoCManager.BuildGraph();
|
||||||
|
|
||||||
|
_componentManager = IoCManager.Resolve<IComponentManager>();
|
||||||
|
|
||||||
|
// Initialize N entities with one component.
|
||||||
|
for (var i = 0; i < N; i++)
|
||||||
|
{
|
||||||
|
var entity = new Entity();
|
||||||
|
entity.SetUid(new EntityUid(i + 1));
|
||||||
|
_entities.Add(entity);
|
||||||
|
|
||||||
|
_componentManager.AddComponent<DummyComponent>(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int Run()
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
foreach (var _ in _componentManager.GetAllComponents<DummyComponent>())
|
||||||
|
{
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DummyComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "Dummy";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Content.Benchmarks/Content.Benchmarks.csproj
Normal file
29
Content.Benchmarks/Content.Benchmarks.csproj
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
|
||||||
|
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||||
|
<OutputPath>..\bin\Content.Benchmarks\</OutputPath>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<Platforms>x64</Platforms>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<LangVersion>8</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Content.Client\Content.Client.csproj" />
|
||||||
|
<ProjectReference Include="..\Content.Server\Content.Server.csproj" />
|
||||||
|
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||||
|
<ProjectReference Include="..\Content.Tests\Content.Tests.csproj" />
|
||||||
|
<ProjectReference Include="..\Content.IntegrationTests\Content.IntegrationTests.csproj" />
|
||||||
|
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
|
||||||
|
<ProjectReference Include="..\RobustToolbox\Robust.Server\Robust.Server.csproj" />
|
||||||
|
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
||||||
|
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
|
||||||
|
<ProjectReference Include="..\RobustToolbox\Robust.UnitTesting\Robust.UnitTesting.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
69
Content.Benchmarks/DependencyInjectBenchmark.cs
Normal file
69
Content.Benchmarks/DependencyInjectBenchmark.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Benchmarks
|
||||||
|
{
|
||||||
|
// To actually run this benchmark you'll have to make DependencyCollection public so it's accessible.
|
||||||
|
|
||||||
|
public class DependencyInjectBenchmark
|
||||||
|
{
|
||||||
|
[Params(InjectMode.Reflection, InjectMode.DynamicMethod)]
|
||||||
|
public InjectMode Mode { get; set; }
|
||||||
|
|
||||||
|
private DependencyCollection _dependencyCollection;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_dependencyCollection = new DependencyCollection();
|
||||||
|
_dependencyCollection.Register<X1, X1>();
|
||||||
|
_dependencyCollection.Register<X2, X2>();
|
||||||
|
_dependencyCollection.Register<X3, X3>();
|
||||||
|
_dependencyCollection.Register<X4, X4>();
|
||||||
|
_dependencyCollection.Register<X5, X5>();
|
||||||
|
|
||||||
|
_dependencyCollection.BuildGraph();
|
||||||
|
|
||||||
|
switch (Mode)
|
||||||
|
{
|
||||||
|
case InjectMode.Reflection:
|
||||||
|
break;
|
||||||
|
case InjectMode.DynamicMethod:
|
||||||
|
// Running this without oneOff will cause DependencyCollection to cache the DynamicMethod injector.
|
||||||
|
// So future injections (even with oneOff) will keep using the DynamicMethod.
|
||||||
|
// AKA, be fast.
|
||||||
|
_dependencyCollection.InjectDependencies(new TestDummy());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void Inject()
|
||||||
|
{
|
||||||
|
_dependencyCollection.InjectDependencies(new TestDummy(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum InjectMode
|
||||||
|
{
|
||||||
|
Reflection,
|
||||||
|
DynamicMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class X1 { }
|
||||||
|
private sealed class X2 { }
|
||||||
|
private sealed class X3 { }
|
||||||
|
private sealed class X4 { }
|
||||||
|
private sealed class X5 { }
|
||||||
|
|
||||||
|
private sealed class TestDummy
|
||||||
|
{
|
||||||
|
[Dependency] private readonly X1 _x1;
|
||||||
|
[Dependency] private readonly X2 _x2;
|
||||||
|
[Dependency] private readonly X3 _x3;
|
||||||
|
[Dependency] private readonly X4 _x4;
|
||||||
|
[Dependency] private readonly X5 _x5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
12
Content.Benchmarks/Program.cs
Normal file
12
Content.Benchmarks/Program.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using BenchmarkDotNet.Running;
|
||||||
|
|
||||||
|
namespace Content.Benchmarks
|
||||||
|
{
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
BenchmarkRunner.Run<ComponentManagerGetAllComponents>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Content.Client/AssemblyInfo.cs
Normal file
3
Content.Client/AssemblyInfo.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Content.Tests")]
|
||||||
223
Content.Client/Chat/ChatBox.cs
Normal file
223
Content.Client/Chat/ChatBox.cs
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.Chat;
|
||||||
|
using Robust.Client.Graphics.Drawing;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.Input;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client.Chat
|
||||||
|
{
|
||||||
|
public class ChatBox : MarginContainer
|
||||||
|
{
|
||||||
|
public delegate void TextSubmitHandler(ChatBox chatBox, string text);
|
||||||
|
|
||||||
|
public delegate void FilterToggledHandler(ChatBox chatBox, BaseButton.ButtonToggledEventArgs e);
|
||||||
|
|
||||||
|
private const int MaxLinePixelLength = 500;
|
||||||
|
|
||||||
|
private readonly IList<string> _inputHistory = new List<string>();
|
||||||
|
|
||||||
|
private readonly ILocalizationManager localize = IoCManager.Resolve<ILocalizationManager>();
|
||||||
|
|
||||||
|
public LineEdit Input { get; private set; }
|
||||||
|
public OutputPanel Contents { get; }
|
||||||
|
|
||||||
|
// Buttons for filtering
|
||||||
|
public Button AllButton { get; }
|
||||||
|
public Button LocalButton { get; }
|
||||||
|
public Button OOCButton { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Index while cycling through the input history. -1 means not going through history.
|
||||||
|
/// </summary>
|
||||||
|
private int _inputIndex = -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Message that WAS being input before going through history began.
|
||||||
|
/// </summary>
|
||||||
|
private string _inputTemp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default formatting string for the ClientChatConsole.
|
||||||
|
/// </summary>
|
||||||
|
public string DefaultChatFormat { get; set; }
|
||||||
|
|
||||||
|
public bool ReleaseFocusOnEnter { get; set; } = true;
|
||||||
|
|
||||||
|
public ChatBox()
|
||||||
|
{
|
||||||
|
/*MarginLeft = -475.0f;
|
||||||
|
MarginTop = 10.0f;
|
||||||
|
MarginRight = -10.0f;
|
||||||
|
MarginBottom = 235.0f;
|
||||||
|
|
||||||
|
AnchorLeft = 1.0f;
|
||||||
|
AnchorRight = 1.0f;*/
|
||||||
|
|
||||||
|
var outerVBox = new VBoxContainer();
|
||||||
|
|
||||||
|
var panelContainer = new PanelContainer
|
||||||
|
{
|
||||||
|
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#25252aaa")},
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand
|
||||||
|
};
|
||||||
|
var vBox = new VBoxContainer();
|
||||||
|
panelContainer.AddChild(vBox);
|
||||||
|
var hBox = new HBoxContainer();
|
||||||
|
|
||||||
|
outerVBox.AddChild(panelContainer);
|
||||||
|
outerVBox.AddChild(hBox);
|
||||||
|
|
||||||
|
var contentMargin = new MarginContainer
|
||||||
|
{
|
||||||
|
MarginLeftOverride = 4, MarginRightOverride = 4,
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand
|
||||||
|
};
|
||||||
|
Contents = new OutputPanel();
|
||||||
|
contentMargin.AddChild(Contents);
|
||||||
|
vBox.AddChild(contentMargin);
|
||||||
|
|
||||||
|
Input = new LineEdit();
|
||||||
|
Input.OnKeyBindDown += InputKeyBindDown;
|
||||||
|
Input.OnTextEntered += Input_OnTextEntered;
|
||||||
|
vBox.AddChild(Input);
|
||||||
|
|
||||||
|
AllButton = new Button
|
||||||
|
{
|
||||||
|
Text = localize.GetString("All"),
|
||||||
|
Name = "ALL",
|
||||||
|
SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Expand,
|
||||||
|
ToggleMode = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
LocalButton = new Button
|
||||||
|
{
|
||||||
|
Text = localize.GetString("Local"),
|
||||||
|
Name = "Local",
|
||||||
|
ToggleMode = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
OOCButton = new Button
|
||||||
|
{
|
||||||
|
Text = localize.GetString("OOC"),
|
||||||
|
Name = "OOC",
|
||||||
|
ToggleMode = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
AllButton.OnToggled += OnFilterToggled;
|
||||||
|
LocalButton.OnToggled += OnFilterToggled;
|
||||||
|
OOCButton.OnToggled += OnFilterToggled;
|
||||||
|
|
||||||
|
hBox.AddChild(AllButton);
|
||||||
|
hBox.AddChild(LocalButton);
|
||||||
|
hBox.AddChild(OOCButton);
|
||||||
|
|
||||||
|
AddChild(outerVBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||||
|
{
|
||||||
|
base.KeyBindDown(args);
|
||||||
|
|
||||||
|
if (!args.CanFocus)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Input.GrabKeyboardFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InputKeyBindDown(GUIBoundKeyEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Function == EngineKeyFunctions.TextReleaseFocus)
|
||||||
|
{
|
||||||
|
Input.ReleaseKeyboardFocus();
|
||||||
|
args.Handle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (args.Function == EngineKeyFunctions.TextHistoryPrev)
|
||||||
|
{
|
||||||
|
if (_inputIndex == -1 && _inputHistory.Count != 0)
|
||||||
|
{
|
||||||
|
_inputTemp = Input.Text;
|
||||||
|
_inputIndex++;
|
||||||
|
}
|
||||||
|
else if (_inputIndex + 1 < _inputHistory.Count)
|
||||||
|
{
|
||||||
|
_inputIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_inputIndex != -1)
|
||||||
|
{
|
||||||
|
Input.Text = _inputHistory[_inputIndex];
|
||||||
|
}
|
||||||
|
Input.CursorPos = Input.Text.Length;
|
||||||
|
|
||||||
|
args.Handle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (args.Function == EngineKeyFunctions.TextHistoryNext)
|
||||||
|
{
|
||||||
|
if (_inputIndex == 0)
|
||||||
|
{
|
||||||
|
Input.Text = _inputTemp;
|
||||||
|
_inputTemp = "";
|
||||||
|
_inputIndex--;
|
||||||
|
}
|
||||||
|
else if (_inputIndex != -1)
|
||||||
|
{
|
||||||
|
_inputIndex--;
|
||||||
|
Input.Text = _inputHistory[_inputIndex];
|
||||||
|
}
|
||||||
|
Input.CursorPos = Input.Text.Length;
|
||||||
|
|
||||||
|
args.Handle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event TextSubmitHandler TextSubmitted;
|
||||||
|
|
||||||
|
public event FilterToggledHandler FilterToggled;
|
||||||
|
|
||||||
|
public void AddLine(string message, ChatChannel channel, Color color)
|
||||||
|
{
|
||||||
|
if (Disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var formatted = new FormattedMessage(3);
|
||||||
|
formatted.PushColor(color);
|
||||||
|
formatted.AddText(message);
|
||||||
|
formatted.Pop();
|
||||||
|
Contents.AddMessage(formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Input_OnTextEntered(LineEdit.LineEditEventArgs args)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(args.Text))
|
||||||
|
{
|
||||||
|
TextSubmitted?.Invoke(this, args.Text);
|
||||||
|
_inputHistory.Insert(0, args.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
_inputIndex = -1;
|
||||||
|
|
||||||
|
Input.Clear();
|
||||||
|
|
||||||
|
if (ReleaseFocusOnEnter)
|
||||||
|
{
|
||||||
|
Input.ReleaseKeyboardFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFilterToggled(BaseButton.ButtonToggledEventArgs args)
|
||||||
|
{
|
||||||
|
FilterToggled?.Invoke(this, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
411
Content.Client/Chat/ChatManager.cs
Normal file
411
Content.Client/Chat/ChatManager.cs
Normal file
@@ -0,0 +1,411 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Client.Interfaces.Chat;
|
||||||
|
using Content.Shared.Chat;
|
||||||
|
using Robust.Client.Console;
|
||||||
|
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||||
|
using Robust.Client.Interfaces.UserInterface;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client.Chat
|
||||||
|
{
|
||||||
|
internal sealed class ChatManager : IChatManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The max amount of chars allowed to fit in a single speech bubble.
|
||||||
|
/// </summary>
|
||||||
|
private const int SingleBubbleCharLimit = 100;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base queue delay each speech bubble has.
|
||||||
|
/// </summary>
|
||||||
|
private const float BubbleDelayBase = 0.2f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Factor multiplied by speech bubble char length to add to delay.
|
||||||
|
/// </summary>
|
||||||
|
private const float BubbleDelayFactor = 0.8f / SingleBubbleCharLimit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The max amount of speech bubbles over a single entity at once.
|
||||||
|
/// </summary>
|
||||||
|
private const int SpeechBubbleCap = 4;
|
||||||
|
|
||||||
|
private const char ConCmdSlash = '/';
|
||||||
|
private const char OOCAlias = '[';
|
||||||
|
private const char MeAlias = '@';
|
||||||
|
|
||||||
|
private readonly List<StoredChatMessage> filteredHistory = new List<StoredChatMessage>();
|
||||||
|
|
||||||
|
// Filter Button States
|
||||||
|
private bool _allState;
|
||||||
|
private bool _localState;
|
||||||
|
private bool _oocState;
|
||||||
|
|
||||||
|
// Flag Enums for holding filtered channels
|
||||||
|
private ChatChannel _filteredChannels;
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IClientNetManager _netManager;
|
||||||
|
[Dependency] private readonly IClientConsole _console;
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager;
|
||||||
|
[Dependency] private readonly IEyeManager _eyeManager;
|
||||||
|
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private ChatBox _currentChatBox;
|
||||||
|
private Control _speechBubbleRoot;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Speech bubbles that are currently visible on screen.
|
||||||
|
/// We track them to push them up when new ones get added.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<EntityUid, List<SpeechBubble>> _activeSpeechBubbles =
|
||||||
|
new Dictionary<EntityUid, List<SpeechBubble>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Speech bubbles that are to-be-sent because of the "rate limit" they have.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<EntityUid, SpeechBubbleQueueData> _queuedSpeechBubbles
|
||||||
|
= new Dictionary<EntityUid, SpeechBubbleQueueData>();
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_netManager.RegisterNetMessage<MsgChatMessage>(MsgChatMessage.NAME, _onChatMessage);
|
||||||
|
|
||||||
|
_speechBubbleRoot = new LayoutContainer
|
||||||
|
{
|
||||||
|
MouseFilter = Control.MouseFilterMode.Ignore
|
||||||
|
};
|
||||||
|
LayoutContainer.SetAnchorPreset(_speechBubbleRoot, LayoutContainer.LayoutPreset.Wide);
|
||||||
|
_userInterfaceManager.StateRoot.AddChild(_speechBubbleRoot);
|
||||||
|
_speechBubbleRoot.SetPositionFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FrameUpdate(FrameEventArgs delta)
|
||||||
|
{
|
||||||
|
// Update queued speech bubbles.
|
||||||
|
if (_queuedSpeechBubbles.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (entityUid, queueData) in _queuedSpeechBubbles.ShallowClone())
|
||||||
|
{
|
||||||
|
if (!_entityManager.TryGetEntity(entityUid, out var entity))
|
||||||
|
{
|
||||||
|
_queuedSpeechBubbles.Remove(entityUid);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
queueData.TimeLeft -= delta.DeltaSeconds;
|
||||||
|
if (queueData.TimeLeft > 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queueData.MessageQueue.Count == 0)
|
||||||
|
{
|
||||||
|
_queuedSpeechBubbles.Remove(entityUid);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg = queueData.MessageQueue.Dequeue();
|
||||||
|
|
||||||
|
queueData.TimeLeft += BubbleDelayBase + msg.Length * BubbleDelayFactor;
|
||||||
|
|
||||||
|
// We keep the queue around while it has 0 items. This allows us to keep the timer.
|
||||||
|
// When the timer hits 0 and there's no messages left, THEN we can clear it up.
|
||||||
|
CreateSpeechBubble(entity, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetChatBox(ChatBox chatBox)
|
||||||
|
{
|
||||||
|
if (_currentChatBox != null)
|
||||||
|
{
|
||||||
|
_currentChatBox.TextSubmitted -= _onChatBoxTextSubmitted;
|
||||||
|
_currentChatBox.FilterToggled -= _onFilterButtonToggled;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentChatBox = chatBox;
|
||||||
|
if (_currentChatBox != null)
|
||||||
|
{
|
||||||
|
_currentChatBox.TextSubmitted += _onChatBoxTextSubmitted;
|
||||||
|
_currentChatBox.FilterToggled += _onFilterButtonToggled;
|
||||||
|
}
|
||||||
|
|
||||||
|
RepopulateChat(filteredHistory);
|
||||||
|
_currentChatBox.AllButton.Pressed = !_allState;
|
||||||
|
_currentChatBox.LocalButton.Pressed = !_localState;
|
||||||
|
_currentChatBox.OOCButton.Pressed = !_oocState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveSpeechBubble(EntityUid entityUid, SpeechBubble bubble)
|
||||||
|
{
|
||||||
|
bubble.Dispose();
|
||||||
|
|
||||||
|
var list = _activeSpeechBubbles[entityUid];
|
||||||
|
list.Remove(bubble);
|
||||||
|
|
||||||
|
if (list.Count == 0)
|
||||||
|
{
|
||||||
|
_activeSpeechBubbles.Remove(entityUid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteChatMessage(StoredChatMessage message)
|
||||||
|
{
|
||||||
|
Logger.Debug($"{message.Channel}: {message.Message}");
|
||||||
|
|
||||||
|
if (IsFiltered(message.Channel))
|
||||||
|
{
|
||||||
|
Logger.Debug($"Message filtered: {message.Channel}: {message.Message}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var color = Color.DarkGray;
|
||||||
|
var messageText = message.Message;
|
||||||
|
if (!string.IsNullOrEmpty(message.MessageWrap))
|
||||||
|
{
|
||||||
|
messageText = string.Format(message.MessageWrap, messageText);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (message.Channel)
|
||||||
|
{
|
||||||
|
case ChatChannel.Server:
|
||||||
|
color = Color.Orange;
|
||||||
|
break;
|
||||||
|
case ChatChannel.OOC:
|
||||||
|
color = Color.LightSkyBlue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentChatBox?.AddLine(messageText, message.Channel, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _onChatBoxTextSubmitted(ChatBox chatBox, string text)
|
||||||
|
{
|
||||||
|
DebugTools.Assert(chatBox == _currentChatBox);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (text[0])
|
||||||
|
{
|
||||||
|
case ConCmdSlash:
|
||||||
|
{
|
||||||
|
// run locally
|
||||||
|
var conInput = text.Substring(1);
|
||||||
|
_console.ProcessCommand(conInput);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OOCAlias:
|
||||||
|
{
|
||||||
|
var conInput = text.Substring(1);
|
||||||
|
_console.ProcessCommand($"ooc \"{conInput}\"");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MeAlias:
|
||||||
|
{
|
||||||
|
var conInput = text.Substring(1);
|
||||||
|
_console.ProcessCommand($"me \"{conInput}\"");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
var conInput = _currentChatBox.DefaultChatFormat != null
|
||||||
|
? string.Format(_currentChatBox.DefaultChatFormat, text)
|
||||||
|
: text;
|
||||||
|
_console.ProcessCommand(conInput);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _onFilterButtonToggled(ChatBox chatBox, BaseButton.ButtonToggledEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.Button.Name)
|
||||||
|
{
|
||||||
|
case "Local":
|
||||||
|
_localState = !_localState;
|
||||||
|
if (_localState)
|
||||||
|
{
|
||||||
|
_filteredChannels |= ChatChannel.Local;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_filteredChannels &= ~ChatChannel.Local;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "OOC":
|
||||||
|
_oocState = !_oocState;
|
||||||
|
if (_oocState)
|
||||||
|
{
|
||||||
|
_filteredChannels |= ChatChannel.OOC;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_filteredChannels &= ~ChatChannel.OOC;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "ALL":
|
||||||
|
chatBox.LocalButton.Pressed ^= true;
|
||||||
|
chatBox.OOCButton.Pressed ^= true;
|
||||||
|
_allState = !_allState;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
RepopulateChat(filteredHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RepopulateChat(IEnumerable<StoredChatMessage> filteredMessages)
|
||||||
|
{
|
||||||
|
_currentChatBox.Contents.Clear();
|
||||||
|
|
||||||
|
foreach (var msg in filteredMessages)
|
||||||
|
{
|
||||||
|
WriteChatMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _onChatMessage(MsgChatMessage msg)
|
||||||
|
{
|
||||||
|
Logger.Debug($"{msg.Channel}: {msg.Message}");
|
||||||
|
|
||||||
|
// Log all incoming chat to repopulate when filter is un-toggled
|
||||||
|
var storedMessage = new StoredChatMessage(msg);
|
||||||
|
filteredHistory.Add(storedMessage);
|
||||||
|
WriteChatMessage(storedMessage);
|
||||||
|
|
||||||
|
// Local messages that have an entity attached get a speech bubble.
|
||||||
|
if (msg.Channel == ChatChannel.Local && msg.SenderEntity != default)
|
||||||
|
{
|
||||||
|
AddSpeechBubble(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddSpeechBubble(MsgChatMessage msg)
|
||||||
|
{
|
||||||
|
if (!_entityManager.TryGetEntity(msg.SenderEntity, out var entity))
|
||||||
|
{
|
||||||
|
Logger.WarningS("chat", "Got local chat message with invalid sender entity: {0}", msg.SenderEntity);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split message into words separated by spaces.
|
||||||
|
var words = msg.Message.Split(' ');
|
||||||
|
var messages = new List<string>();
|
||||||
|
var currentBuffer = new List<string>();
|
||||||
|
|
||||||
|
// Really shoddy way to approximate word length.
|
||||||
|
// Yes, I am aware of all the crimes here.
|
||||||
|
// TODO: Improve this to use actual glyph width etc..
|
||||||
|
var currentWordLength = 0;
|
||||||
|
foreach (var word in words)
|
||||||
|
{
|
||||||
|
// +1 for the space.
|
||||||
|
currentWordLength += word.Length + 1;
|
||||||
|
|
||||||
|
if (currentWordLength > SingleBubbleCharLimit)
|
||||||
|
{
|
||||||
|
// Too long for the current speech bubble, flush it.
|
||||||
|
messages.Add(string.Join(" ", currentBuffer));
|
||||||
|
currentBuffer.Clear();
|
||||||
|
|
||||||
|
currentWordLength = word.Length;
|
||||||
|
|
||||||
|
if (currentWordLength > SingleBubbleCharLimit)
|
||||||
|
{
|
||||||
|
// Word is STILL too long.
|
||||||
|
// Truncate it with an ellipse.
|
||||||
|
messages.Add($"{word.Substring(0, SingleBubbleCharLimit - 3)}...");
|
||||||
|
currentWordLength = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBuffer.Add(word);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentBuffer.Count != 0)
|
||||||
|
{
|
||||||
|
// Don't forget the last bubble.
|
||||||
|
messages.Add(string.Join(" ", currentBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var message in messages)
|
||||||
|
{
|
||||||
|
EnqueueSpeechBubble(entity, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnqueueSpeechBubble(IEntity entity, string contents)
|
||||||
|
{
|
||||||
|
if (!_queuedSpeechBubbles.TryGetValue(entity.Uid, out var queueData))
|
||||||
|
{
|
||||||
|
queueData = new SpeechBubbleQueueData();
|
||||||
|
_queuedSpeechBubbles.Add(entity.Uid, queueData);
|
||||||
|
}
|
||||||
|
|
||||||
|
queueData.MessageQueue.Enqueue(contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateSpeechBubble(IEntity entity, string contents)
|
||||||
|
{
|
||||||
|
var bubble = new SpeechBubble(contents, entity, _eyeManager, this);
|
||||||
|
|
||||||
|
if (_activeSpeechBubbles.TryGetValue(entity.Uid, out var existing))
|
||||||
|
{
|
||||||
|
// Push up existing bubbles above the mob's head.
|
||||||
|
foreach (var existingBubble in existing)
|
||||||
|
{
|
||||||
|
existingBubble.VerticalOffset += bubble.ContentHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existing = new List<SpeechBubble>();
|
||||||
|
_activeSpeechBubbles.Add(entity.Uid, existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
existing.Add(bubble);
|
||||||
|
_speechBubbleRoot.AddChild(bubble);
|
||||||
|
|
||||||
|
if (existing.Count > SpeechBubbleCap)
|
||||||
|
{
|
||||||
|
// Get the oldest to start fading fast.
|
||||||
|
var last = existing[0];
|
||||||
|
last.FadeNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsFiltered(ChatChannel channel)
|
||||||
|
{
|
||||||
|
// _allState works as inverter.
|
||||||
|
return _allState ^ _filteredChannels.HasFlag(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SpeechBubbleQueueData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Time left until the next speech bubble can appear.
|
||||||
|
/// </summary>
|
||||||
|
public float TimeLeft { get; set; }
|
||||||
|
|
||||||
|
public Queue<string> MessageQueue { get; } = new Queue<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
140
Content.Client/Chat/SpeechBubble.cs
Normal file
140
Content.Client/Chat/SpeechBubble.cs
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
using Content.Client.Interfaces.Chat;
|
||||||
|
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Timers;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Client.Chat
|
||||||
|
{
|
||||||
|
public class SpeechBubble : Control
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The total time a speech bubble stays on screen.
|
||||||
|
/// </summary>
|
||||||
|
private const float TotalTime = 4;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of time at the end of the bubble's life at which it starts fading.
|
||||||
|
/// </summary>
|
||||||
|
private const float FadeTime = 0.25f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The distance in world space to offset the speech bubble from the center of the entity.
|
||||||
|
/// i.e. greater -> higher above the mob's head.
|
||||||
|
/// </summary>
|
||||||
|
private const float EntityVerticalOffset = 0.5f;
|
||||||
|
|
||||||
|
private readonly IEyeManager _eyeManager;
|
||||||
|
private readonly IEntity _senderEntity;
|
||||||
|
private readonly IChatManager _chatManager;
|
||||||
|
|
||||||
|
private float _timeLeft = TotalTime;
|
||||||
|
|
||||||
|
public float VerticalOffset { get; set; }
|
||||||
|
private float _verticalOffsetAchieved;
|
||||||
|
|
||||||
|
public float ContentHeight { get; private set; }
|
||||||
|
|
||||||
|
public SpeechBubble(string text, IEntity senderEntity, IEyeManager eyeManager, IChatManager chatManager)
|
||||||
|
{
|
||||||
|
_chatManager = chatManager;
|
||||||
|
_senderEntity = senderEntity;
|
||||||
|
_eyeManager = eyeManager;
|
||||||
|
|
||||||
|
MouseFilter = MouseFilterMode.Ignore;
|
||||||
|
// Use text clipping so new messages don't overlap old ones being pushed up.
|
||||||
|
RectClipContent = true;
|
||||||
|
|
||||||
|
var label = new RichTextLabel
|
||||||
|
{
|
||||||
|
MaxWidth = 256,
|
||||||
|
MouseFilter = MouseFilterMode.Ignore
|
||||||
|
};
|
||||||
|
label.SetMessage(text);
|
||||||
|
|
||||||
|
var panel = new PanelContainer
|
||||||
|
{
|
||||||
|
StyleClasses = { "tooltipBox" },
|
||||||
|
Children = { label },
|
||||||
|
MouseFilter = MouseFilterMode.Ignore,
|
||||||
|
ModulateSelfOverride = Color.White.WithAlpha(0.75f)
|
||||||
|
};
|
||||||
|
|
||||||
|
AddChild(panel);
|
||||||
|
|
||||||
|
ForceRunStyleUpdate();
|
||||||
|
|
||||||
|
ContentHeight = panel.CombinedMinimumSize.Y;
|
||||||
|
_verticalOffsetAchieved = -ContentHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Vector2 CalculateMinimumSize()
|
||||||
|
{
|
||||||
|
return (base.CalculateMinimumSize().X, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
|
{
|
||||||
|
base.FrameUpdate(args);
|
||||||
|
|
||||||
|
_timeLeft -= args.DeltaSeconds;
|
||||||
|
|
||||||
|
if (_timeLeft <= FadeTime)
|
||||||
|
{
|
||||||
|
// Update alpha if we're fading.
|
||||||
|
Modulate = Color.White.WithAlpha(_timeLeft / FadeTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_senderEntity.Deleted || _timeLeft <= 0)
|
||||||
|
{
|
||||||
|
// Timer spawn to prevent concurrent modification exception.
|
||||||
|
Timer.Spawn(0, Die);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lerp to our new vertical offset if it's been modified.
|
||||||
|
if (FloatMath.CloseTo(_verticalOffsetAchieved - VerticalOffset, 0, 0.1))
|
||||||
|
{
|
||||||
|
_verticalOffsetAchieved = VerticalOffset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_verticalOffsetAchieved = FloatMath.Lerp(_verticalOffsetAchieved, VerticalOffset, 10 * args.DeltaSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
var worldPos = _senderEntity.Transform.WorldPosition;
|
||||||
|
worldPos += (0, EntityVerticalOffset);
|
||||||
|
|
||||||
|
var lowerCenter = _eyeManager.WorldToScreen(worldPos) / UIScale;
|
||||||
|
var screenPos = lowerCenter - (Width / 2, ContentHeight + _verticalOffsetAchieved);
|
||||||
|
LayoutContainer.SetPosition(this, screenPos);
|
||||||
|
|
||||||
|
var height = (lowerCenter.Y - screenPos.Y).Clamp(0, ContentHeight);
|
||||||
|
LayoutContainer.SetSize(this, (Size.X, height));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Die()
|
||||||
|
{
|
||||||
|
if (Disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_chatManager.RemoveSpeechBubble(_senderEntity.Uid, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Causes the speech bubble to start fading IMMEDIATELY.
|
||||||
|
/// </summary>
|
||||||
|
public void FadeNow()
|
||||||
|
{
|
||||||
|
if (_timeLeft > FadeTime)
|
||||||
|
{
|
||||||
|
_timeLeft = FadeTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
Content.Client/Chat/StoredChatMessage.cs
Normal file
38
Content.Client/Chat/StoredChatMessage.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using Content.Shared.Chat;
|
||||||
|
|
||||||
|
namespace Content.Client.Chat
|
||||||
|
{
|
||||||
|
public class StoredChatMessage
|
||||||
|
{
|
||||||
|
// TODO Make me reflected with respect to MsgChatMessage
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Client's own copies of chat messages used in filtering locally
|
||||||
|
/// </summary>
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actual Message contents, i.e. words
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Message channel, used for filtering
|
||||||
|
/// </summary>
|
||||||
|
public ChatChannel Channel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What to "wrap" the message contents with. Example is stuff like 'Joe says: "{0}"'
|
||||||
|
/// </summary>
|
||||||
|
public string MessageWrap { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor to copy a net message into stored client variety
|
||||||
|
/// </summary>
|
||||||
|
public StoredChatMessage(MsgChatMessage netMsg)
|
||||||
|
{
|
||||||
|
Message = netMsg.Message;
|
||||||
|
Channel = netMsg.Channel;
|
||||||
|
MessageWrap = netMsg.MessageWrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Content.Client/ClientContentIoC.cs
Normal file
32
Content.Client/ClientContentIoC.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Content.Client.Chat;
|
||||||
|
using Content.Client.GameTicking;
|
||||||
|
using Content.Client.Interfaces;
|
||||||
|
using Content.Client.Interfaces.Chat;
|
||||||
|
using Content.Client.Interfaces.Parallax;
|
||||||
|
using Content.Client.Parallax;
|
||||||
|
using Content.Client.Sandbox;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Client.Utility;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Client
|
||||||
|
{
|
||||||
|
internal static class ClientContentIoC
|
||||||
|
{
|
||||||
|
public static void Register()
|
||||||
|
{
|
||||||
|
IoCManager.Register<IGameHud, GameHud>();
|
||||||
|
IoCManager.Register<IClientNotifyManager, ClientNotifyManager>();
|
||||||
|
IoCManager.Register<ISharedNotifyManager, ClientNotifyManager>();
|
||||||
|
IoCManager.Register<IClientGameTicker, ClientGameTicker>();
|
||||||
|
IoCManager.Register<IParallaxManager, ParallaxManager>();
|
||||||
|
IoCManager.Register<IChatManager, ChatManager>();
|
||||||
|
IoCManager.Register<IEscapeMenuOwner, EscapeMenuOwner>();
|
||||||
|
IoCManager.Register<ISandboxManager, SandboxManager>();
|
||||||
|
IoCManager.Register<IModuleManager, ClientModuleManager>();
|
||||||
|
IoCManager.Register<IClientPreferencesManager, ClientPreferencesManager>();
|
||||||
|
IoCManager.Register<IItemSlotManager, ItemSlotManager>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Content.Client/ClientModuleTestingCallbacks.cs
Normal file
10
Content.Client/ClientModuleTestingCallbacks.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Shared;
|
||||||
|
|
||||||
|
namespace Content.Client
|
||||||
|
{
|
||||||
|
public sealed class ClientModuleTestingCallbacks : SharedModuleTestingCallbacks
|
||||||
|
{
|
||||||
|
public Action ClientBeforeIoC { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
123
Content.Client/ClientNotifyManager.cs
Normal file
123
Content.Client/ClientNotifyManager.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Client.Interfaces;
|
||||||
|
using Content.Shared;
|
||||||
|
using Robust.Client.Interfaces.Console;
|
||||||
|
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||||
|
using Robust.Client.Interfaces.Input;
|
||||||
|
using Robust.Client.Interfaces.UserInterface;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client
|
||||||
|
{
|
||||||
|
public class ClientNotifyManager : SharedNotifyManager, IClientNotifyManager
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private IPlayerManager _playerManager;
|
||||||
|
[Dependency] private IUserInterfaceManager _userInterfaceManager;
|
||||||
|
[Dependency] private IInputManager _inputManager;
|
||||||
|
[Dependency] private IEyeManager _eyeManager;
|
||||||
|
[Dependency] private IClientNetManager _netManager;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private readonly List<PopupLabel> _aliveLabels = new List<PopupLabel>();
|
||||||
|
private bool _initialized;
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
DebugTools.Assert(!_initialized);
|
||||||
|
|
||||||
|
_netManager.RegisterNetMessage<MsgDoNotify>(nameof(MsgDoNotify), DoNotifyMessage);
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoNotifyMessage(MsgDoNotify message)
|
||||||
|
{
|
||||||
|
PopupMessage(_eyeManager.WorldToScreen(message.Coordinates), message.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PopupMessage(GridCoordinates coordinates, IEntity viewer, string message)
|
||||||
|
{
|
||||||
|
if (viewer != _playerManager.LocalPlayer.ControlledEntity)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PopupMessage(_eyeManager.WorldToScreen(coordinates), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopupMessage(ScreenCoordinates coordinates, string message)
|
||||||
|
{
|
||||||
|
var label = new PopupLabel {Text = message};
|
||||||
|
var minimumSize = label.CombinedMinimumSize;
|
||||||
|
LayoutContainer.SetPosition(label, label.InitialPos = coordinates.Position - minimumSize / 2);
|
||||||
|
_userInterfaceManager.PopupRoot.AddChild(label);
|
||||||
|
_aliveLabels.Add(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopupMessage(string message)
|
||||||
|
{
|
||||||
|
PopupMessage(new ScreenCoordinates(_inputManager.MouseScreenPosition), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FrameUpdate(FrameEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
_aliveLabels.ForEach(l =>
|
||||||
|
{
|
||||||
|
if (l.TimeLeft > 3f)
|
||||||
|
{
|
||||||
|
l.Dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_aliveLabels.RemoveAll(l => l.Disposed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PopupLabel : Label
|
||||||
|
{
|
||||||
|
public float TimeLeft { get; private set; }
|
||||||
|
public Vector2 InitialPos { get; set; }
|
||||||
|
|
||||||
|
public PopupLabel()
|
||||||
|
{
|
||||||
|
ShadowOffsetXOverride = 1;
|
||||||
|
ShadowOffsetYOverride = 1;
|
||||||
|
FontColorShadowOverride = Color.Black;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update(FrameEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
TimeLeft += eventArgs.DeltaSeconds;
|
||||||
|
LayoutContainer.SetPosition(this, InitialPos - (0, 20 * (TimeLeft * TimeLeft + TimeLeft)));
|
||||||
|
if (TimeLeft > 0.5f)
|
||||||
|
{
|
||||||
|
Modulate = Color.White.WithAlpha(1f - 0.2f * (float)Math.Pow(TimeLeft - 0.5f, 3f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PopupMessageCommand : IConsoleCommand
|
||||||
|
{
|
||||||
|
public string Command => "popupmsg";
|
||||||
|
public string Description => "";
|
||||||
|
public string Help => "";
|
||||||
|
|
||||||
|
public bool Execute(IDebugConsole console, params string[] args)
|
||||||
|
{
|
||||||
|
var arg = args[0];
|
||||||
|
var mgr = IoCManager.Resolve<IClientNotifyManager>();
|
||||||
|
mgr.PopupMessage(arg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
Content.Client/ClientPreferencesManager.cs
Normal file
74
Content.Client/ClientPreferencesManager.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Client.Interfaces;
|
||||||
|
using Content.Shared.Preferences;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Client
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Receives <see cref="PlayerPreferences" /> and <see cref="GameSettings" /> from the server during the initial
|
||||||
|
/// connection.
|
||||||
|
/// Stores preferences on the server through <see cref="SelectCharacter" /> and <see cref="UpdateCharacter" />.
|
||||||
|
/// </summary>
|
||||||
|
public class ClientPreferencesManager : SharedPreferencesManager, IClientPreferencesManager
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IClientNetManager _netManager;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
public GameSettings Settings { get; private set; }
|
||||||
|
public PlayerPreferences Preferences { get; private set; }
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_netManager.RegisterNetMessage<MsgPreferencesAndSettings>(nameof(MsgPreferencesAndSettings),
|
||||||
|
HandlePreferencesAndSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectCharacter(ICharacterProfile profile)
|
||||||
|
{
|
||||||
|
SelectCharacter(Preferences.IndexOfCharacter(profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectCharacter(int slot)
|
||||||
|
{
|
||||||
|
Preferences = new PlayerPreferences(Preferences.Characters, slot);
|
||||||
|
var msg = _netManager.CreateNetMessage<MsgSelectCharacter>();
|
||||||
|
msg.SelectedCharacterIndex = slot;
|
||||||
|
_netManager.ClientSendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateCharacter(ICharacterProfile profile, int slot)
|
||||||
|
{
|
||||||
|
var characters = Preferences.Characters.ToArray();
|
||||||
|
characters[slot] = profile;
|
||||||
|
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex);
|
||||||
|
var msg = _netManager.CreateNetMessage<MsgUpdateCharacter>();
|
||||||
|
msg.Profile = profile;
|
||||||
|
msg.Slot = slot;
|
||||||
|
_netManager.ClientSendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateCharacter(ICharacterProfile profile)
|
||||||
|
{
|
||||||
|
UpdateCharacter(profile, Preferences.FirstEmptySlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteCharacter(ICharacterProfile profile)
|
||||||
|
{
|
||||||
|
DeleteCharacter(Preferences.IndexOfCharacter(profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteCharacter(int slot)
|
||||||
|
{
|
||||||
|
UpdateCharacter(null, slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePreferencesAndSettings(MsgPreferencesAndSettings message)
|
||||||
|
{
|
||||||
|
Preferences = message.Preferences;
|
||||||
|
Settings = message.Settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
Content.Client/Commands/DebugCommands.cs
Normal file
57
Content.Client/Commands/DebugCommands.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using Content.Client.Interfaces;
|
||||||
|
using Content.Shared.GameObjects.Components.Markers;
|
||||||
|
using Robust.Client.Interfaces.Console;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Client.Commands
|
||||||
|
{
|
||||||
|
internal sealed class ShowMarkersCommand : IConsoleCommand
|
||||||
|
{
|
||||||
|
// ReSharper disable once StringLiteralTypo
|
||||||
|
public string Command => "togglemarkers";
|
||||||
|
public string Description => "Toggles visibility of markers such as spawn points.";
|
||||||
|
public string Help => "";
|
||||||
|
|
||||||
|
public bool Execute(IDebugConsole console, params string[] args)
|
||||||
|
{
|
||||||
|
bool? whichToSet = null;
|
||||||
|
foreach (var entity in IoCManager.Resolve<IEntityManager>()
|
||||||
|
.GetEntities(new TypeEntityQuery(typeof(SharedSpawnPointComponent))))
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out ISpriteComponent sprite))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!whichToSet.HasValue)
|
||||||
|
{
|
||||||
|
whichToSet = !sprite.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprite.Visible = whichToSet.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class NotifyCommand : IConsoleCommand
|
||||||
|
{
|
||||||
|
public string Command => "notify";
|
||||||
|
public string Description => "Send a notify client side.";
|
||||||
|
public string Help => "notify <message>";
|
||||||
|
|
||||||
|
public bool Execute(IDebugConsole console, params string[] args)
|
||||||
|
{
|
||||||
|
var message = args[0];
|
||||||
|
|
||||||
|
var notifyManager = IoCManager.Resolve<IClientNotifyManager>();
|
||||||
|
notifyManager.PopupMessage(message);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
393
Content.Client/Construction/ConstructionMenu.cs
Normal file
393
Content.Client/Construction/ConstructionMenu.cs
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Client.GameObjects.Components.Construction;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.Interfaces.Placement;
|
||||||
|
using Robust.Client.Interfaces.ResourceManagement;
|
||||||
|
using Robust.Client.Placement;
|
||||||
|
using Robust.Client.ResourceManagement;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Client.Utility;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client.Construction
|
||||||
|
{
|
||||||
|
public class ConstructionMenu : SS14Window
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0649
|
||||||
|
[Dependency] readonly IPrototypeManager PrototypeManager;
|
||||||
|
[Dependency] readonly IResourceCache ResourceCache;
|
||||||
|
#pragma warning restore
|
||||||
|
|
||||||
|
public ConstructorComponent Owner { get; set; }
|
||||||
|
private readonly Button BuildButton;
|
||||||
|
private readonly Button EraseButton;
|
||||||
|
private readonly LineEdit SearchBar;
|
||||||
|
private readonly Tree RecipeList;
|
||||||
|
private readonly TextureRect InfoIcon;
|
||||||
|
private readonly Label InfoLabel;
|
||||||
|
private readonly ItemList StepList;
|
||||||
|
|
||||||
|
private CategoryNode RootCategory;
|
||||||
|
|
||||||
|
// This list is flattened in such a way that the top most deepest category is first.
|
||||||
|
private List<CategoryNode> FlattenedCategories;
|
||||||
|
private readonly PlacementManager Placement;
|
||||||
|
|
||||||
|
protected override Vector2? CustomSize => (500, 350);
|
||||||
|
|
||||||
|
public ConstructionMenu()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
Placement = (PlacementManager) IoCManager.Resolve<IPlacementManager>();
|
||||||
|
Placement.PlacementCanceled += OnPlacementCanceled;
|
||||||
|
|
||||||
|
Title = "Construction";
|
||||||
|
|
||||||
|
var hSplitContainer = new HSplitContainer();
|
||||||
|
|
||||||
|
// Left side
|
||||||
|
var recipes = new VBoxContainer {CustomMinimumSize = new Vector2(150.0f, 0.0f)};
|
||||||
|
SearchBar = new LineEdit {PlaceHolder = "Search"};
|
||||||
|
RecipeList = new Tree {SizeFlagsVertical = SizeFlags.FillExpand, HideRoot = true};
|
||||||
|
recipes.AddChild(SearchBar);
|
||||||
|
recipes.AddChild(RecipeList);
|
||||||
|
hSplitContainer.AddChild(recipes);
|
||||||
|
|
||||||
|
// Right side
|
||||||
|
var guide = new VBoxContainer();
|
||||||
|
var info = new HBoxContainer();
|
||||||
|
InfoIcon = new TextureRect();
|
||||||
|
InfoLabel = new Label
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsVertical = SizeFlags.ShrinkCenter
|
||||||
|
};
|
||||||
|
info.AddChild(InfoIcon);
|
||||||
|
info.AddChild(InfoLabel);
|
||||||
|
guide.AddChild(info);
|
||||||
|
|
||||||
|
var stepsLabel = new Label
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
|
||||||
|
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||||
|
Text = "Steps"
|
||||||
|
};
|
||||||
|
guide.AddChild(stepsLabel);
|
||||||
|
|
||||||
|
StepList = new ItemList
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand, SelectMode = ItemList.ItemListSelectMode.None
|
||||||
|
};
|
||||||
|
guide.AddChild(StepList);
|
||||||
|
|
||||||
|
var buttonsContainer = new HBoxContainer();
|
||||||
|
BuildButton = new Button
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
TextAlign = Button.AlignMode.Center,
|
||||||
|
Text = "Build!",
|
||||||
|
Disabled = true,
|
||||||
|
ToggleMode = false
|
||||||
|
};
|
||||||
|
EraseButton = new Button
|
||||||
|
{
|
||||||
|
TextAlign = Button.AlignMode.Center, Text = "Clear Ghosts", ToggleMode = true
|
||||||
|
};
|
||||||
|
buttonsContainer.AddChild(BuildButton);
|
||||||
|
buttonsContainer.AddChild(EraseButton);
|
||||||
|
guide.AddChild(buttonsContainer);
|
||||||
|
|
||||||
|
hSplitContainer.AddChild(guide);
|
||||||
|
Contents.AddChild(hSplitContainer);
|
||||||
|
|
||||||
|
BuildButton.OnPressed += OnBuildPressed;
|
||||||
|
EraseButton.OnToggled += OnEraseToggled;
|
||||||
|
SearchBar.OnTextChanged += OnTextEntered;
|
||||||
|
RecipeList.OnItemSelected += OnItemSelected;
|
||||||
|
|
||||||
|
PopulatePrototypeList();
|
||||||
|
PopulateTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
Placement.PlacementCanceled -= OnPlacementCanceled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnItemSelected()
|
||||||
|
{
|
||||||
|
var prototype = (ConstructionPrototype) RecipeList.Selected.Metadata;
|
||||||
|
|
||||||
|
if (prototype == null)
|
||||||
|
{
|
||||||
|
InfoLabel.Text = "";
|
||||||
|
InfoIcon.Texture = null;
|
||||||
|
StepList.Clear();
|
||||||
|
BuildButton.Disabled = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BuildButton.Disabled = false;
|
||||||
|
InfoLabel.Text = prototype.Description;
|
||||||
|
InfoIcon.Texture = prototype.Icon.Frame0();
|
||||||
|
|
||||||
|
StepList.Clear();
|
||||||
|
|
||||||
|
foreach (var forward in prototype.Stages.Select(a => a.Forward))
|
||||||
|
{
|
||||||
|
if (forward == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture icon;
|
||||||
|
string text;
|
||||||
|
switch (forward)
|
||||||
|
{
|
||||||
|
case ConstructionStepMaterial mat:
|
||||||
|
switch (mat.Material)
|
||||||
|
{
|
||||||
|
case ConstructionStepMaterial.MaterialType.Metal:
|
||||||
|
icon = ResourceCache.GetResource<TextureResource>(
|
||||||
|
"/Textures/Objects/Materials/sheet_metal.png");
|
||||||
|
text = $"Metal x{mat.Amount}";
|
||||||
|
break;
|
||||||
|
case ConstructionStepMaterial.MaterialType.Glass:
|
||||||
|
icon = ResourceCache.GetResource<TextureResource>(
|
||||||
|
"/Textures/Objects/Materials/sheet_glass.png");
|
||||||
|
text = $"Glass x{mat.Amount}";
|
||||||
|
break;
|
||||||
|
case ConstructionStepMaterial.MaterialType.Cable:
|
||||||
|
icon = ResourceCache.GetResource<TextureResource>(
|
||||||
|
"/Textures/Objects/Tools/cable_coil.png");
|
||||||
|
text = $"Cable Coil x{mat.Amount}";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ConstructionStepTool tool:
|
||||||
|
switch (tool.Tool)
|
||||||
|
{
|
||||||
|
case ConstructionStepTool.ToolType.Wrench:
|
||||||
|
icon = ResourceCache.GetResource<TextureResource>("/Textures/Objects/Tools/wrench.png");
|
||||||
|
text = "Wrench";
|
||||||
|
break;
|
||||||
|
case ConstructionStepTool.ToolType.Crowbar:
|
||||||
|
icon = ResourceCache.GetResource<TextureResource>("/Textures/Objects/Tools/crowbar.png");
|
||||||
|
text = "Crowbar";
|
||||||
|
break;
|
||||||
|
case ConstructionStepTool.ToolType.Screwdriver:
|
||||||
|
icon = ResourceCache.GetResource<TextureResource>(
|
||||||
|
"/Textures/Objects/Tools/screwdriver.png");
|
||||||
|
text = "Screwdriver";
|
||||||
|
break;
|
||||||
|
case ConstructionStepTool.ToolType.Welder:
|
||||||
|
icon = ResourceCache.GetResource<RSIResource>("/Textures/Objects/tools.rsi")
|
||||||
|
.RSI["welder"].Frame0;
|
||||||
|
text = $"Welding tool ({tool.Amount} fuel)";
|
||||||
|
break;
|
||||||
|
case ConstructionStepTool.ToolType.Wirecutters:
|
||||||
|
icon = ResourceCache.GetResource<TextureResource>(
|
||||||
|
"/Textures/Objects/Tools/wirecutter.png");
|
||||||
|
text = "Wirecutters";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
StepList.AddItem(text, icon, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnTextEntered(LineEdit.LineEditEventArgs args)
|
||||||
|
{
|
||||||
|
var str = args.Text;
|
||||||
|
PopulateTree(string.IsNullOrWhiteSpace(str) ? null : str.ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnBuildPressed(BaseButton.ButtonEventArgs args)
|
||||||
|
{
|
||||||
|
var prototype = (ConstructionPrototype) RecipeList.Selected.Metadata;
|
||||||
|
if (prototype == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prototype.Type != ConstructionType.Structure)
|
||||||
|
{
|
||||||
|
// In-hand attackby doesn't exist so this is the best alternative.
|
||||||
|
var loc = Owner.Owner.GetComponent<ITransformComponent>().GridPosition;
|
||||||
|
Owner.SpawnGhost(prototype, loc, Direction.North);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hijack = new ConstructionPlacementHijack(prototype, Owner);
|
||||||
|
var info = new PlacementInformation
|
||||||
|
{
|
||||||
|
IsTile = false,
|
||||||
|
PlacementOption = prototype.PlacementMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Placement.BeginHijackedPlacing(info, hijack);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEraseToggled(BaseButton.ButtonToggledEventArgs args)
|
||||||
|
{
|
||||||
|
var hijack = new ConstructionPlacementHijack(null, Owner);
|
||||||
|
Placement.ToggleEraserHijacked(hijack);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopulatePrototypeList()
|
||||||
|
{
|
||||||
|
RootCategory = new CategoryNode("", null);
|
||||||
|
int count = 1;
|
||||||
|
|
||||||
|
foreach (var prototype in PrototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||||
|
{
|
||||||
|
var currentNode = RootCategory;
|
||||||
|
|
||||||
|
foreach (var category in prototype.CategorySegments)
|
||||||
|
{
|
||||||
|
if (!currentNode.ChildCategories.TryGetValue(category, out var subNode))
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
subNode = new CategoryNode(category, currentNode);
|
||||||
|
currentNode.ChildCategories.Add(category, subNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentNode = subNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentNode.Prototypes.Add(prototype);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a pass to sort the prototype lists and flatten the hierarchy.
|
||||||
|
void Recurse(CategoryNode node)
|
||||||
|
{
|
||||||
|
// I give up we're using recursion to flatten this.
|
||||||
|
// There probably IS a way to do it.
|
||||||
|
// I'm too stupid to think of what that way is.
|
||||||
|
foreach (var child in node.ChildCategories.Values)
|
||||||
|
{
|
||||||
|
Recurse(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Prototypes.Sort(ComparePrototype);
|
||||||
|
FlattenedCategories.Add(node);
|
||||||
|
node.FlattenedIndex = FlattenedCategories.Count - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FlattenedCategories = new List<CategoryNode>(count);
|
||||||
|
Recurse(RootCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopulateTree(string searchTerm = null)
|
||||||
|
{
|
||||||
|
RecipeList.Clear();
|
||||||
|
|
||||||
|
var categoryItems = new Tree.Item[FlattenedCategories.Count];
|
||||||
|
categoryItems[RootCategory.FlattenedIndex] = RecipeList.CreateItem();
|
||||||
|
|
||||||
|
// Yay more recursion.
|
||||||
|
Tree.Item ItemForNode(CategoryNode node)
|
||||||
|
{
|
||||||
|
if (categoryItems[node.FlattenedIndex] != null)
|
||||||
|
{
|
||||||
|
return categoryItems[node.FlattenedIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = RecipeList.CreateItem(ItemForNode(node.Parent));
|
||||||
|
item.Text = node.Name;
|
||||||
|
item.Selectable = false;
|
||||||
|
categoryItems[node.FlattenedIndex] = item;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var node in FlattenedCategories)
|
||||||
|
{
|
||||||
|
foreach (var prototype in node.Prototypes)
|
||||||
|
{
|
||||||
|
if (searchTerm != null)
|
||||||
|
{
|
||||||
|
var found = false;
|
||||||
|
// TODO: don't run ToLowerInvariant() constantly.
|
||||||
|
if (prototype.Name.ToLowerInvariant().IndexOf(searchTerm, StringComparison.Ordinal) != -1)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var keyw in prototype.Keywords.Concat(prototype.CategorySegments))
|
||||||
|
{
|
||||||
|
// TODO: don't run ToLowerInvariant() constantly.
|
||||||
|
if (keyw.ToLowerInvariant().IndexOf(searchTerm, StringComparison.Ordinal) != -1)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var subItem = RecipeList.CreateItem(ItemForNode(node));
|
||||||
|
subItem.Text = prototype.Name;
|
||||||
|
subItem.Metadata = prototype;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlacementCanceled(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
EraseButton.Pressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ComparePrototype(ConstructionPrototype x, ConstructionPrototype y)
|
||||||
|
{
|
||||||
|
return String.Compare(x.Name, y.Name, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
class CategoryNode
|
||||||
|
{
|
||||||
|
public readonly string Name;
|
||||||
|
public readonly CategoryNode Parent;
|
||||||
|
|
||||||
|
public SortedDictionary<string, CategoryNode>
|
||||||
|
ChildCategories = new SortedDictionary<string, CategoryNode>();
|
||||||
|
|
||||||
|
public List<ConstructionPrototype> Prototypes = new List<ConstructionPrototype>();
|
||||||
|
public int FlattenedIndex = -1;
|
||||||
|
|
||||||
|
public CategoryNode(string name, CategoryNode parent)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Parent = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Content.Client/Construction/ConstructionPlacementHijack.cs
Normal file
49
Content.Client/Construction/ConstructionPlacementHijack.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Content.Client.GameObjects.Components.Construction;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using Robust.Client.Interfaces.ResourceManagement;
|
||||||
|
using Robust.Client.Placement;
|
||||||
|
using Robust.Client.Utility;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Client.Construction
|
||||||
|
{
|
||||||
|
public class ConstructionPlacementHijack : PlacementHijack
|
||||||
|
{
|
||||||
|
private readonly ConstructionPrototype Prototype;
|
||||||
|
private readonly ConstructorComponent Owner;
|
||||||
|
|
||||||
|
public ConstructionPlacementHijack(ConstructionPrototype prototype, ConstructorComponent owner)
|
||||||
|
{
|
||||||
|
Prototype = prototype;
|
||||||
|
Owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool HijackPlacementRequest(GridCoordinates coords)
|
||||||
|
{
|
||||||
|
if (Prototype != null)
|
||||||
|
{
|
||||||
|
var dir = Manager.Direction;
|
||||||
|
Owner.SpawnGhost(Prototype, coords, dir);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool HijackDeletion(IEntity entity)
|
||||||
|
{
|
||||||
|
if (entity.TryGetComponent(out ConstructionGhostComponent ghost))
|
||||||
|
{
|
||||||
|
Owner.ClearGhost(ghost.GhostID);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void StartHijack(PlacementManager manager)
|
||||||
|
{
|
||||||
|
base.StartHijack(manager);
|
||||||
|
|
||||||
|
manager.CurrentBaseSprite = Prototype.Icon.DirFrame0();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,87 +1,31 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||||
<ProjectGuid>{A2E5F175-78AF-4DDD-8F97-E2D2552372ED}</ProjectGuid>
|
<LangVersion>8</LangVersion>
|
||||||
<OutputType>Library</OutputType>
|
<IsPackable>false</IsPackable>
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
<Platforms>x64</Platforms>
|
||||||
<RootNamespace>Content.Client</RootNamespace>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
<AssemblyName>Content.Client</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
|
||||||
<FileAlignment>512</FileAlignment>
|
|
||||||
<ContentAssemblyTarget>..\bin\Client\Assemblies\</ContentAssemblyTarget>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<!--
|
|
||||||
This copies all dependencies,
|
|
||||||
but on the plus side it's automatically located in the right place.
|
|
||||||
-->
|
|
||||||
<OutputPath>..\bin\Content.Client\</OutputPath>
|
<OutputPath>..\bin\Content.Client\</OutputPath>
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
|
||||||
<!--
|
|
||||||
This copies all dependencies,
|
|
||||||
but on the plus side it's automatically located in the right place.
|
|
||||||
-->
|
|
||||||
<OutputPath>..\bin\Content.Client\</OutputPath>
|
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<Import Project="..\RobustToolbox\MSBuild\Robust.DefineConstants.targets" />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="System" />
|
<PackageReference Include="Nett" Version="0.13.0" />
|
||||||
<Reference Include="System.Core" />
|
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0007" />
|
||||||
<Reference Include="System.Xml.Linq" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0006" />
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<PackageReference Include="YamlDotNet" Version="8.1.0" />
|
||||||
<Reference Include="System.Data" />
|
<PackageReference Include="SharpZipLib" Version="1.2.0" />
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="EntryPoint.cs" />
|
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
||||||
</ItemGroup>
|
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
|
||||||
<ItemGroup>
|
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
|
||||||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj">
|
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||||
<Project>{26aeebb3-dde7-443a-9f43-7bc7f4acf6b5}</Project>
|
|
||||||
<Name>Content.Shared</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\engine\Lidgren.Network\Lidgren.Network.csproj">
|
|
||||||
<Project>{59250baf-0000-0000-0000-000000000000}</Project>
|
|
||||||
<Name>Lidgren.Network</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\engine\SS14.Client.Graphics\SS14.Client.Graphics.csproj">
|
|
||||||
<Project>{302b877e-0000-0000-0000-000000000000}</Project>
|
|
||||||
<Name>SS14.Client.Graphics</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\engine\SS14.Client\SS14.Client.csproj">
|
|
||||||
<Project>{0c31dfdf-0000-0000-0000-000000000000}</Project>
|
|
||||||
<Name>SS14.Client</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\engine\SS14.Shared\SS14.Shared.csproj">
|
|
||||||
<Project>{0529f740-0000-0000-0000-000000000000}</Project>
|
|
||||||
<Name>SS14.Shared</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
<Import Project="..\SS14.Content.targets" />
|
|
||||||
<Target Name="AfterBuild" DependsOnTargets="CopyContentAssemblies" />
|
|
||||||
<ItemGroup>
|
|
||||||
<!-- Files to be copied into Client/Assemblies -->
|
|
||||||
<ContentAssemblies Include="$(OutputPath)Content.Client.dll" />
|
|
||||||
<ContentAssemblies Include="$(OutputPath)Content.Shared.dll" />
|
|
||||||
<ContentAssemblies Include="$(OutputPath)Content.Client.pdb" Condition="'$(Configuration)' == 'Debug'" />
|
|
||||||
<ContentAssemblies Include="$(OutputPath)Content.Shared.pdb" Condition="'$(Configuration)' == 'Debug'" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<Import Project="..\RobustToolbox\MSBuild\Robust.Engine.targets" />
|
||||||
|
<Target Name="ContentAfterBuild" DependsOnTargets="ClientAfterBuild" AfterTargets="Build" />
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,12 +1,246 @@
|
|||||||
using SS14.Shared.ContentPack;
|
using System;
|
||||||
|
using Content.Client.GameObjects.Components.Actor;
|
||||||
|
using Content.Client.Input;
|
||||||
|
using Content.Client.Interfaces;
|
||||||
|
using Content.Client.Interfaces.Chat;
|
||||||
|
using Content.Client.Interfaces.Parallax;
|
||||||
|
using Content.Client.Parallax;
|
||||||
|
using Content.Client.Sandbox;
|
||||||
|
using Content.Client.State;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Shared.GameObjects.Components;
|
||||||
|
using Content.Shared.GameObjects.Components.Cargo;
|
||||||
|
using Content.Shared.GameObjects.Components.Chemistry;
|
||||||
|
using Content.Shared.GameObjects.Components.Markers;
|
||||||
|
using Content.Shared.GameObjects.Components.Research;
|
||||||
|
using Content.Shared.GameObjects.Components.VendingMachines;
|
||||||
|
using Robust.Client.Interfaces;
|
||||||
|
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||||
|
using Robust.Client.Interfaces.Input;
|
||||||
|
using Robust.Client.Interfaces.State;
|
||||||
|
using Robust.Client.Interfaces.UserInterface;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
using Robust.Shared.ContentPack;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Client
|
namespace Content.Client
|
||||||
{
|
{
|
||||||
public class EntryPoint : GameClient
|
public class EntryPoint : GameClient
|
||||||
{
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager;
|
||||||
|
[Dependency] private readonly IBaseClient _baseClient;
|
||||||
|
[Dependency] private readonly IStateManager _stateManager;
|
||||||
|
[Dependency] private readonly IEscapeMenuOwner _escapeMenuOwner;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
public override void Init()
|
public override void Init()
|
||||||
{
|
{
|
||||||
// TODO: Anything at all.
|
var factory = IoCManager.Resolve<IComponentFactory>();
|
||||||
|
var prototypes = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
|
||||||
|
factory.DoAutoRegistrations();
|
||||||
|
|
||||||
|
var registerIgnore = new[]
|
||||||
|
{
|
||||||
|
"Wrenchable",
|
||||||
|
"AmmoBox",
|
||||||
|
"Breakable",
|
||||||
|
"Pickaxe",
|
||||||
|
"Interactable",
|
||||||
|
"Destructible",
|
||||||
|
"Temperature",
|
||||||
|
"PowerTransfer",
|
||||||
|
"PowerNode",
|
||||||
|
"PowerProvider",
|
||||||
|
"PowerDevice",
|
||||||
|
"PowerStorage",
|
||||||
|
"PowerGenerator",
|
||||||
|
"Explosive",
|
||||||
|
"OnUseTimerTrigger",
|
||||||
|
"ToolboxElectricalFill",
|
||||||
|
"ToolLockerFill",
|
||||||
|
"EmitSoundOnUse",
|
||||||
|
"FootstepModifier",
|
||||||
|
"HeatResistance",
|
||||||
|
"Teleportable",
|
||||||
|
"ItemTeleporter",
|
||||||
|
"Portal",
|
||||||
|
"EntityStorage",
|
||||||
|
"PlaceableSurface",
|
||||||
|
"Wirecutter",
|
||||||
|
"Screwdriver",
|
||||||
|
"Multitool",
|
||||||
|
"Wrench",
|
||||||
|
"Crowbar",
|
||||||
|
"HitscanWeapon",
|
||||||
|
"ProjectileWeapon",
|
||||||
|
"Projectile",
|
||||||
|
"MeleeWeapon",
|
||||||
|
"Storeable",
|
||||||
|
"Dice",
|
||||||
|
"Construction",
|
||||||
|
"Apc",
|
||||||
|
"Door",
|
||||||
|
"PoweredLight",
|
||||||
|
"Smes",
|
||||||
|
"Powercell",
|
||||||
|
"LightBulb",
|
||||||
|
"Healing",
|
||||||
|
"Catwalk",
|
||||||
|
"BallisticMagazine",
|
||||||
|
"BallisticBullet",
|
||||||
|
"HitscanWeaponCapacitor",
|
||||||
|
"PowerCell",
|
||||||
|
"WeaponCapacitorCharger",
|
||||||
|
"PowerCellCharger",
|
||||||
|
"AiController",
|
||||||
|
"PlayerInputMover",
|
||||||
|
"Computer",
|
||||||
|
"AsteroidRock",
|
||||||
|
"ResearchServer",
|
||||||
|
"ResearchPointSource",
|
||||||
|
"ResearchClient",
|
||||||
|
"IdCard",
|
||||||
|
"Access",
|
||||||
|
"AccessReader",
|
||||||
|
"IdCardConsole",
|
||||||
|
"Airlock",
|
||||||
|
"MedicalScanner",
|
||||||
|
"WirePlacer",
|
||||||
|
"Species",
|
||||||
|
"Drink",
|
||||||
|
"Food",
|
||||||
|
"DrinkFoodContainer",
|
||||||
|
"Stomach",
|
||||||
|
"Hunger",
|
||||||
|
"Thirst",
|
||||||
|
"Rotatable",
|
||||||
|
"MagicMirror",
|
||||||
|
"MedkitFill",
|
||||||
|
"FloorTile",
|
||||||
|
"FootstepSound",
|
||||||
|
"UtilityBeltClothingFill",
|
||||||
|
"ShuttleController",
|
||||||
|
"HumanInventoryController",
|
||||||
|
"UseDelay",
|
||||||
|
"Pourable"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var ignoreName in registerIgnore)
|
||||||
|
{
|
||||||
|
factory.RegisterIgnore(ignoreName);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory.Register<SharedResearchConsoleComponent>();
|
||||||
|
factory.Register<SharedLatheComponent>();
|
||||||
|
factory.Register<SharedSpawnPointComponent>();
|
||||||
|
|
||||||
|
factory.Register<SolutionComponent>();
|
||||||
|
|
||||||
|
factory.Register<SharedVendingMachineComponent>();
|
||||||
|
factory.Register<SharedWiresComponent>();
|
||||||
|
factory.Register<SharedCargoConsoleComponent>();
|
||||||
|
factory.Register<SharedReagentDispenserComponent>();
|
||||||
|
|
||||||
|
prototypes.RegisterIgnore("material");
|
||||||
|
prototypes.RegisterIgnore("reaction"); //Chemical reactions only needed by server. Reactions checks are server-side.
|
||||||
|
|
||||||
|
ClientContentIoC.Register();
|
||||||
|
|
||||||
|
if (TestingCallbacks != null)
|
||||||
|
{
|
||||||
|
var cast = (ClientModuleTestingCallbacks) TestingCallbacks;
|
||||||
|
cast.ClientBeforeIoC?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
IoCManager.BuildGraph();
|
||||||
|
|
||||||
|
IoCManager.Resolve<IParallaxManager>().LoadParallax();
|
||||||
|
IoCManager.Resolve<IBaseClient>().PlayerJoinedServer += SubscribePlayerAttachmentEvents;
|
||||||
|
|
||||||
|
var stylesheet = new NanoStyle();
|
||||||
|
|
||||||
|
IoCManager.Resolve<IUserInterfaceManager>().Stylesheet = stylesheet.Stylesheet;
|
||||||
|
IoCManager.Resolve<IUserInterfaceManager>().Stylesheet = stylesheet.Stylesheet;
|
||||||
|
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
_escapeMenuOwner.Initialize();
|
||||||
|
|
||||||
|
_baseClient.PlayerJoinedGame += (sender, args) =>
|
||||||
|
{
|
||||||
|
_stateManager.RequestStateChange<GameScreen>();
|
||||||
|
};
|
||||||
|
|
||||||
|
_baseClient.PlayerJoinedServer += (sender, args) =>
|
||||||
|
{
|
||||||
|
IoCManager.Resolve<IMapManager>().CreateNewMapEntity(MapId.Nullspace);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribe events to the player manager after the player manager is set up
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
public void SubscribePlayerAttachmentEvents(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
_playerManager.LocalPlayer.EntityAttached += AttachPlayerToEntity;
|
||||||
|
_playerManager.LocalPlayer.EntityDetached += DetachPlayerFromEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add the character interface master which combines all character interfaces into one window
|
||||||
|
/// </summary>
|
||||||
|
public static void AttachPlayerToEntity(EntityAttachedEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
eventArgs.NewEntity.AddComponent<CharacterInterface>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove the character interface master from this entity now that we have detached ourselves from it
|
||||||
|
/// </summary>
|
||||||
|
public static void DetachPlayerFromEntity(EntityDetachedEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
eventArgs.OldEntity.RemoveComponent<CharacterInterface>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PostInit()
|
||||||
|
{
|
||||||
|
base.PostInit();
|
||||||
|
|
||||||
|
// Setup key contexts
|
||||||
|
var inputMan = IoCManager.Resolve<IInputManager>();
|
||||||
|
ContentContexts.SetupContexts(inputMan.Contexts);
|
||||||
|
|
||||||
|
IoCManager.Resolve<IGameHud>().Initialize();
|
||||||
|
IoCManager.Resolve<IClientNotifyManager>().Initialize();
|
||||||
|
IoCManager.Resolve<IClientGameTicker>().Initialize();
|
||||||
|
IoCManager.Resolve<IOverlayManager>().AddOverlay(new ParallaxOverlay());
|
||||||
|
IoCManager.Resolve<IChatManager>().Initialize();
|
||||||
|
IoCManager.Resolve<ISandboxManager>().Initialize();
|
||||||
|
IoCManager.Resolve<IClientPreferencesManager>().Initialize();
|
||||||
|
IoCManager.Resolve<IItemSlotManager>().Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs)
|
||||||
|
{
|
||||||
|
base.Update(level, frameEventArgs);
|
||||||
|
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case ModUpdateLevel.FramePreEngine:
|
||||||
|
IoCManager.Resolve<IClientNotifyManager>().FrameUpdate(frameEventArgs);
|
||||||
|
IoCManager.Resolve<IClientGameTicker>().FrameUpdate(frameEventArgs);
|
||||||
|
IoCManager.Resolve<IChatManager>().FrameUpdate(frameEventArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
103
Content.Client/EscapeMenuOwner.cs
Normal file
103
Content.Client/EscapeMenuOwner.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using Content.Client.State;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Robust.Client.Console;
|
||||||
|
using Robust.Client.Interfaces.Input;
|
||||||
|
using Robust.Client.Interfaces.Placement;
|
||||||
|
using Robust.Client.Interfaces.ResourceManagement;
|
||||||
|
using Robust.Client.Interfaces.State;
|
||||||
|
using Robust.Client.State.States;
|
||||||
|
using Robust.Shared.Input;
|
||||||
|
using Robust.Shared.Interfaces.Configuration;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client
|
||||||
|
{
|
||||||
|
internal sealed class EscapeMenuOwner : IEscapeMenuOwner
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IClientConsole _clientConsole;
|
||||||
|
[Dependency] private readonly IConfigurationManager _configurationManager;
|
||||||
|
[Dependency] private readonly IInputManager _inputManager;
|
||||||
|
[Dependency] private readonly IPlacementManager _placementManager;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
||||||
|
[Dependency] private readonly IResourceCache _resourceCache;
|
||||||
|
[Dependency] private readonly IStateManager _stateManager;
|
||||||
|
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager;
|
||||||
|
[Dependency] private readonly IGameHud _gameHud;
|
||||||
|
[Dependency] private readonly ILocalizationManager _localizationManager;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private EscapeMenu _escapeMenu;
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_stateManager.OnStateChanged += StateManagerOnOnStateChanged;
|
||||||
|
|
||||||
|
_gameHud.EscapeButtonToggled += _setOpenValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StateManagerOnOnStateChanged(StateChangedEventArgs obj)
|
||||||
|
{
|
||||||
|
if (obj.NewState is GameScreen)
|
||||||
|
{
|
||||||
|
// Switched TO GameScreen.
|
||||||
|
_escapeMenu = new EscapeMenu(_clientConsole, _tileDefinitionManager, _placementManager,
|
||||||
|
_prototypeManager, _resourceCache, _configurationManager, _localizationManager);
|
||||||
|
|
||||||
|
_escapeMenu.OnClose += () => _gameHud.EscapeButtonDown = false;
|
||||||
|
|
||||||
|
_inputManager.SetInputCommand(EngineKeyFunctions.EscapeMenu,
|
||||||
|
InputCmdHandler.FromDelegate(s => Enabled()));
|
||||||
|
}
|
||||||
|
else if (obj.OldState is GameScreen)
|
||||||
|
{
|
||||||
|
// Switched FROM GameScreen.
|
||||||
|
_escapeMenu.Dispose();
|
||||||
|
_escapeMenu = null;
|
||||||
|
|
||||||
|
_inputManager.SetInputCommand(EngineKeyFunctions.EscapeMenu, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Enabled()
|
||||||
|
{
|
||||||
|
if (_escapeMenu.IsOpen)
|
||||||
|
{
|
||||||
|
if (_escapeMenu.IsAtFront())
|
||||||
|
{
|
||||||
|
_setOpenValue(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_escapeMenu.MoveToFront();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_setOpenValue(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _setOpenValue(bool value)
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
_gameHud.EscapeButtonDown = true;
|
||||||
|
_escapeMenu.OpenCentered();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_gameHud.EscapeButtonDown = false;
|
||||||
|
_escapeMenu.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IEscapeMenuOwner
|
||||||
|
{
|
||||||
|
void Initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using static Content.Shared.GameObjects.Components.Access.SharedIdCardConsoleComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Access
|
||||||
|
{
|
||||||
|
public class IdCardConsoleBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly ILocalizationManager _localizationManager;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
||||||
|
#pragma warning restore 649
|
||||||
|
public IdCardConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private IdCardConsoleWindow _window;
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_window = new IdCardConsoleWindow(this, _localizationManager, _prototypeManager);
|
||||||
|
_window.Title = Owner.Owner.Name;
|
||||||
|
_window.OnClose += Close;
|
||||||
|
_window.OpenCentered();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
var castState = (IdCardConsoleBoundUserInterfaceState) state;
|
||||||
|
_window.UpdateState(castState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ButtonPressed(UiButton button)
|
||||||
|
{
|
||||||
|
SendMessage(new IdButtonPressedMessage(button));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SubmitData(string newFullName, string newJobTitle, List<string> newAccessList)
|
||||||
|
{
|
||||||
|
SendMessage(new WriteToTargetIdMessage(
|
||||||
|
newFullName,
|
||||||
|
newJobTitle,
|
||||||
|
newAccessList));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
// Only unused on .NET Core due to KeyValuePair.Deconstruct
|
||||||
|
// ReSharper disable once RedundantUsingDirective
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Access;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using static Content.Shared.GameObjects.Components.Access.SharedIdCardConsoleComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Access
|
||||||
|
{
|
||||||
|
public class IdCardConsoleWindow : SS14Window
|
||||||
|
{
|
||||||
|
private readonly Button _privilegedIdButton;
|
||||||
|
private readonly Button _targetIdButton;
|
||||||
|
|
||||||
|
private readonly Label _privilegedIdLabel;
|
||||||
|
private readonly Label _targetIdLabel;
|
||||||
|
|
||||||
|
private readonly Label _fullNameLabel;
|
||||||
|
private readonly LineEdit _fullNameLineEdit;
|
||||||
|
private readonly Label _jobTitleLabel;
|
||||||
|
private readonly LineEdit _jobTitleLineEdit;
|
||||||
|
|
||||||
|
private readonly Button _fullNameSaveButton;
|
||||||
|
private readonly Button _jobTitleSaveButton;
|
||||||
|
|
||||||
|
private readonly IdCardConsoleBoundUserInterface _owner;
|
||||||
|
|
||||||
|
private readonly ILocalizationManager _loc;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, Button> _accessButtons = new Dictionary<string, Button>();
|
||||||
|
|
||||||
|
private string _lastFullName;
|
||||||
|
private string _lastJobTitle;
|
||||||
|
|
||||||
|
protected override Vector2? CustomSize => (650, 270);
|
||||||
|
|
||||||
|
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, ILocalizationManager loc, IPrototypeManager prototypeManager)
|
||||||
|
{
|
||||||
|
_loc = loc;
|
||||||
|
_owner = owner;
|
||||||
|
var vBox = new VBoxContainer();
|
||||||
|
|
||||||
|
vBox.AddChild(new GridContainer
|
||||||
|
{
|
||||||
|
Columns = 3,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label {Text = loc.GetString("Privileged ID:")},
|
||||||
|
(_privilegedIdButton = new Button()),
|
||||||
|
(_privilegedIdLabel = new Label()),
|
||||||
|
|
||||||
|
new Label {Text = loc.GetString("Target ID:")},
|
||||||
|
(_targetIdButton = new Button()),
|
||||||
|
(_targetIdLabel = new Label())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_privilegedIdButton.OnPressed += _ => _owner.ButtonPressed(UiButton.PrivilegedId);
|
||||||
|
_targetIdButton.OnPressed += _ => _owner.ButtonPressed(UiButton.TargetId);
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
vBox.AddChild(new Control {CustomMinimumSize = (0, 8)});
|
||||||
|
|
||||||
|
// Name and job title line edits.
|
||||||
|
vBox.AddChild(new GridContainer
|
||||||
|
{
|
||||||
|
Columns = 3,
|
||||||
|
HSeparationOverride = 4,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
// Name
|
||||||
|
(_fullNameLabel = new Label
|
||||||
|
{
|
||||||
|
Text = loc.GetString("Full name:")
|
||||||
|
}),
|
||||||
|
(_fullNameLineEdit = new LineEdit
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
}),
|
||||||
|
(_fullNameSaveButton = new Button
|
||||||
|
{
|
||||||
|
Text = loc.GetString("Save"),
|
||||||
|
Disabled = true
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Title
|
||||||
|
(_jobTitleLabel = new Label
|
||||||
|
{
|
||||||
|
Text = loc.GetString("Job title:")
|
||||||
|
}),
|
||||||
|
(_jobTitleLineEdit = new LineEdit
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||||
|
}),
|
||||||
|
(_jobTitleSaveButton = new Button
|
||||||
|
{
|
||||||
|
Text = loc.GetString("Save"),
|
||||||
|
Disabled = true
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
_fullNameLineEdit.OnTextEntered += _ => SubmitData();
|
||||||
|
_fullNameLineEdit.OnTextChanged += _ =>
|
||||||
|
{
|
||||||
|
_fullNameSaveButton.Disabled = _fullNameSaveButton.Text == _lastFullName;
|
||||||
|
};
|
||||||
|
_fullNameSaveButton.OnPressed += _ => SubmitData();
|
||||||
|
|
||||||
|
_jobTitleLineEdit.OnTextEntered += _ => SubmitData();
|
||||||
|
_jobTitleLineEdit.OnTextChanged += _ =>
|
||||||
|
{
|
||||||
|
_jobTitleSaveButton.Disabled = _jobTitleLineEdit.Text == _lastJobTitle;
|
||||||
|
};
|
||||||
|
_jobTitleSaveButton.OnPressed += _ => SubmitData();
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
vBox.AddChild(new Control {CustomMinimumSize = (0, 8)});
|
||||||
|
|
||||||
|
{
|
||||||
|
var grid = new GridContainer
|
||||||
|
{
|
||||||
|
Columns = 5,
|
||||||
|
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
|
||||||
|
};
|
||||||
|
vBox.AddChild(grid);
|
||||||
|
|
||||||
|
foreach (var accessLevel in prototypeManager.EnumeratePrototypes<AccessLevelPrototype>())
|
||||||
|
{
|
||||||
|
var newButton = new Button
|
||||||
|
{
|
||||||
|
Text = accessLevel.Name,
|
||||||
|
ToggleMode = true,
|
||||||
|
};
|
||||||
|
grid.AddChild(newButton);
|
||||||
|
_accessButtons.Add(accessLevel.ID, newButton);
|
||||||
|
newButton.OnPressed += _ => SubmitData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Contents.AddChild(vBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState(IdCardConsoleBoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
_privilegedIdButton.Text = state.IsPrivilegedIdPresent
|
||||||
|
? _loc.GetString("Eject")
|
||||||
|
: _loc.GetString("Insert");
|
||||||
|
|
||||||
|
_privilegedIdLabel.Text = state.PrivilegedIdName;
|
||||||
|
|
||||||
|
_targetIdButton.Text = state.IsTargetIdPresent
|
||||||
|
? _loc.GetString("Eject")
|
||||||
|
: _loc.GetString("Insert");
|
||||||
|
|
||||||
|
_targetIdLabel.Text = state.TargetIdName;
|
||||||
|
|
||||||
|
var interfaceEnabled =
|
||||||
|
state.IsPrivilegedIdPresent && state.IsPrivilegedIdAuthorized && state.IsTargetIdPresent;
|
||||||
|
|
||||||
|
var fullNameDirty = _lastFullName != null && _fullNameLineEdit.Text != state.TargetIdFullName;
|
||||||
|
var jobTitleDirty = _lastJobTitle != null && _jobTitleLineEdit.Text != state.TargetIdJobTitle;
|
||||||
|
|
||||||
|
_fullNameLabel.Modulate = interfaceEnabled ? Color.White : Color.Gray;
|
||||||
|
_fullNameLineEdit.Editable = interfaceEnabled;
|
||||||
|
if (!fullNameDirty)
|
||||||
|
{
|
||||||
|
_fullNameLineEdit.Text = state.TargetIdFullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
_fullNameSaveButton.Disabled = !interfaceEnabled || !fullNameDirty;
|
||||||
|
|
||||||
|
_jobTitleLabel.Modulate = interfaceEnabled ? Color.White : Color.Gray;
|
||||||
|
_jobTitleLineEdit.Editable = interfaceEnabled;
|
||||||
|
if (!jobTitleDirty)
|
||||||
|
{
|
||||||
|
_jobTitleLineEdit.Text = state.TargetIdJobTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
_jobTitleSaveButton.Disabled = !interfaceEnabled || !jobTitleDirty;
|
||||||
|
|
||||||
|
foreach (var (accessName, button) in _accessButtons)
|
||||||
|
{
|
||||||
|
button.Disabled = !interfaceEnabled;
|
||||||
|
if (interfaceEnabled)
|
||||||
|
{
|
||||||
|
button.Pressed = state.TargetIdAccessList.Contains(accessName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastFullName = state.TargetIdFullName;
|
||||||
|
_lastJobTitle = state.TargetIdJobTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubmitData()
|
||||||
|
{
|
||||||
|
_owner.SubmitData(
|
||||||
|
_fullNameLineEdit.Text,
|
||||||
|
_jobTitleLineEdit.Text,
|
||||||
|
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
|
||||||
|
_accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
using Content.Client.GameObjects.Components.Mobs;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Client.Interfaces.ResourceManagement;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Actor
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class CharacterInfoComponent : Component, ICharacterUI
|
||||||
|
{
|
||||||
|
private CharacterInfoControl _control;
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly ILocalizationManager _loc;
|
||||||
|
[Dependency] private readonly IResourceCache _resourceCache;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
public override string Name => "CharacterInfo";
|
||||||
|
|
||||||
|
public Control Scene { get; private set; }
|
||||||
|
public UIPriority Priority => UIPriority.Info;
|
||||||
|
|
||||||
|
public override void OnAdd()
|
||||||
|
{
|
||||||
|
base.OnAdd();
|
||||||
|
|
||||||
|
Scene = _control = new CharacterInfoControl(_resourceCache, _loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out ISpriteComponent spriteComponent))
|
||||||
|
{
|
||||||
|
_control.SpriteView.Sprite = spriteComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
_control.NameLabel.Text = Owner.Name;
|
||||||
|
// ReSharper disable once StringLiteralTypo
|
||||||
|
_control.SubText.Text = _loc.GetString("Professional Greyshirt");
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class CharacterInfoControl : VBoxContainer
|
||||||
|
{
|
||||||
|
public SpriteView SpriteView { get; }
|
||||||
|
public Label NameLabel { get; }
|
||||||
|
public Label SubText { get; }
|
||||||
|
|
||||||
|
public CharacterInfoControl(IResourceCache resourceCache, ILocalizationManager loc)
|
||||||
|
{
|
||||||
|
AddChild(new HBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
(SpriteView = new SpriteView { Scale = (2, 2)}),
|
||||||
|
new VBoxContainer
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.None,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
(NameLabel = new Label()),
|
||||||
|
(SubText = new Label
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.None,
|
||||||
|
StyleClasses = {NanoStyle.StyleClassLabelSubText}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddChild(new Placeholder(resourceCache)
|
||||||
|
{
|
||||||
|
PlaceholderText = loc.GetString("Health & status effects")
|
||||||
|
});
|
||||||
|
|
||||||
|
AddChild(new Placeholder(resourceCache)
|
||||||
|
{
|
||||||
|
PlaceholderText = loc.GetString("Objectives")
|
||||||
|
});
|
||||||
|
|
||||||
|
AddChild(new Placeholder(resourceCache)
|
||||||
|
{
|
||||||
|
PlaceholderText = loc.GetString("Antagonist Roles")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Client.GameObjects.Components.Mobs;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Shared.Input;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.Input;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Actor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A semi-abstract component which gets added to entities upon attachment and collects all character
|
||||||
|
/// user interfaces into a single window and keybind for the user
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public class CharacterInterface : Component
|
||||||
|
{
|
||||||
|
public override string Name => "Character Interface Component";
|
||||||
|
|
||||||
|
[Dependency]
|
||||||
|
#pragma warning disable 649
|
||||||
|
private readonly IGameHud _gameHud;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Window to hold each of the character interfaces
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Null if it would otherwise be empty.
|
||||||
|
/// </remarks>
|
||||||
|
public SS14Window Window { get; private set; }
|
||||||
|
|
||||||
|
private List<ICharacterUI> _uiComponents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create the window with all character UIs and bind it to a keypress
|
||||||
|
/// </summary>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
//Use all the character ui interfaced components to create the character window
|
||||||
|
_uiComponents = Owner.GetAllComponents<ICharacterUI>().ToList();
|
||||||
|
if (_uiComponents.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Window = new CharacterWindow(_uiComponents);
|
||||||
|
Window.OnClose += () => _gameHud.CharacterButtonDown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of window and the keypress binding
|
||||||
|
/// </summary>
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
base.OnRemove();
|
||||||
|
|
||||||
|
foreach (var component in _uiComponents)
|
||||||
|
{
|
||||||
|
// Make sure these don't get deleted when the window is disposed.
|
||||||
|
component.Scene.Orphan();
|
||||||
|
}
|
||||||
|
|
||||||
|
_uiComponents = null;
|
||||||
|
|
||||||
|
Window?.Close();
|
||||||
|
Window = null;
|
||||||
|
|
||||||
|
var inputMgr = IoCManager.Resolve<IInputManager>();
|
||||||
|
inputMgr.SetInputCommand(ContentKeyFunctions.OpenCharacterMenu, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
|
||||||
|
IComponent component = null)
|
||||||
|
{
|
||||||
|
base.HandleMessage(message, netChannel, component);
|
||||||
|
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case PlayerAttachedMsg _:
|
||||||
|
if (Window != null)
|
||||||
|
{
|
||||||
|
_gameHud.CharacterButtonVisible = true;
|
||||||
|
_gameHud.CharacterButtonToggled = b =>
|
||||||
|
{
|
||||||
|
if (b)
|
||||||
|
{
|
||||||
|
Window.Open();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Window.Close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PlayerDetachedMsg _:
|
||||||
|
if (Window != null)
|
||||||
|
{
|
||||||
|
_gameHud.CharacterButtonVisible = false;
|
||||||
|
Window.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A window that collects and shows all the individual character user interfaces
|
||||||
|
/// </summary>
|
||||||
|
public class CharacterWindow : SS14Window
|
||||||
|
{
|
||||||
|
private readonly VBoxContainer _contentsVBox;
|
||||||
|
|
||||||
|
public CharacterWindow(List<ICharacterUI> windowComponents)
|
||||||
|
{
|
||||||
|
Title = "Character";
|
||||||
|
|
||||||
|
_contentsVBox = new VBoxContainer();
|
||||||
|
Contents.AddChild(_contentsVBox);
|
||||||
|
|
||||||
|
windowComponents.Sort((a, b) => ((int) a.Priority).CompareTo((int) b.Priority));
|
||||||
|
foreach (var element in windowComponents)
|
||||||
|
{
|
||||||
|
_contentsVBox.AddChild(element.Scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines ordering of the character user interface, small values come sooner
|
||||||
|
/// </summary>
|
||||||
|
public enum UIPriority
|
||||||
|
{
|
||||||
|
First = 0,
|
||||||
|
Info = 5,
|
||||||
|
Species = 100,
|
||||||
|
Last = 99999
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using Robust.Client.Animations;
|
||||||
|
using Robust.Client.GameObjects.Components.Animations;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Animations;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class AnimationsTestComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "AnimationsTest";
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
var animations = Owner.GetComponent<AnimationPlayerComponent>();
|
||||||
|
animations.Play(new Animation
|
||||||
|
{
|
||||||
|
Length = TimeSpan.FromSeconds(20),
|
||||||
|
AnimationTracks =
|
||||||
|
{
|
||||||
|
new AnimationTrackComponentProperty
|
||||||
|
{
|
||||||
|
ComponentType = typeof(ITransformComponent),
|
||||||
|
Property = nameof(ITransformComponent.LocalRotation),
|
||||||
|
InterpolationMode = AnimationInterpolationMode.Linear,
|
||||||
|
KeyFrames =
|
||||||
|
{
|
||||||
|
new AnimationTrackProperty.KeyFrame(Angle.Zero, 0),
|
||||||
|
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(1440), 20)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new AnimationTrackComponentProperty
|
||||||
|
{
|
||||||
|
ComponentType = typeof(ISpriteComponent),
|
||||||
|
Property = "layer/0/texture",
|
||||||
|
KeyFrames =
|
||||||
|
{
|
||||||
|
new AnimationTrackProperty.KeyFrame("Objects/toolbox_r.png", 0),
|
||||||
|
new AnimationTrackProperty.KeyFrame("Objects/Toolbox_b.png", 5),
|
||||||
|
new AnimationTrackProperty.KeyFrame("Objects/Toolbox_y.png", 5),
|
||||||
|
new AnimationTrackProperty.KeyFrame("Objects/toolbox_r.png", 5),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "yes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
using Content.Client.UserInterface.Cargo;
|
||||||
|
using Content.Shared.GameObjects.Components.Cargo;
|
||||||
|
using Content.Shared.Prototypes.Cargo;
|
||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Cargo
|
||||||
|
{
|
||||||
|
public class CargoConsoleBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
[ViewVariables]
|
||||||
|
private CargoConsoleMenu _menu;
|
||||||
|
[ViewVariables]
|
||||||
|
private CargoConsoleOrderMenu _orderMenu;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public GalacticMarketComponent Market { get; private set; }
|
||||||
|
[ViewVariables]
|
||||||
|
public CargoOrderDatabaseComponent Orders { get; private set; }
|
||||||
|
[ViewVariables]
|
||||||
|
public bool RequestOnly { get; private set; }
|
||||||
|
[ViewVariables]
|
||||||
|
public int BankId { get; private set; }
|
||||||
|
[ViewVariables]
|
||||||
|
public string BankName { get; private set; }
|
||||||
|
[ViewVariables]
|
||||||
|
public int BankBalance { get; private set; }
|
||||||
|
|
||||||
|
private CargoProductPrototype _product;
|
||||||
|
|
||||||
|
public CargoConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
if (!Owner.Owner.TryGetComponent(out GalacticMarketComponent market)
|
||||||
|
|| !Owner.Owner.TryGetComponent(out CargoOrderDatabaseComponent orders)) return;
|
||||||
|
|
||||||
|
Market = market;
|
||||||
|
Orders = orders;
|
||||||
|
|
||||||
|
_menu = new CargoConsoleMenu(this);
|
||||||
|
_orderMenu = new CargoConsoleOrderMenu();
|
||||||
|
|
||||||
|
_menu.OnClose += Close;
|
||||||
|
|
||||||
|
_menu.Populate();
|
||||||
|
|
||||||
|
Market.OnDatabaseUpdated += _menu.PopulateProducts;
|
||||||
|
Market.OnDatabaseUpdated += _menu.PopulateCategories;
|
||||||
|
Orders.OnDatabaseUpdated += _menu.PopulateOrders;
|
||||||
|
|
||||||
|
_menu.CallShuttleButton.OnPressed += (args) =>
|
||||||
|
{
|
||||||
|
SendMessage(new SharedCargoConsoleComponent.CargoConsoleShuttleMessage());
|
||||||
|
};
|
||||||
|
_menu.OnItemSelected += (args) =>
|
||||||
|
{
|
||||||
|
if (!(args.Button.Parent is CargoProductRow row))
|
||||||
|
return;
|
||||||
|
_product = row.Product;
|
||||||
|
_orderMenu.Requester.Text = null;
|
||||||
|
_orderMenu.Reason.Text = null;
|
||||||
|
_orderMenu.Amount.Value = 1;
|
||||||
|
_orderMenu.OpenCenteredMinSize();
|
||||||
|
};
|
||||||
|
_menu.OnOrderApproved += ApproveOrder;
|
||||||
|
_menu.OnOrderCanceled += RemoveOrder;
|
||||||
|
_orderMenu.SubmitButton.OnPressed += (args) =>
|
||||||
|
{
|
||||||
|
AddOrder();
|
||||||
|
_orderMenu.Close();
|
||||||
|
};
|
||||||
|
|
||||||
|
_menu.OpenCentered();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
if (!(state is CargoConsoleInterfaceState cstate))
|
||||||
|
return;
|
||||||
|
if (RequestOnly != cstate.RequestOnly)
|
||||||
|
{
|
||||||
|
RequestOnly = cstate.RequestOnly;
|
||||||
|
_menu.UpdateRequestOnly();
|
||||||
|
}
|
||||||
|
BankId = cstate.BankId;
|
||||||
|
BankName = cstate.BankName;
|
||||||
|
BankBalance = cstate.BankBalance;
|
||||||
|
_menu.UpdateBankData();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing) return;
|
||||||
|
Market.OnDatabaseUpdated -= _menu.PopulateProducts;
|
||||||
|
Market.OnDatabaseUpdated -= _menu.PopulateCategories;
|
||||||
|
Orders.OnDatabaseUpdated -= _menu.PopulateOrders;
|
||||||
|
_menu?.Dispose();
|
||||||
|
_orderMenu?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddOrder()
|
||||||
|
{
|
||||||
|
SendMessage(new SharedCargoConsoleComponent.CargoConsoleAddOrderMessage(_orderMenu.Requester.Text,
|
||||||
|
_orderMenu.Reason.Text, _product.ID, _orderMenu.Amount.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RemoveOrder(BaseButton.ButtonEventArgs args)
|
||||||
|
{
|
||||||
|
if (!(args.Button.Parent.Parent is CargoOrderRow row))
|
||||||
|
return;
|
||||||
|
SendMessage(new SharedCargoConsoleComponent.CargoConsoleRemoveOrderMessage(row.Order.OrderNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ApproveOrder(BaseButton.ButtonEventArgs args)
|
||||||
|
{
|
||||||
|
if (!(args.Button.Parent.Parent is CargoOrderRow row))
|
||||||
|
return;
|
||||||
|
SendMessage(new SharedCargoConsoleComponent.CargoConsoleApproveOrderMessage(row.Order.OrderNumber));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Cargo;
|
||||||
|
using Content.Shared.Prototypes.Cargo;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Cargo
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class CargoOrderDatabaseComponent : SharedCargoOrderDatabaseComponent
|
||||||
|
{
|
||||||
|
private List<CargoOrderData> _orders = new List<CargoOrderData>();
|
||||||
|
|
||||||
|
public IReadOnlyList<CargoOrderData> Orders => _orders;
|
||||||
|
/// <summary>
|
||||||
|
/// Event called when the database is updated.
|
||||||
|
/// </summary>
|
||||||
|
public event Action OnDatabaseUpdated;
|
||||||
|
|
||||||
|
// TODO add account selector menu
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all orders from the database.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Clear()
|
||||||
|
{
|
||||||
|
_orders.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an order to the database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="order">The order to be added.</param>
|
||||||
|
public virtual void AddOrder(CargoOrderData order)
|
||||||
|
{
|
||||||
|
if (!_orders.Contains(order))
|
||||||
|
_orders.Add(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
base.HandleComponentState(curState, nextState);
|
||||||
|
if (!(curState is CargoOrderDatabaseState state))
|
||||||
|
return;
|
||||||
|
Clear();
|
||||||
|
if (state.Orders == null)
|
||||||
|
return;
|
||||||
|
foreach (var order in state.Orders)
|
||||||
|
{
|
||||||
|
AddOrder(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnDatabaseUpdated?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Cargo;
|
||||||
|
using Content.Shared.Prototypes.Cargo;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Cargo
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class GalacticMarketComponent : SharedGalacticMarketComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0649
|
||||||
|
[Dependency] private IPrototypeManager _prototypeManager;
|
||||||
|
#pragma warning restore
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event called when the database is updated.
|
||||||
|
/// </summary>
|
||||||
|
public event Action OnDatabaseUpdated;
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
base.HandleComponentState(curState, nextState);
|
||||||
|
if (!(curState is GalacticMarketState state))
|
||||||
|
return;
|
||||||
|
_products.Clear();
|
||||||
|
foreach (var productId in state.Products)
|
||||||
|
{
|
||||||
|
if (!_prototypeManager.TryIndex(productId, out CargoProductPrototype product))
|
||||||
|
continue;
|
||||||
|
_products.Add(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnDatabaseUpdated?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.GameObjects.Components.Chemistry;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using static Content.Shared.GameObjects.Components.Chemistry.SharedReagentDispenserComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Chemistry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a <see cref="ReagentDispenserWindow"/> and updates it when new server messages are received.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class ReagentDispenserBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly ILocalizationManager _localizationManager;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private ReagentDispenserWindow _window;
|
||||||
|
private ReagentDispenserBoundUserInterfaceState _lastState;
|
||||||
|
|
||||||
|
public ReagentDispenserBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called each time a dispenser UI instance is opened. Generates the dispenser window and fills it with
|
||||||
|
/// relevant info. Sets the actions for static buttons.
|
||||||
|
/// <para>Buttons which can change like reagent dispense buttons have their actions set in <see cref="UpdateReagentsList"/>.</para>
|
||||||
|
/// </summary>
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
//Setup window layout/elements
|
||||||
|
_window = new ReagentDispenserWindow
|
||||||
|
{
|
||||||
|
Title = _localizationManager.GetString("Reagent dispenser"),
|
||||||
|
};
|
||||||
|
|
||||||
|
_window.OpenCenteredMinSize();
|
||||||
|
_window.OnClose += Close;
|
||||||
|
|
||||||
|
//Setup static button actions.
|
||||||
|
_window.EjectButton.OnPressed += _ => ButtonPressed(UiButton.Eject);
|
||||||
|
_window.ClearButton.OnPressed += _ => ButtonPressed(UiButton.Clear);
|
||||||
|
_window.DispenseButton1.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount1);
|
||||||
|
_window.DispenseButton5.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount5);
|
||||||
|
_window.DispenseButton10.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount10);
|
||||||
|
_window.DispenseButton25.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount25);
|
||||||
|
_window.DispenseButton50.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount50);
|
||||||
|
_window.DispenseButton100.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the ui each time new state data is sent from the server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">
|
||||||
|
/// Data of the <see cref="SharedReagentDispenserComponent"/> that this ui represents.
|
||||||
|
/// Sent from the server.
|
||||||
|
/// </param>
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
var castState = (ReagentDispenserBoundUserInterfaceState)state;
|
||||||
|
_lastState = castState;
|
||||||
|
|
||||||
|
_window?.UpdateState(castState); //Update window state
|
||||||
|
UpdateReagentsList(castState.Inventory); //Update reagents list & reagent button actions
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the list of reagents that this dispenser can dispense on the UI.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inventory">A list of the reagents which can be dispensed.</param>
|
||||||
|
private void UpdateReagentsList(List<ReagentDispenserInventoryEntry> inventory)
|
||||||
|
{
|
||||||
|
_window.UpdateReagentsList(inventory);
|
||||||
|
for (int i = 0; i < _window.ChemicalList.Children.Count(); i++)
|
||||||
|
{
|
||||||
|
var button = (Button)_window.ChemicalList.Children.ElementAt(i);
|
||||||
|
var i1 = i;
|
||||||
|
button.OnPressed += _ => ButtonPressed(UiButton.Dispense, i1);
|
||||||
|
button.OnMouseEntered += _ =>
|
||||||
|
{
|
||||||
|
if (_lastState != null)
|
||||||
|
{
|
||||||
|
_window.UpdateContainerInfo(_lastState, inventory[i1].ID);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
button.OnMouseExited += _ =>
|
||||||
|
{
|
||||||
|
if (_lastState != null)
|
||||||
|
{
|
||||||
|
_window.UpdateContainerInfo(_lastState);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ButtonPressed(UiButton button, int dispenseIndex = -1)
|
||||||
|
{
|
||||||
|
SendMessage(new UiButtonPressedMessage(button, dispenseIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_window.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Shared.Chemistry;
|
||||||
|
using Content.Shared.GameObjects.Components.Chemistry;
|
||||||
|
using Robust.Client.Graphics.Drawing;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using static Content.Shared.GameObjects.Components.Chemistry.SharedReagentDispenserComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Chemistry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Client-side UI used to control a <see cref="SharedReagentDispenserComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
public class ReagentDispenserWindow : SS14Window
|
||||||
|
{
|
||||||
|
/// <summary>Contains info about the reagent container such as it's contents, if one is loaded into the dispenser.</summary>
|
||||||
|
private readonly VBoxContainer ContainerInfo;
|
||||||
|
|
||||||
|
/// <summary>Sets the dispense amount to 1 when pressed.</summary>
|
||||||
|
public Button DispenseButton1 { get; }
|
||||||
|
|
||||||
|
/// <summary>Sets the dispense amount to 5 when pressed.</summary>
|
||||||
|
public Button DispenseButton5 { get; }
|
||||||
|
|
||||||
|
/// <summary>Sets the dispense amount to 10 when pressed.</summary>
|
||||||
|
public Button DispenseButton10 { get; }
|
||||||
|
|
||||||
|
/// <summary>Sets the dispense amount to 25 when pressed.</summary>
|
||||||
|
public Button DispenseButton25 { get; }
|
||||||
|
|
||||||
|
/// <summary>Sets the dispense amount to 50 when pressed.</summary>
|
||||||
|
public Button DispenseButton50 { get; }
|
||||||
|
|
||||||
|
/// <summary>Sets the dispense amount to 100 when pressed.</summary>
|
||||||
|
public Button DispenseButton100 { get; }
|
||||||
|
|
||||||
|
/// <summary>Ejects the reagent container from the dispenser.</summary>
|
||||||
|
public Button ClearButton { get; }
|
||||||
|
|
||||||
|
/// <summary>Removes all reagents from the reagent container.</summary>
|
||||||
|
public Button EjectButton { get; }
|
||||||
|
|
||||||
|
/// <summary>A grid of buttons for each reagent which can be dispensed.</summary>
|
||||||
|
public GridContainer ChemicalList { get; }
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
||||||
|
[Dependency] private readonly ILocalizationManager _localizationManager;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
protected override Vector2? CustomSize => (500, 600);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create and initialize the dispenser UI client-side. Creates the basic layout,
|
||||||
|
/// actual data isn't filled in until the server sends data about the dispenser.
|
||||||
|
/// </summary>
|
||||||
|
public ReagentDispenserWindow()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
var dispenseAmountGroup = new ButtonGroup();
|
||||||
|
|
||||||
|
Contents.AddChild(new VBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
//First, our dispense amount buttons
|
||||||
|
new HBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label {Text = _localizationManager.GetString("Amount")},
|
||||||
|
//Padding
|
||||||
|
new Control {CustomMinimumSize = (20, 0)},
|
||||||
|
(DispenseButton1 = new Button {Text = "1", Group = dispenseAmountGroup}),
|
||||||
|
(DispenseButton5 = new Button {Text = "5", Group = dispenseAmountGroup}),
|
||||||
|
(DispenseButton10 = new Button {Text = "10", Group = dispenseAmountGroup}),
|
||||||
|
(DispenseButton25 = new Button {Text = "25", Group = dispenseAmountGroup}),
|
||||||
|
(DispenseButton50 = new Button {Text = "50", Group = dispenseAmountGroup}),
|
||||||
|
(DispenseButton100 = new Button {Text = "100", Group = dispenseAmountGroup}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//Padding
|
||||||
|
new Control {CustomMinimumSize = (0.0f, 10.0f)},
|
||||||
|
//Grid of which reagents can be dispensed.
|
||||||
|
(ChemicalList = new GridContainer
|
||||||
|
{
|
||||||
|
Columns = 5
|
||||||
|
}),
|
||||||
|
//Padding
|
||||||
|
new Control {CustomMinimumSize = (0.0f, 10.0f)},
|
||||||
|
new HBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label {Text = _localizationManager.GetString("Container: ")},
|
||||||
|
(ClearButton = new Button {Text = _localizationManager.GetString("Clear")}),
|
||||||
|
(EjectButton = new Button {Text = _localizationManager.GetString("Eject")})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//Wrap the container info in a PanelContainer so we can color it's background differently.
|
||||||
|
new PanelContainer
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||||
|
SizeFlagsStretchRatio = 6,
|
||||||
|
CustomMinimumSize = (0, 150),
|
||||||
|
PanelOverride = new StyleBoxFlat
|
||||||
|
{
|
||||||
|
BackgroundColor = new Color(27, 27, 30)
|
||||||
|
},
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
//Currently empty, when server sends state data this will have container contents and fill volume.
|
||||||
|
(ContainerInfo = new VBoxContainer
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = _localizationManager.GetString("No container loaded.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the button grid of reagents which can be dispensed.
|
||||||
|
/// <para>The actions for these buttons are set in <see cref="ReagentDispenserBoundUserInterface.UpdateReagentsList"/>.</para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inventory">Reagents which can be dispensed by this dispenser</param>
|
||||||
|
public void UpdateReagentsList(List<ReagentDispenserInventoryEntry> inventory)
|
||||||
|
{
|
||||||
|
if (ChemicalList == null) return;
|
||||||
|
if (inventory == null) return;
|
||||||
|
|
||||||
|
ChemicalList.Children.Clear();
|
||||||
|
|
||||||
|
foreach (var entry in inventory)
|
||||||
|
{
|
||||||
|
if (_prototypeManager.TryIndex(entry.ID, out ReagentPrototype proto))
|
||||||
|
{
|
||||||
|
ChemicalList.AddChild(new Button {Text = proto.Name});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ChemicalList.AddChild(new Button {Text = _localizationManager.GetString("Reagent name not found")});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the UI state when new state data is received from the server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">State data sent by the server.</param>
|
||||||
|
public void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
var castState = (ReagentDispenserBoundUserInterfaceState) state;
|
||||||
|
Title = castState.DispenserName;
|
||||||
|
UpdateContainerInfo(castState);
|
||||||
|
|
||||||
|
switch (castState.SelectedDispenseAmount)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
DispenseButton1.Pressed = true;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
DispenseButton5.Pressed = true;
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
DispenseButton10.Pressed = true;
|
||||||
|
break;
|
||||||
|
case 25:
|
||||||
|
DispenseButton25.Pressed = true;
|
||||||
|
break;
|
||||||
|
case 50:
|
||||||
|
DispenseButton50.Pressed = true;
|
||||||
|
break;
|
||||||
|
case 100:
|
||||||
|
DispenseButton100.Pressed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the fill state and list of reagents held by the current reagent container, if applicable.
|
||||||
|
/// <para>Also highlights a reagent if it's dispense button is being mouse hovered.</para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">State data for the dispenser.</param>
|
||||||
|
/// <param name="highlightedReagentId">Prototype id of the reagent whose dispense button is currently being mouse hovered.</param>
|
||||||
|
public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state,
|
||||||
|
string highlightedReagentId = null)
|
||||||
|
{
|
||||||
|
ContainerInfo.Children.Clear();
|
||||||
|
|
||||||
|
if (!state.HasBeaker)
|
||||||
|
{
|
||||||
|
ContainerInfo.Children.Add(new Label {Text = _localizationManager.GetString("No container loaded.")});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContainerInfo.Children.Add(new HBoxContainer // Name of the container and its fill status (Ex: 44/100u)
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label {Text = $"{state.ContainerName}: "},
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = $"{state.BeakerCurrentVolume}/{state.BeakerMaxVolume}",
|
||||||
|
StyleClasses = {NanoStyle.StyleClassLabelSecondaryColor}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (state.ContainerReagents == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var reagent in state.ContainerReagents)
|
||||||
|
{
|
||||||
|
var name = _localizationManager.GetString("Unknown reagent");
|
||||||
|
//Try to the prototype for the given reagent. This gives us it's name.
|
||||||
|
if (_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
|
||||||
|
{
|
||||||
|
name = proto.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the reagent is being moused over. If so, color it green.
|
||||||
|
if (proto != null && proto.ID == highlightedReagentId)
|
||||||
|
{
|
||||||
|
ContainerInfo.Children.Add(new HBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = $"{name}: ",
|
||||||
|
StyleClasses = {NanoStyle.StyleClassPowerStateGood}
|
||||||
|
},
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = $"{reagent.Quantity}u",
|
||||||
|
StyleClasses = {NanoStyle.StyleClassPowerStateGood}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else //Otherwise, color it the normal colors.
|
||||||
|
{
|
||||||
|
ContainerInfo.Children.Add(new HBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label {Text = $"{name}: "},
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = $"{reagent.Quantity}u",
|
||||||
|
StyleClasses = {NanoStyle.StyleClassLabelSecondaryColor}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using Content.Shared.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
using Content.Shared.GameObjects.Components.Items;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Clothing
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(ItemComponent))]
|
||||||
|
public class ClothingComponent : ItemComponent
|
||||||
|
{
|
||||||
|
private FemaleClothingMask _femaleMask;
|
||||||
|
public override string Name => "Clothing";
|
||||||
|
public override uint? NetID => ContentNetIDs.CLOTHING;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public string ClothingEquippedPrefix { get; set; }
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public FemaleClothingMask FemaleMask
|
||||||
|
{
|
||||||
|
get => _femaleMask;
|
||||||
|
set => _femaleMask = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
|
serializer.DataField(ref _femaleMask, "femaleMask", FemaleClothingMask.UniformFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (RSI rsi, RSI.StateId stateId)? GetEquippedStateInfo(EquipmentSlotDefines.SlotFlags slot)
|
||||||
|
{
|
||||||
|
if (RsiPath == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rsi = GetRSI();
|
||||||
|
var prefix = ClothingEquippedPrefix ?? EquippedPrefix;
|
||||||
|
var stateId = prefix != null ? $"{prefix}-equipped-{slot}" : $"equipped-{slot}";
|
||||||
|
if (rsi.TryGetState(stateId, out _))
|
||||||
|
{
|
||||||
|
return (rsi, stateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
if (curState == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var clothingComponentState = (ClothingComponentState)curState;
|
||||||
|
ClothingEquippedPrefix = clothingComponentState.ClothingEquippedPrefix;
|
||||||
|
EquippedPrefix = clothingComponentState.EquippedPrefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FemaleClothingMask
|
||||||
|
{
|
||||||
|
NoMask = 0,
|
||||||
|
UniformFull,
|
||||||
|
UniformTop
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using Content.Shared.GameObjects.Components;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components
|
||||||
|
{
|
||||||
|
public sealed class ComputerVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
private string KeyboardState = "generic_key";
|
||||||
|
private string ScreenState = "generic";
|
||||||
|
|
||||||
|
public override void LoadData(YamlMappingNode node)
|
||||||
|
{
|
||||||
|
base.LoadData(node);
|
||||||
|
|
||||||
|
if (node.TryGetNode("key", out var scalar))
|
||||||
|
{
|
||||||
|
KeyboardState = scalar.AsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.TryGetNode("screen", out scalar))
|
||||||
|
{
|
||||||
|
ScreenState = scalar.AsString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void InitializeEntity(IEntity entity)
|
||||||
|
{
|
||||||
|
base.InitializeEntity(entity);
|
||||||
|
|
||||||
|
var sprite = entity.GetComponent<ISpriteComponent>();
|
||||||
|
sprite.LayerSetState(Layers.Screen, ScreenState);
|
||||||
|
sprite.LayerSetState(Layers.Keyboard, $"{KeyboardState}_off");
|
||||||
|
sprite.LayerSetState(Layers.KeyboardOn, KeyboardState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
|
||||||
|
if (!component.TryGetData(ComputerVisuals.Powered, out bool powered))
|
||||||
|
{
|
||||||
|
powered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.TryGetData(ComputerVisuals.Broken, out bool broken);
|
||||||
|
|
||||||
|
if (broken)
|
||||||
|
{
|
||||||
|
sprite.LayerSetState(Layers.Body, "broken");
|
||||||
|
sprite.LayerSetState(Layers.Screen, "computer_broken");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprite.LayerSetState(Layers.Body, "computer");
|
||||||
|
sprite.LayerSetState(Layers.Screen, ScreenState);
|
||||||
|
}
|
||||||
|
|
||||||
|
sprite.LayerSetVisible(Layers.Screen, powered);
|
||||||
|
sprite.LayerSetVisible(Layers.KeyboardOn, powered);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Layers
|
||||||
|
{
|
||||||
|
Body,
|
||||||
|
Screen,
|
||||||
|
Keyboard,
|
||||||
|
KeyboardOn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Content.Shared.Construction;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Construction
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class ConstructionGhostComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "ConstructionGhost";
|
||||||
|
|
||||||
|
[ViewVariables] public ConstructionPrototype Prototype { get; set; }
|
||||||
|
[ViewVariables] public ConstructorComponent Master { get; set; }
|
||||||
|
[ViewVariables] public int GhostID { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Client.Construction;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using Content.Shared.GameObjects.Components.Construction;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Construction
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class ConstructorComponent : SharedConstructorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IGameHud _gameHud;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private int nextId;
|
||||||
|
private readonly Dictionary<int, ConstructionGhostComponent> Ghosts = new Dictionary<int, ConstructionGhostComponent>();
|
||||||
|
public ConstructionMenu ConstructionMenu { get; private set; }
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
Owner.GetComponent<ITransformComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
|
||||||
|
{
|
||||||
|
base.HandleMessage(message, netChannel, component);
|
||||||
|
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case PlayerAttachedMsg _:
|
||||||
|
if (ConstructionMenu == null)
|
||||||
|
{
|
||||||
|
ConstructionMenu = new ConstructionMenu {Owner = this};
|
||||||
|
ConstructionMenu.OnClose += () => _gameHud.CraftingButtonDown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_gameHud.CraftingButtonVisible = true;
|
||||||
|
_gameHud.CraftingButtonToggled = b =>
|
||||||
|
{
|
||||||
|
if (b)
|
||||||
|
{
|
||||||
|
ConstructionMenu.Open();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ConstructionMenu.Close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PlayerDetachedMsg _:
|
||||||
|
_gameHud.CraftingButtonVisible = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AckStructureConstructionMessage ackMsg:
|
||||||
|
ClearGhost(ackMsg.Ack);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
ConstructionMenu?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SpawnGhost(ConstructionPrototype prototype, GridCoordinates loc, Direction dir)
|
||||||
|
{
|
||||||
|
var entMgr = IoCManager.Resolve<IClientEntityManager>();
|
||||||
|
var ghost = entMgr.SpawnEntity("constructionghost", loc);
|
||||||
|
var comp = ghost.GetComponent<ConstructionGhostComponent>();
|
||||||
|
comp.Prototype = prototype;
|
||||||
|
comp.Master = this;
|
||||||
|
comp.GhostID = nextId++;
|
||||||
|
ghost.GetComponent<ITransformComponent>().LocalRotation = dir.ToAngle();
|
||||||
|
var sprite = ghost.GetComponent<SpriteComponent>();
|
||||||
|
sprite.LayerSetSprite(0, prototype.Icon);
|
||||||
|
sprite.LayerSetVisible(0, true);
|
||||||
|
|
||||||
|
Ghosts.Add(comp.GhostID, comp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TryStartConstruction(int ghostId)
|
||||||
|
{
|
||||||
|
var ghost = Ghosts[ghostId];
|
||||||
|
var transform = ghost.Owner.GetComponent<ITransformComponent>();
|
||||||
|
var msg = new TryStartStructureConstructionMessage(transform.GridPosition, ghost.Prototype.ID, transform.LocalRotation, ghostId);
|
||||||
|
SendNetworkMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearGhost(int ghostId)
|
||||||
|
{
|
||||||
|
if (Ghosts.TryGetValue(ghostId, out var ghost))
|
||||||
|
{
|
||||||
|
ghost.Owner.Delete();
|
||||||
|
Ghosts.Remove(ghostId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Content.Client/GameObjects/Components/DamageableComponent.cs
Normal file
29
Content.Client/GameObjects/Components/DamageableComponent.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fuck I really hate doing this
|
||||||
|
/// TODO: make sure the client only gets damageable component on the clientside entity for its player mob
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public class DamageableComponent : SharedDamageableComponent
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Name => "Damageable";
|
||||||
|
|
||||||
|
public Dictionary<DamageType, int> CurrentDamage = new Dictionary<DamageType, int>();
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
base.HandleComponentState(curState, nextState);
|
||||||
|
|
||||||
|
if(curState is DamageComponentState damagestate)
|
||||||
|
{
|
||||||
|
CurrentDamage = damagestate.CurrentDamage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Client.GameObjects.Components.Wires;
|
||||||
|
using Content.Shared.GameObjects.Components.Doors;
|
||||||
|
using Robust.Client.Animations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.GameObjects.Components.Animations;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Doors
|
||||||
|
{
|
||||||
|
public class AirlockVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
private const string AnimationKey = "airlock_animation";
|
||||||
|
|
||||||
|
private Animation CloseAnimation;
|
||||||
|
private Animation OpenAnimation;
|
||||||
|
private Animation DenyAnimation;
|
||||||
|
|
||||||
|
public override void LoadData(YamlMappingNode node)
|
||||||
|
{
|
||||||
|
base.LoadData(node);
|
||||||
|
|
||||||
|
var openSound = node.GetNode("open_sound").AsString();
|
||||||
|
var closeSound = node.GetNode("close_sound").AsString();
|
||||||
|
var denySound = node.GetNode("deny_sound").AsString();
|
||||||
|
|
||||||
|
CloseAnimation = new Animation {Length = TimeSpan.FromSeconds(1.2f)};
|
||||||
|
{
|
||||||
|
var flick = new AnimationTrackSpriteFlick();
|
||||||
|
CloseAnimation.AnimationTracks.Add(flick);
|
||||||
|
flick.LayerKey = DoorVisualLayers.Base;
|
||||||
|
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("closing", 0f));
|
||||||
|
|
||||||
|
var flickUnlit = new AnimationTrackSpriteFlick();
|
||||||
|
CloseAnimation.AnimationTracks.Add(flickUnlit);
|
||||||
|
flickUnlit.LayerKey = DoorVisualLayers.BaseUnlit;
|
||||||
|
flickUnlit.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("closing_unlit", 0f));
|
||||||
|
|
||||||
|
var flickMaintenancePanel = new AnimationTrackSpriteFlick();
|
||||||
|
CloseAnimation.AnimationTracks.Add(flickMaintenancePanel);
|
||||||
|
flickMaintenancePanel.LayerKey = WiresVisualizer2D.WiresVisualLayers.MaintenancePanel;
|
||||||
|
flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_closing", 0f));
|
||||||
|
|
||||||
|
var sound = new AnimationTrackPlaySound();
|
||||||
|
CloseAnimation.AnimationTracks.Add(sound);
|
||||||
|
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(closeSound, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenAnimation = new Animation {Length = TimeSpan.FromSeconds(1.2f)};
|
||||||
|
{
|
||||||
|
var flick = new AnimationTrackSpriteFlick();
|
||||||
|
OpenAnimation.AnimationTracks.Add(flick);
|
||||||
|
flick.LayerKey = DoorVisualLayers.Base;
|
||||||
|
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("opening", 0f));
|
||||||
|
|
||||||
|
var flickUnlit = new AnimationTrackSpriteFlick();
|
||||||
|
OpenAnimation.AnimationTracks.Add(flickUnlit);
|
||||||
|
flickUnlit.LayerKey = DoorVisualLayers.BaseUnlit;
|
||||||
|
flickUnlit.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("opening_unlit", 0f));
|
||||||
|
|
||||||
|
var flickMaintenancePanel = new AnimationTrackSpriteFlick();
|
||||||
|
OpenAnimation.AnimationTracks.Add(flickMaintenancePanel);
|
||||||
|
flickMaintenancePanel.LayerKey = WiresVisualizer2D.WiresVisualLayers.MaintenancePanel;
|
||||||
|
flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_opening", 0f));
|
||||||
|
|
||||||
|
var sound = new AnimationTrackPlaySound();
|
||||||
|
OpenAnimation.AnimationTracks.Add(sound);
|
||||||
|
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(openSound, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
DenyAnimation = new Animation {Length = TimeSpan.FromSeconds(0.45f)};
|
||||||
|
{
|
||||||
|
var flick = new AnimationTrackSpriteFlick();
|
||||||
|
DenyAnimation.AnimationTracks.Add(flick);
|
||||||
|
flick.LayerKey = DoorVisualLayers.Base;
|
||||||
|
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("deny", 0f));
|
||||||
|
|
||||||
|
var sound = new AnimationTrackPlaySound();
|
||||||
|
DenyAnimation.AnimationTracks.Add(sound);
|
||||||
|
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(denySound, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void InitializeEntity(IEntity entity)
|
||||||
|
{
|
||||||
|
if (!entity.HasComponent<AnimationPlayerComponent>())
|
||||||
|
{
|
||||||
|
entity.AddComponent<AnimationPlayerComponent>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
var animPlayer = component.Owner.GetComponent<AnimationPlayerComponent>();
|
||||||
|
if (!component.TryGetData(DoorVisuals.VisualState, out DoorVisualState state))
|
||||||
|
{
|
||||||
|
state = DoorVisualState.Closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var unlitVisible = true;
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case DoorVisualState.Closed:
|
||||||
|
sprite.LayerSetState(DoorVisualLayers.Base, "closed");
|
||||||
|
sprite.LayerSetState(DoorVisualLayers.BaseUnlit, "closed_unlit");
|
||||||
|
sprite.LayerSetState(WiresVisualizer2D.WiresVisualLayers.MaintenancePanel, "panel_open");
|
||||||
|
break;
|
||||||
|
case DoorVisualState.Closing:
|
||||||
|
if (!animPlayer.HasRunningAnimation(AnimationKey))
|
||||||
|
{
|
||||||
|
animPlayer.Play(CloseAnimation, AnimationKey);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DoorVisualState.Opening:
|
||||||
|
if (!animPlayer.HasRunningAnimation(AnimationKey))
|
||||||
|
{
|
||||||
|
animPlayer.Play(OpenAnimation, AnimationKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case DoorVisualState.Open:
|
||||||
|
sprite.LayerSetState(DoorVisualLayers.Base, "open");
|
||||||
|
unlitVisible = false;
|
||||||
|
break;
|
||||||
|
case DoorVisualState.Deny:
|
||||||
|
unlitVisible = false;
|
||||||
|
if (!animPlayer.HasRunningAnimation(AnimationKey))
|
||||||
|
{
|
||||||
|
animPlayer.Play(DenyAnimation, AnimationKey);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.TryGetData(DoorVisuals.Powered, out bool powered) && !powered)
|
||||||
|
{
|
||||||
|
unlitVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DoorVisualLayers
|
||||||
|
{
|
||||||
|
Base,
|
||||||
|
BaseUnlit
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
// Only unused on .NET Core due to KeyValuePair.Deconstruct
|
||||||
|
// ReSharper disable once RedundantUsingDirective
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Client.GameObjects.Components.Clothing;
|
||||||
|
using Content.Shared.GameObjects;
|
||||||
|
using Content.Shared.Preferences.Appearance;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines;
|
||||||
|
using static Content.Shared.GameObjects.SharedInventoryComponent.ClientInventoryMessage;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A character UI which shows items the user has equipped within his inventory
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public class ClientInventoryComponent : SharedInventoryComponent
|
||||||
|
{
|
||||||
|
private readonly Dictionary<Slots, IEntity> _slots = new Dictionary<Slots, IEntity>();
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public InventoryInterfaceController InterfaceController { get; private set; }
|
||||||
|
|
||||||
|
private ISpriteComponent _sprite;
|
||||||
|
|
||||||
|
private bool _playerAttached = false;
|
||||||
|
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
base.OnRemove();
|
||||||
|
|
||||||
|
if (_playerAttached)
|
||||||
|
{
|
||||||
|
InterfaceController?.PlayerDetached();
|
||||||
|
}
|
||||||
|
InterfaceController?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
var controllerType = ReflectionManager.LooseGetType(InventoryInstance.InterfaceControllerTypeName);
|
||||||
|
var args = new object[] {this};
|
||||||
|
InterfaceController = DynamicTypeFactory.CreateInstance<InventoryInterfaceController>(controllerType, args);
|
||||||
|
InterfaceController.Initialize();
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out _sprite))
|
||||||
|
{
|
||||||
|
foreach (var mask in InventoryInstance.SlotMasks.OrderBy(s => InventoryInstance.SlotDrawingOrder(s)))
|
||||||
|
{
|
||||||
|
if (mask == Slots.NONE)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sprite.LayerMapReserveBlank(mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component state already came in but we couldn't set anything visually because, well, we didn't initialize yet.
|
||||||
|
foreach (var (slot, entity) in _slots)
|
||||||
|
{
|
||||||
|
_setSlot(slot, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
base.HandleComponentState(curState, nextState);
|
||||||
|
|
||||||
|
if (curState == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var cast = (InventoryComponentState) curState;
|
||||||
|
|
||||||
|
var doneSlots = new HashSet<Slots>();
|
||||||
|
|
||||||
|
foreach (var (slot, entityUid) in cast.Entities)
|
||||||
|
{
|
||||||
|
var entity = Owner.EntityManager.GetEntity(entityUid);
|
||||||
|
if (!_slots.ContainsKey(slot) || _slots[slot] != entity)
|
||||||
|
{
|
||||||
|
_slots[slot] = entity;
|
||||||
|
_setSlot(slot, entity);
|
||||||
|
}
|
||||||
|
doneSlots.Add(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var slot in _slots.Keys.ToList())
|
||||||
|
{
|
||||||
|
if (!doneSlots.Contains(slot))
|
||||||
|
{
|
||||||
|
_clearSlot(slot);
|
||||||
|
_slots.Remove(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _setSlot(Slots slot, IEntity entity)
|
||||||
|
{
|
||||||
|
SetSlotVisuals(slot, entity);
|
||||||
|
|
||||||
|
InterfaceController?.AddToSlot(slot, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetSlotVisuals(Slots slot, IEntity entity)
|
||||||
|
{
|
||||||
|
if (_sprite == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity != null && entity.TryGetComponent(out ClothingComponent clothing))
|
||||||
|
{
|
||||||
|
var flag = SlotMasks[slot];
|
||||||
|
var data = clothing.GetEquippedStateInfo(flag);
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
var (rsi, state) = data.Value;
|
||||||
|
_sprite.LayerSetVisible(slot, true);
|
||||||
|
_sprite.LayerSetRSI(slot, rsi);
|
||||||
|
_sprite.LayerSetState(slot, state);
|
||||||
|
|
||||||
|
if (slot == Slots.INNERCLOTHING)
|
||||||
|
{
|
||||||
|
_sprite.LayerSetState(HumanoidVisualLayers.StencilMask, clothing.FemaleMask switch
|
||||||
|
{
|
||||||
|
FemaleClothingMask.NoMask => "female_none",
|
||||||
|
FemaleClothingMask.UniformTop => "female_top",
|
||||||
|
_ => "female_full",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sprite.LayerSetVisible(slot, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ClearAllSlotVisuals()
|
||||||
|
{
|
||||||
|
foreach (var slot in InventoryInstance.SlotMasks)
|
||||||
|
{
|
||||||
|
if (slot != Slots.NONE)
|
||||||
|
{
|
||||||
|
_sprite.LayerSetVisible(slot, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _clearSlot(Slots slot)
|
||||||
|
{
|
||||||
|
InterfaceController?.RemoveFromSlot(slot);
|
||||||
|
_sprite?.LayerSetVisible(slot, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendEquipMessage(Slots slot)
|
||||||
|
{
|
||||||
|
var equipmessage = new ClientInventoryMessage(slot, ClientInventoryUpdate.Equip);
|
||||||
|
SendNetworkMessage(equipmessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendUseMessage(Slots slot)
|
||||||
|
{
|
||||||
|
var equipmessage = new ClientInventoryMessage(slot, ClientInventoryUpdate.Use);
|
||||||
|
SendNetworkMessage(equipmessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendOpenStorageUIMessage(Slots slot)
|
||||||
|
{
|
||||||
|
SendNetworkMessage(new OpenSlotStorageUIMessage(slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
|
||||||
|
IComponent component = null)
|
||||||
|
{
|
||||||
|
base.HandleMessage(message, netChannel, component);
|
||||||
|
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case PlayerAttachedMsg _:
|
||||||
|
InterfaceController.PlayerAttached();
|
||||||
|
_playerAttached = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PlayerDetachedMsg _:
|
||||||
|
InterfaceController.PlayerDetached();
|
||||||
|
_playerAttached = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetSlot(Slots slot, out IEntity item)
|
||||||
|
{
|
||||||
|
return _slots.TryGetValue(slot, out item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
// Only unused on .NET Core due to KeyValuePair.Deconstruct
|
||||||
|
// ReSharper disable once RedundantUsingDirective
|
||||||
|
using Content.Client.Utility;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.Interfaces.ResourceManagement;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects
|
||||||
|
{
|
||||||
|
// Dynamically instantiated by ClientInventoryComponent.
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class HumanInventoryInterfaceController : InventoryInterfaceController
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly ILocalizationManager _loc;
|
||||||
|
[Dependency] private readonly IResourceCache _resourceCache;
|
||||||
|
[Dependency] private readonly IItemSlotManager _itemSlotManager;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private readonly Dictionary<Slots, List<ItemSlotButton>> _inventoryButtons
|
||||||
|
= new Dictionary<Slots, List<ItemSlotButton>>();
|
||||||
|
|
||||||
|
private ItemSlotButton _hudButtonPocket1;
|
||||||
|
private ItemSlotButton _hudButtonPocket2;
|
||||||
|
private ItemSlotButton _hudButtonBelt;
|
||||||
|
private ItemSlotButton _hudButtonBack;
|
||||||
|
private ItemSlotButton _hudButtonId;
|
||||||
|
private Control _quickButtonsContainer;
|
||||||
|
|
||||||
|
public HumanInventoryInterfaceController(ClientInventoryComponent owner) : base(owner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_window = new HumanInventoryWindow(_loc, _resourceCache);
|
||||||
|
_window.OnClose += () => _gameHud.InventoryButtonDown = false;
|
||||||
|
foreach (var (slot, button) in _window.Buttons)
|
||||||
|
{
|
||||||
|
button.OnPressed = (e) => AddToInventory(e, slot);
|
||||||
|
button.OnStoragePressed = (e) => OpenStorage(e, slot);
|
||||||
|
_inventoryButtons.Add(slot, new List<ItemSlotButton> {button});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddButton(out ItemSlotButton variable, Slots slot, string textureName)
|
||||||
|
{
|
||||||
|
var texture = _resourceCache.GetTexture($"/Textures/UserInterface/Inventory/{textureName}.png");
|
||||||
|
var storageTexture = _resourceCache.GetTexture("/Textures/UserInterface/Inventory/back.png");
|
||||||
|
variable = new ItemSlotButton(texture, storageTexture)
|
||||||
|
{
|
||||||
|
OnPressed = (e) => AddToInventory(e, slot),
|
||||||
|
OnStoragePressed = (e) => OpenStorage(e, slot)
|
||||||
|
};
|
||||||
|
_inventoryButtons[slot].Add(variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddButton(out _hudButtonPocket1, Slots.POCKET1, "pocket");
|
||||||
|
AddButton(out _hudButtonPocket2, Slots.POCKET2, "pocket");
|
||||||
|
AddButton(out _hudButtonBack, Slots.BACKPACK, "back");
|
||||||
|
AddButton(out _hudButtonBelt, Slots.BELT, "belt");
|
||||||
|
AddButton(out _hudButtonId, Slots.IDCARD, "id");
|
||||||
|
|
||||||
|
_quickButtonsContainer = new HBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
_hudButtonId,
|
||||||
|
_hudButtonBelt,
|
||||||
|
_hudButtonBack,
|
||||||
|
_hudButtonPocket1,
|
||||||
|
_hudButtonPocket2,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SS14Window Window => _window;
|
||||||
|
private HumanInventoryWindow _window;
|
||||||
|
|
||||||
|
public override void AddToSlot(Slots slot, IEntity entity)
|
||||||
|
{
|
||||||
|
base.AddToSlot(slot, entity);
|
||||||
|
|
||||||
|
if (!_inventoryButtons.TryGetValue(slot, out var buttons))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var button in buttons)
|
||||||
|
{
|
||||||
|
_itemSlotManager.SetItemSlot(button, entity);
|
||||||
|
button.OnPressed = (e) => HandleInventoryKeybind(e, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RemoveFromSlot(Slots slot)
|
||||||
|
{
|
||||||
|
base.RemoveFromSlot(slot);
|
||||||
|
|
||||||
|
if (!_inventoryButtons.TryGetValue(slot, out var buttons))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var button in buttons)
|
||||||
|
{
|
||||||
|
ClearButton(button, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HandleInventoryKeybind(BaseButton.ButtonEventArgs args, Slots slot)
|
||||||
|
{
|
||||||
|
if (!_inventoryButtons.TryGetValue(slot, out var buttons))
|
||||||
|
return;
|
||||||
|
if (!Owner.TryGetSlot(slot, out var item))
|
||||||
|
return;
|
||||||
|
if (_itemSlotManager.OnButtonPressed(args.Event, item))
|
||||||
|
return;
|
||||||
|
|
||||||
|
base.HandleInventoryKeybind(args, slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearButton(ItemSlotButton button, Slots slot)
|
||||||
|
{
|
||||||
|
button.OnPressed = (e) => AddToInventory(e, slot);
|
||||||
|
_itemSlotManager.SetItemSlot(button, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PlayerAttached()
|
||||||
|
{
|
||||||
|
base.PlayerAttached();
|
||||||
|
|
||||||
|
_gameHud.InventoryQuickButtonContainer.AddChild(_quickButtonsContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PlayerDetached()
|
||||||
|
{
|
||||||
|
base.PlayerDetached();
|
||||||
|
|
||||||
|
_gameHud.InventoryQuickButtonContainer.RemoveChild(_quickButtonsContainer);
|
||||||
|
|
||||||
|
foreach (var (slot, list) in _inventoryButtons)
|
||||||
|
{
|
||||||
|
foreach (var button in list)
|
||||||
|
{
|
||||||
|
ClearButton(button, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HumanInventoryWindow : SS14Window
|
||||||
|
{
|
||||||
|
private const int ButtonSize = 64;
|
||||||
|
private const int ButtonSeparation = 2;
|
||||||
|
private const int RightSeparation = 2;
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<Slots, ItemSlotButton> Buttons { get; }
|
||||||
|
|
||||||
|
public HumanInventoryWindow(ILocalizationManager loc, IResourceCache resourceCache)
|
||||||
|
{
|
||||||
|
Title = loc.GetString("Your Inventory");
|
||||||
|
Resizable = false;
|
||||||
|
|
||||||
|
var buttonDict = new Dictionary<Slots, ItemSlotButton>();
|
||||||
|
Buttons = buttonDict;
|
||||||
|
|
||||||
|
const int width = ButtonSize * 4 + ButtonSeparation * 3 + RightSeparation;
|
||||||
|
const int height = ButtonSize * 4 + ButtonSeparation * 3;
|
||||||
|
|
||||||
|
var windowContents = new LayoutContainer {CustomMinimumSize = (width, height)};
|
||||||
|
Contents.AddChild(windowContents);
|
||||||
|
|
||||||
|
void AddButton(Slots slot, string textureName, Vector2 position)
|
||||||
|
{
|
||||||
|
var texture = resourceCache.GetTexture($"/Textures/UserInterface/Inventory/{textureName}.png");
|
||||||
|
var storageTexture = resourceCache.GetTexture("/Textures/UserInterface/Inventory/back.png");
|
||||||
|
var button = new ItemSlotButton(texture, storageTexture);
|
||||||
|
|
||||||
|
LayoutContainer.SetPosition(button, position);
|
||||||
|
|
||||||
|
windowContents.AddChild(button);
|
||||||
|
buttonDict.Add(slot, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int size = ButtonSize;
|
||||||
|
const int sep = ButtonSeparation;
|
||||||
|
const int rSep = RightSeparation;
|
||||||
|
|
||||||
|
// Left column.
|
||||||
|
AddButton(Slots.EYES, "glasses", (0, size + sep));
|
||||||
|
AddButton(Slots.INNERCLOTHING, "uniform", (0, 2 * (size + sep)));
|
||||||
|
AddButton(Slots.EXOSUITSLOT1, "suit_storage", (0, 3 * (size + sep)));
|
||||||
|
|
||||||
|
// Middle column.
|
||||||
|
AddButton(Slots.HEAD, "head", (size + sep, 0));
|
||||||
|
AddButton(Slots.MASK, "mask", (size + sep, size + sep));
|
||||||
|
AddButton(Slots.OUTERCLOTHING, "suit", (size + sep, 2 * (size + sep)));
|
||||||
|
AddButton(Slots.SHOES, "shoes", (size + sep, 3 * (size + sep)));
|
||||||
|
|
||||||
|
// Right column
|
||||||
|
AddButton(Slots.EARS, "ears", (2 * (size + sep), 0));
|
||||||
|
AddButton(Slots.IDCARD, "id", (2 * (size + sep), size + sep));
|
||||||
|
AddButton(Slots.GLOVES, "gloves", (2 * (size + sep), 2 * (size + sep)));
|
||||||
|
|
||||||
|
// Far right column.
|
||||||
|
AddButton(Slots.BACKPACK, "back", (rSep + 3 * (size + sep), 0));
|
||||||
|
AddButton(Slots.BELT, "belt", (rSep + 3 * (size + sep), size + sep));
|
||||||
|
AddButton(Slots.POCKET1, "pocket", (rSep + 3 * (size + sep), 2 * (size + sep)));
|
||||||
|
AddButton(Slots.POCKET2, "pocket", (rSep + 3 * (size + sep), 3 * (size + sep)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
using Content.Shared.Input;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects
|
||||||
|
{
|
||||||
|
public abstract class InventoryInterfaceController : IDisposable
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] protected readonly IGameHud _gameHud;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
protected InventoryInterfaceController(ClientInventoryComponent owner)
|
||||||
|
{
|
||||||
|
Owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Initialize()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract SS14Window Window { get; }
|
||||||
|
protected ClientInventoryComponent Owner { get; }
|
||||||
|
|
||||||
|
public virtual void PlayerAttached()
|
||||||
|
{
|
||||||
|
_gameHud.InventoryButtonVisible = true;
|
||||||
|
_gameHud.InventoryButtonToggled = b =>
|
||||||
|
{
|
||||||
|
if (b)
|
||||||
|
{
|
||||||
|
Window.Open();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Window.Close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void PlayerDetached()
|
||||||
|
{
|
||||||
|
_gameHud.InventoryButtonVisible = false;
|
||||||
|
Window.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void AddToSlot(EquipmentSlotDefines.Slots slot, IEntity entity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void RemoveFromSlot(EquipmentSlotDefines.Slots slot)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void HandleInventoryKeybind(BaseButton.ButtonEventArgs args, EquipmentSlotDefines.Slots slot)
|
||||||
|
{
|
||||||
|
if (args.Event.CanFocus)
|
||||||
|
{
|
||||||
|
UseItemOnInventory(args, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AddToInventory(BaseButton.ButtonEventArgs args, EquipmentSlotDefines.Slots slot)
|
||||||
|
{
|
||||||
|
if (!args.Event.CanFocus)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
args.Button.Pressed = false;
|
||||||
|
|
||||||
|
Owner.SendEquipMessage(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UseItemOnInventory(BaseButton.ButtonEventArgs args, EquipmentSlotDefines.Slots slot)
|
||||||
|
{
|
||||||
|
args.Button.Pressed = false;
|
||||||
|
|
||||||
|
Owner.SendUseMessage(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OpenStorage(BaseButton.ButtonEventArgs args, EquipmentSlotDefines.Slots slot)
|
||||||
|
{
|
||||||
|
if (!args.Event.CanFocus && args.Event.Function != ContentKeyFunctions.ActivateItemInWorld)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
args.Button.Pressed = false;
|
||||||
|
|
||||||
|
Owner.SendOpenStorageUIMessage(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
106
Content.Client/GameObjects/Components/HandheldLightComponent.cs
Normal file
106
Content.Client/GameObjects/Components/HandheldLightComponent.cs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Shared.GameObjects.Components;
|
||||||
|
using Robust.Client.Graphics.Drawing;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class HandheldLightComponent : SharedHandheldLightComponent, IItemStatus
|
||||||
|
{
|
||||||
|
[ViewVariables] public float? Charge { get; private set; }
|
||||||
|
|
||||||
|
public Control MakeControl()
|
||||||
|
{
|
||||||
|
return new StatusControl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
var cast = (HandheldLightComponentState) curState;
|
||||||
|
|
||||||
|
Charge = cast.Charge;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class StatusControl : Control
|
||||||
|
{
|
||||||
|
private const float TimerCycle = 1;
|
||||||
|
|
||||||
|
private readonly HandheldLightComponent _parent;
|
||||||
|
private readonly PanelContainer[] _sections = new PanelContainer[5];
|
||||||
|
|
||||||
|
private float _timer;
|
||||||
|
|
||||||
|
private static readonly StyleBoxFlat _styleBoxLit = new StyleBoxFlat
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.Green
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly StyleBoxFlat _styleBoxUnlit = new StyleBoxFlat
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.Black
|
||||||
|
};
|
||||||
|
|
||||||
|
public StatusControl(HandheldLightComponent parent)
|
||||||
|
{
|
||||||
|
_parent = parent;
|
||||||
|
|
||||||
|
var wrapper = new HBoxContainer
|
||||||
|
{
|
||||||
|
SeparationOverride = 4,
|
||||||
|
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
|
||||||
|
};
|
||||||
|
|
||||||
|
AddChild(wrapper);
|
||||||
|
|
||||||
|
for (var i = 0; i < _sections.Length; i++)
|
||||||
|
{
|
||||||
|
var panel = new PanelContainer {CustomMinimumSize = (20, 20)};
|
||||||
|
wrapper.AddChild(panel);
|
||||||
|
_sections[i] = panel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update(FrameEventArgs args)
|
||||||
|
{
|
||||||
|
base.Update(args);
|
||||||
|
|
||||||
|
_timer += args.DeltaSeconds;
|
||||||
|
_timer %= TimerCycle;
|
||||||
|
|
||||||
|
var charge = _parent.Charge ?? 0;
|
||||||
|
|
||||||
|
int level;
|
||||||
|
|
||||||
|
if (FloatMath.CloseTo(charge, 0))
|
||||||
|
{
|
||||||
|
level = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
level = 1 + (int) MathF.Round(charge * 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level == 1)
|
||||||
|
{
|
||||||
|
// Flash the last light.
|
||||||
|
_sections[0].PanelOverride = _timer > TimerCycle / 2 ? _styleBoxLit : _styleBoxUnlit;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_sections[0].PanelOverride = level > 2 ? _styleBoxLit : _styleBoxUnlit;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sections[1].PanelOverride = level > 3 ? _styleBoxLit : _styleBoxUnlit;
|
||||||
|
_sections[2].PanelOverride = level > 4 ? _styleBoxLit : _styleBoxUnlit;
|
||||||
|
_sections[3].PanelOverride = level > 5 ? _styleBoxLit : _styleBoxUnlit;
|
||||||
|
_sections[4].PanelOverride = level > 6 ? _styleBoxLit : _styleBoxUnlit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Content.Client/GameObjects/Components/IItemStatus.cs
Normal file
33
Content.Client/GameObjects/Components/IItemStatus.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Robust.Client.UserInterface;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Allows a component to provide status tooltips next to the hands interface.
|
||||||
|
/// </summary>
|
||||||
|
public interface IItemStatus
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Called to get a control that represents the status for this component.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The control to render as status.
|
||||||
|
/// </returns>
|
||||||
|
public Control MakeControl();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the item no longer needs this status (say, dropped from hand)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Useful to allow you to drop the control for the GC, if you need to.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Note that this may be called after a second invocation of <see cref="MakeControl"/> (for example if the user switches the item between two hands).
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public void DestroyControl(Control control)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Content.Client.GameObjects.EntitySystems;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components.Transform;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using static Robust.Client.GameObjects.SpriteComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.IconSmoothing
|
||||||
|
{
|
||||||
|
// TODO: Potential improvements:
|
||||||
|
// Defer updating of these.
|
||||||
|
// Get told by somebody to use a loop.
|
||||||
|
/// <summary>
|
||||||
|
/// Makes sprites of other grid-aligned entities like us connect.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The system is based on Baystation12's smoothwalling, and thus will work with those.
|
||||||
|
/// To use, set <c>base</c> equal to the prefix of the corner states in the sprite base RSI.
|
||||||
|
/// Any objects with the same <c>key</c> will connect.
|
||||||
|
/// </remarks>
|
||||||
|
[RegisterComponent]
|
||||||
|
public class IconSmoothComponent : Component
|
||||||
|
{
|
||||||
|
private string _smoothKey;
|
||||||
|
private string _stateBase;
|
||||||
|
private IconSmoothingMode _mode;
|
||||||
|
|
||||||
|
public override string Name => "IconSmooth";
|
||||||
|
|
||||||
|
internal ISpriteComponent Sprite { get; private set; }
|
||||||
|
internal SnapGridComponent SnapGrid { get; private set; }
|
||||||
|
private (GridId, MapIndices) _lastPosition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// We will smooth with other objects with the same key.
|
||||||
|
/// </summary>
|
||||||
|
public string SmoothKey => _smoothKey;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prepended to the RSI state.
|
||||||
|
/// </summary>
|
||||||
|
public string StateBase => _stateBase;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mode that controls how the icon should be selected.
|
||||||
|
/// </summary>
|
||||||
|
public IconSmoothingMode Mode => _mode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used by <see cref="IconSmoothSystem"/> to reduce redundant updates.
|
||||||
|
/// </summary>
|
||||||
|
internal int UpdateGeneration { get; set; }
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SnapGrid = Owner.GetComponent<SnapGridComponent>();
|
||||||
|
Sprite = Owner.GetComponent<ISpriteComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
|
serializer.DataFieldCached(ref _stateBase, "base", "");
|
||||||
|
serializer.DataFieldCached(ref _smoothKey, "key", null);
|
||||||
|
serializer.DataFieldCached(ref _mode, "mode", IconSmoothingMode.Corners);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Startup()
|
||||||
|
{
|
||||||
|
base.Startup();
|
||||||
|
|
||||||
|
SnapGrid.OnPositionChanged += SnapGridOnPositionChanged;
|
||||||
|
Owner.EntityManager.EventBus.RaiseEvent(Owner, new IconSmoothDirtyEvent(null, SnapGrid.Offset, Mode));
|
||||||
|
if (Mode == IconSmoothingMode.Corners)
|
||||||
|
{
|
||||||
|
var state0 = $"{StateBase}0";
|
||||||
|
Sprite.LayerMapSet(CornerLayers.SE, Sprite.AddLayerState(state0));
|
||||||
|
Sprite.LayerSetDirOffset(CornerLayers.SE, DirectionOffset.None);
|
||||||
|
Sprite.LayerMapSet(CornerLayers.NE, Sprite.AddLayerState(state0));
|
||||||
|
Sprite.LayerSetDirOffset(CornerLayers.NE, DirectionOffset.CounterClockwise);
|
||||||
|
Sprite.LayerMapSet(CornerLayers.NW, Sprite.AddLayerState(state0));
|
||||||
|
Sprite.LayerSetDirOffset(CornerLayers.NW, DirectionOffset.Flip);
|
||||||
|
Sprite.LayerMapSet(CornerLayers.SW, Sprite.AddLayerState(state0));
|
||||||
|
Sprite.LayerSetDirOffset(CornerLayers.SW, DirectionOffset.Clockwise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal virtual void CalculateNewSprite()
|
||||||
|
{
|
||||||
|
switch (Mode)
|
||||||
|
{
|
||||||
|
case IconSmoothingMode.Corners:
|
||||||
|
CalculateNewSpriteCorers();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IconSmoothingMode.CardinalFlags:
|
||||||
|
CalculateNewSpriteCardinal();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CalculateNewSpriteCardinal()
|
||||||
|
{
|
||||||
|
var dirs = CardinalConnectDirs.None;
|
||||||
|
|
||||||
|
if (MatchingEntity(SnapGrid.GetInDir(Direction.North)))
|
||||||
|
dirs |= CardinalConnectDirs.North;
|
||||||
|
if (MatchingEntity(SnapGrid.GetInDir(Direction.South)))
|
||||||
|
dirs |= CardinalConnectDirs.South;
|
||||||
|
if (MatchingEntity(SnapGrid.GetInDir(Direction.East)))
|
||||||
|
dirs |= CardinalConnectDirs.East;
|
||||||
|
if (MatchingEntity(SnapGrid.GetInDir(Direction.West)))
|
||||||
|
dirs |= CardinalConnectDirs.West;
|
||||||
|
|
||||||
|
Sprite.LayerSetState(0, $"{StateBase}{(int) dirs}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CalculateNewSpriteCorers()
|
||||||
|
{
|
||||||
|
var n = MatchingEntity(SnapGrid.GetInDir(Direction.North));
|
||||||
|
var ne = MatchingEntity(SnapGrid.GetInDir(Direction.NorthEast));
|
||||||
|
var e = MatchingEntity(SnapGrid.GetInDir(Direction.East));
|
||||||
|
var se = MatchingEntity(SnapGrid.GetInDir(Direction.SouthEast));
|
||||||
|
var s = MatchingEntity(SnapGrid.GetInDir(Direction.South));
|
||||||
|
var sw = MatchingEntity(SnapGrid.GetInDir(Direction.SouthWest));
|
||||||
|
var w = MatchingEntity(SnapGrid.GetInDir(Direction.West));
|
||||||
|
var nw = MatchingEntity(SnapGrid.GetInDir(Direction.NorthWest));
|
||||||
|
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
var cornerNE = CornerFill.None;
|
||||||
|
var cornerSE = CornerFill.None;
|
||||||
|
var cornerSW = CornerFill.None;
|
||||||
|
var cornerNW = CornerFill.None;
|
||||||
|
// ReSharper restore InconsistentNaming
|
||||||
|
|
||||||
|
if (n)
|
||||||
|
{
|
||||||
|
cornerNE |= CornerFill.CounterClockwise;
|
||||||
|
cornerNW |= CornerFill.Clockwise;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ne)
|
||||||
|
{
|
||||||
|
cornerNE |= CornerFill.Diagonal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e)
|
||||||
|
{
|
||||||
|
cornerNE |= CornerFill.Clockwise;
|
||||||
|
cornerSE |= CornerFill.CounterClockwise;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (se)
|
||||||
|
{
|
||||||
|
cornerSE |= CornerFill.Diagonal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s)
|
||||||
|
{
|
||||||
|
cornerSE |= CornerFill.Clockwise;
|
||||||
|
cornerSW |= CornerFill.CounterClockwise;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sw)
|
||||||
|
{
|
||||||
|
cornerSW |= CornerFill.Diagonal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (w)
|
||||||
|
{
|
||||||
|
cornerSW |= CornerFill.Clockwise;
|
||||||
|
cornerNW |= CornerFill.CounterClockwise;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nw)
|
||||||
|
{
|
||||||
|
cornerNW |= CornerFill.Diagonal;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sprite.LayerSetState(CornerLayers.NE, $"{StateBase}{(int) cornerNE}");
|
||||||
|
Sprite.LayerSetState(CornerLayers.SE, $"{StateBase}{(int) cornerSE}");
|
||||||
|
Sprite.LayerSetState(CornerLayers.SW, $"{StateBase}{(int) cornerSW}");
|
||||||
|
Sprite.LayerSetState(CornerLayers.NW, $"{StateBase}{(int) cornerNW}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
|
||||||
|
SnapGrid.OnPositionChanged -= SnapGridOnPositionChanged;
|
||||||
|
Owner.EntityManager.EventBus.RaiseEvent(Owner, new IconSmoothDirtyEvent(_lastPosition, SnapGrid.Offset, Mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SnapGridOnPositionChanged()
|
||||||
|
{
|
||||||
|
Owner.EntityManager.EventBus.RaiseEvent(Owner, new IconSmoothDirtyEvent(_lastPosition, SnapGrid.Offset, Mode));
|
||||||
|
_lastPosition = (Owner.Transform.GridID, SnapGrid.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.Diagnostics.Contracts.Pure]
|
||||||
|
protected bool MatchingEntity(IEnumerable<IEntity> candidates)
|
||||||
|
{
|
||||||
|
foreach (var entity in candidates)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out IconSmoothComponent other))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other.SmoothKey == SmoothKey)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum CardinalConnectDirs : byte
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
North = 1,
|
||||||
|
South = 2,
|
||||||
|
East = 4,
|
||||||
|
West = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum CornerFill : byte
|
||||||
|
{
|
||||||
|
// These values are pulled from Baystation12.
|
||||||
|
// I'm too lazy to convert the state names.
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
// The cardinal tile counter-clockwise of this corner is filled.
|
||||||
|
CounterClockwise = 1,
|
||||||
|
|
||||||
|
// The diagonal tile in the direction of this corner.
|
||||||
|
Diagonal = 2,
|
||||||
|
|
||||||
|
// The cardinal tile clockwise of this corner is filled.
|
||||||
|
Clockwise = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
|
public enum CornerLayers
|
||||||
|
{
|
||||||
|
SE,
|
||||||
|
NE,
|
||||||
|
NW,
|
||||||
|
SW,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls the mode with which icon smoothing is calculated.
|
||||||
|
/// </summary>
|
||||||
|
[PublicAPI]
|
||||||
|
public enum IconSmoothingMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Each icon is made up of 4 corners, each of which can get a different state depending on
|
||||||
|
/// adjacent entities clockwise, counter-clockwise and diagonal with the corner.
|
||||||
|
/// </summary>
|
||||||
|
Corners,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// There are 16 icons, only one of which is used at once.
|
||||||
|
/// The icon selected is a bit field made up of the cardinal direction flags that have adjacent entities.
|
||||||
|
/// </summary>
|
||||||
|
CardinalFlags,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using Content.Client.Instruments;
|
||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Instruments
|
||||||
|
{
|
||||||
|
public class InstrumentBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
[ViewVariables]
|
||||||
|
private InstrumentMenu _instrumentMenu;
|
||||||
|
|
||||||
|
public InstrumentComponent Instrument { get; set; }
|
||||||
|
|
||||||
|
public InstrumentBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
if (!Owner.Owner.TryGetComponent<InstrumentComponent>(out var instrument)) return;
|
||||||
|
|
||||||
|
Instrument = instrument;
|
||||||
|
_instrumentMenu = new InstrumentMenu(this);
|
||||||
|
_instrumentMenu.OnClose += Close;
|
||||||
|
|
||||||
|
_instrumentMenu.OpenCentered();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing) return;
|
||||||
|
_instrumentMenu?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.GameObjects.Components.Instruments;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Client.Audio.Midi;
|
||||||
|
using Robust.Shared.Audio.Midi;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.Interfaces.Timing;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
using Timer = Robust.Shared.Timers.Timer;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Instruments
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class InstrumentComponent : SharedInstrumentComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Called when a midi song stops playing.
|
||||||
|
/// </summary>
|
||||||
|
public event Action OnMidiPlaybackEnded;
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private IMidiManager _midiManager;
|
||||||
|
[Dependency] private readonly IGameTiming _timing;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private IMidiRenderer _renderer;
|
||||||
|
private int _instrumentProgram = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A queue of MidiEvents to be sent to the server.
|
||||||
|
/// </summary>
|
||||||
|
private Queue<MidiEvent> _eventQueue = new Queue<MidiEvent>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether a midi song will loop or not.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool LoopMidi
|
||||||
|
{
|
||||||
|
get => _renderer.LoopMidi;
|
||||||
|
set => _renderer.LoopMidi = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the instrument the midi renderer will play.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int InstrumentProgram
|
||||||
|
{
|
||||||
|
get => _instrumentProgram;
|
||||||
|
set {
|
||||||
|
_instrumentProgram = value;
|
||||||
|
_renderer.MidiProgram = _instrumentProgram;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether there's a midi song being played or not.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public bool IsMidiOpen => _renderer.Status == MidiRendererStatus.File;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the midi renderer is listening for midi input or not.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public bool IsInputOpen => _renderer.Status == MidiRendererStatus.Input;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
_renderer = _midiManager.GetNewRenderer();
|
||||||
|
_renderer.MidiProgram = _instrumentProgram;
|
||||||
|
_renderer.TrackingEntity = Owner;
|
||||||
|
_renderer.OnMidiPlayerFinished += () => { OnMidiPlaybackEnded?.Invoke(); };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
_renderer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _instrumentProgram, "program", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
|
||||||
|
{
|
||||||
|
base.HandleMessage(message, netChannel, component);
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case InstrumentMidiEventMessage midiEventMessage:
|
||||||
|
// If we're the ones sending the MidiEvents, we ignore this message.
|
||||||
|
if (IsInputOpen || IsMidiOpen) break;
|
||||||
|
Timer.Spawn((int) (500 + _timing.CurTime.TotalMilliseconds - midiEventMessage.Timestamp),
|
||||||
|
() => _renderer.SendMidiEvent(midiEventMessage.MidiEvent));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InstrumentStopMidiMessage _:
|
||||||
|
_renderer.StopAllNotes();
|
||||||
|
if(IsInputOpen) CloseInput();
|
||||||
|
if(IsMidiOpen) CloseMidi();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="MidiRenderer.OpenInput"/>
|
||||||
|
public bool OpenInput()
|
||||||
|
{
|
||||||
|
if (_renderer.OpenInput())
|
||||||
|
{
|
||||||
|
_renderer.OnMidiEvent += RendererOnMidiEvent;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="MidiRenderer.CloseInput"/>
|
||||||
|
public bool CloseInput()
|
||||||
|
{
|
||||||
|
if (!_renderer.CloseInput()) return false;
|
||||||
|
_renderer.OnMidiEvent -= RendererOnMidiEvent;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="MidiRenderer.OpenMidi(string)"/>
|
||||||
|
public bool OpenMidi(string filename)
|
||||||
|
{
|
||||||
|
if (!_renderer.OpenMidi(filename)) return false;
|
||||||
|
_renderer.OnMidiEvent += RendererOnMidiEvent;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="MidiRenderer.CloseMidi"/>
|
||||||
|
public bool CloseMidi()
|
||||||
|
{
|
||||||
|
if (!_renderer.CloseMidi()) return false;
|
||||||
|
_renderer.OnMidiEvent -= RendererOnMidiEvent;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called whenever the renderer receives a midi event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="midiEvent">The received midi event</param>
|
||||||
|
private void RendererOnMidiEvent(MidiEvent midiEvent)
|
||||||
|
{
|
||||||
|
SendNetworkMessage(new InstrumentMidiEventMessage(midiEvent, _timing.CurTime.TotalMilliseconds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using Robust.Client.Graphics.Shaders;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class InteractionOutlineComponent : Component
|
||||||
|
{
|
||||||
|
private const string ShaderInRange = "selection_outline_inrange";
|
||||||
|
private const string ShaderOutOfRange = "selection_outline";
|
||||||
|
|
||||||
|
public override string Name => "InteractionOutline";
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private ShaderInstance _selectionShaderInstance;
|
||||||
|
private ShaderInstance _selectionShaderInRangeInstance;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_selectionShaderInRangeInstance = _prototypeManager.Index<ShaderPrototype>(ShaderInRange).Instance();
|
||||||
|
_selectionShaderInstance = _prototypeManager.Index<ShaderPrototype>(ShaderOutOfRange).Instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnMouseEnter(bool inInteractionRange)
|
||||||
|
{
|
||||||
|
if (Owner.TryGetComponent(out ISpriteComponent sprite))
|
||||||
|
{
|
||||||
|
sprite.PostShader = inInteractionRange ? _selectionShaderInRangeInstance : _selectionShaderInstance;
|
||||||
|
sprite.RenderOrder = Owner.EntityManager.CurrentTick.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnMouseLeave()
|
||||||
|
{
|
||||||
|
if (Owner.TryGetComponent(out ISpriteComponent sprite))
|
||||||
|
{
|
||||||
|
sprite.PostShader = null;
|
||||||
|
sprite.RenderOrder = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateInRange(bool inInteractionRange)
|
||||||
|
{
|
||||||
|
if (Owner.TryGetComponent(out ISpriteComponent sprite))
|
||||||
|
{
|
||||||
|
sprite.PostShader = inInteractionRange ? _selectionShaderInRangeInstance : _selectionShaderInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
// Only unused on .NET Core due to KeyValuePair.Deconstruct
|
||||||
|
// ReSharper disable once RedundantUsingDirective
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Client.Interfaces.GameObjects;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Shared.GameObjects;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(IHandsComponent))]
|
||||||
|
public class HandsComponent : SharedHandsComponent, IHandsComponent
|
||||||
|
{
|
||||||
|
private HandsGui _gui;
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IGameHud _gameHud;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
[ViewVariables] private readonly Dictionary<string, IEntity> _hands = new Dictionary<string, IEntity>();
|
||||||
|
|
||||||
|
[ViewVariables] public string ActiveIndex { get; private set; }
|
||||||
|
|
||||||
|
[ViewVariables] private ISpriteComponent _sprite;
|
||||||
|
|
||||||
|
[ViewVariables] public IEntity ActiveHand => GetEntity(ActiveIndex);
|
||||||
|
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
base.OnRemove();
|
||||||
|
|
||||||
|
_gui?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out _sprite))
|
||||||
|
{
|
||||||
|
foreach (var slot in _hands.Keys)
|
||||||
|
{
|
||||||
|
_sprite.LayerMapReserveBlank($"hand-{slot}");
|
||||||
|
_setHand(slot, _hands[slot]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEntity GetEntity(string index)
|
||||||
|
{
|
||||||
|
if (_hands.TryGetValue(index, out var entity))
|
||||||
|
{
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
if (curState == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var cast = (HandsComponentState) curState;
|
||||||
|
foreach (var (slot, uid) in cast.Hands)
|
||||||
|
{
|
||||||
|
IEntity entity = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
entity = Owner.EntityManager.GetEntity(uid);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
_hands[slot] = entity;
|
||||||
|
_setHand(slot, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var slot in _hands.Keys.ToList())
|
||||||
|
{
|
||||||
|
if (!cast.Hands.ContainsKey(slot))
|
||||||
|
{
|
||||||
|
_hands[slot] = null;
|
||||||
|
_setHand(slot, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveIndex = cast.ActiveIndex;
|
||||||
|
|
||||||
|
_gui?.UpdateHandIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _setHand(string hand, IEntity entity)
|
||||||
|
{
|
||||||
|
if (_sprite == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
_sprite.LayerSetVisible($"hand-{hand}", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = entity.GetComponent<ItemComponent>();
|
||||||
|
var maybeInhands = item.GetInHandStateInfo(hand);
|
||||||
|
if (!maybeInhands.HasValue)
|
||||||
|
{
|
||||||
|
_sprite.LayerSetVisible($"hand-{hand}", false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var (rsi, state) = maybeInhands.Value;
|
||||||
|
_sprite.LayerSetVisible($"hand-{hand}", true);
|
||||||
|
_sprite.LayerSetState($"hand-{hand}", state, rsi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
|
if (!serializer.Reading)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var slot in serializer.ReadDataFieldCached("hands", new List<string>()))
|
||||||
|
{
|
||||||
|
_hands.Add(slot, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
|
||||||
|
IComponent component = null)
|
||||||
|
{
|
||||||
|
base.HandleMessage(message, netChannel, component);
|
||||||
|
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case PlayerAttachedMsg _:
|
||||||
|
if (_gui == null)
|
||||||
|
{
|
||||||
|
_gui = new HandsGui();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_gui.Parent?.RemoveChild(_gui);
|
||||||
|
}
|
||||||
|
|
||||||
|
_gameHud.HandsContainer.AddChild(_gui);
|
||||||
|
_gui.UpdateHandIcons();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PlayerDetachedMsg _:
|
||||||
|
_gui.Parent?.RemoveChild(_gui);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendChangeHand(string index)
|
||||||
|
{
|
||||||
|
SendNetworkMessage(new ClientChangedHandMsg(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AttackByInHand(string index)
|
||||||
|
{
|
||||||
|
SendNetworkMessage(new ClientAttackByInHandMsg(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UseActiveHand()
|
||||||
|
{
|
||||||
|
if (GetEntity(ActiveIndex) != null)
|
||||||
|
{
|
||||||
|
SendNetworkMessage(new UseInHandMsg());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ActivateItemInHand(string handIndex)
|
||||||
|
{
|
||||||
|
if (GetEntity(handIndex) == null)
|
||||||
|
return;
|
||||||
|
SendNetworkMessage(new ActivateInHandMsg(handIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
72
Content.Client/GameObjects/Components/Items/ItemComponent.cs
Normal file
72
Content.Client/GameObjects/Components/Items/ItemComponent.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using Content.Shared.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Items;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.Interfaces.ResourceManagement;
|
||||||
|
using Robust.Client.ResourceManagement;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components.Renderable;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class ItemComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "Item";
|
||||||
|
public override uint? NetID => ContentNetIDs.ITEM;
|
||||||
|
|
||||||
|
[ViewVariables] protected ResourcePath RsiPath;
|
||||||
|
|
||||||
|
private string _equippedPrefix;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public string EquippedPrefix
|
||||||
|
{
|
||||||
|
get => _equippedPrefix;
|
||||||
|
set => _equippedPrefix = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (RSI rsi, RSI.StateId stateId)? GetInHandStateInfo(string hand)
|
||||||
|
{
|
||||||
|
if (RsiPath == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rsi = GetRSI();
|
||||||
|
var stateId = EquippedPrefix != null ? $"{EquippedPrefix}-inhand-{hand}" : $"inhand-{hand}";
|
||||||
|
if (rsi.TryGetState(stateId, out _))
|
||||||
|
{
|
||||||
|
return (rsi, stateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
|
serializer.DataFieldCached(ref RsiPath, "sprite", null);
|
||||||
|
serializer.DataFieldCached(ref _equippedPrefix, "prefix", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RSI GetRSI()
|
||||||
|
{
|
||||||
|
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||||
|
return resourceCache.GetResource<RSIResource>(SharedSpriteComponent.TextureRoot / RsiPath).RSI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
if(curState == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var itemComponentState = (ItemComponentState)curState;
|
||||||
|
EquippedPrefix = itemComponentState.EquippedPrefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class ItemStatusComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "ItemStatus";
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
228
Content.Client/GameObjects/Components/LowWallComponent.cs
Normal file
228
Content.Client/GameObjects/Components/LowWallComponent.cs
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
using Content.Client.GameObjects.Components.IconSmoothing;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
using static Robust.Client.GameObjects.SpriteComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components
|
||||||
|
{
|
||||||
|
// TODO: Over layers should be placed ABOVE the window itself too.
|
||||||
|
// This is gonna require a client entity & parenting,
|
||||||
|
// so IsMapTransform being naive is gonna be a problem.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Override of icon smoothing to handle the specific complexities of low walls.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(IconSmoothComponent))]
|
||||||
|
public class LowWallComponent : IconSmoothComponent
|
||||||
|
{
|
||||||
|
public override string Name => "LowWall";
|
||||||
|
|
||||||
|
public CornerFill LastCornerNE { get; private set; }
|
||||||
|
public CornerFill LastCornerSE { get; private set; }
|
||||||
|
public CornerFill LastCornerSW { get; private set; }
|
||||||
|
public CornerFill LastCornerNW { get; private set; }
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
private IEntity _overlayEntity;
|
||||||
|
private ISpriteComponent _overlaySprite;
|
||||||
|
|
||||||
|
protected override void Startup()
|
||||||
|
{
|
||||||
|
base.Startup();
|
||||||
|
|
||||||
|
_overlayEntity = Owner.EntityManager.SpawnEntity("low_wall_overlay", Owner.Transform.GridPosition);
|
||||||
|
_overlayEntity.Transform.AttachParent(Owner);
|
||||||
|
|
||||||
|
_overlaySprite = _overlayEntity.GetComponent<ISpriteComponent>();
|
||||||
|
|
||||||
|
var overState0 = $"{StateBase}over_0";
|
||||||
|
_overlaySprite.LayerMapSet(OverCornerLayers.SE, _overlaySprite.AddLayerState(overState0));
|
||||||
|
_overlaySprite.LayerSetDirOffset(OverCornerLayers.SE, DirectionOffset.None);
|
||||||
|
_overlaySprite.LayerMapSet(OverCornerLayers.NE, _overlaySprite.AddLayerState(overState0));
|
||||||
|
_overlaySprite.LayerSetDirOffset(OverCornerLayers.NE, DirectionOffset.CounterClockwise);
|
||||||
|
_overlaySprite.LayerMapSet(OverCornerLayers.NW, _overlaySprite.AddLayerState(overState0));
|
||||||
|
_overlaySprite.LayerSetDirOffset(OverCornerLayers.NW, DirectionOffset.Flip);
|
||||||
|
_overlaySprite.LayerMapSet(OverCornerLayers.SW, _overlaySprite.AddLayerState(overState0));
|
||||||
|
_overlaySprite.LayerSetDirOffset(OverCornerLayers.SW, DirectionOffset.Clockwise);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
|
||||||
|
_overlayEntity.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void CalculateNewSprite()
|
||||||
|
{
|
||||||
|
base.CalculateNewSprite();
|
||||||
|
|
||||||
|
var (n, nl) = MatchingWall(SnapGrid.GetInDir(Direction.North));
|
||||||
|
var (ne, nel) = MatchingWall(SnapGrid.GetInDir(Direction.NorthEast));
|
||||||
|
var (e, el) = MatchingWall(SnapGrid.GetInDir(Direction.East));
|
||||||
|
var (se, sel) = MatchingWall(SnapGrid.GetInDir(Direction.SouthEast));
|
||||||
|
var (s, sl) = MatchingWall(SnapGrid.GetInDir(Direction.South));
|
||||||
|
var (sw, swl) = MatchingWall(SnapGrid.GetInDir(Direction.SouthWest));
|
||||||
|
var (w, wl) = MatchingWall(SnapGrid.GetInDir(Direction.West));
|
||||||
|
var (nw, nwl) = MatchingWall(SnapGrid.GetInDir(Direction.NorthWest));
|
||||||
|
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
var cornerNE = CornerFill.None;
|
||||||
|
var cornerSE = CornerFill.None;
|
||||||
|
var cornerSW = CornerFill.None;
|
||||||
|
var cornerNW = CornerFill.None;
|
||||||
|
|
||||||
|
var lowCornerNE = CornerFill.None;
|
||||||
|
var lowCornerSE = CornerFill.None;
|
||||||
|
var lowCornerSW = CornerFill.None;
|
||||||
|
var lowCornerNW = CornerFill.None;
|
||||||
|
// ReSharper restore InconsistentNaming
|
||||||
|
|
||||||
|
if (n)
|
||||||
|
{
|
||||||
|
cornerNE |= CornerFill.CounterClockwise;
|
||||||
|
cornerNW |= CornerFill.Clockwise;
|
||||||
|
|
||||||
|
if (!nl)
|
||||||
|
{
|
||||||
|
lowCornerNE |= CornerFill.CounterClockwise;
|
||||||
|
lowCornerNW |= CornerFill.Clockwise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ne)
|
||||||
|
{
|
||||||
|
cornerNE |= CornerFill.Diagonal;
|
||||||
|
|
||||||
|
if (!nel && (nl || el || n && e))
|
||||||
|
{
|
||||||
|
lowCornerNE |= CornerFill.Diagonal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e)
|
||||||
|
{
|
||||||
|
cornerNE |= CornerFill.Clockwise;
|
||||||
|
cornerSE |= CornerFill.CounterClockwise;
|
||||||
|
|
||||||
|
if (!el)
|
||||||
|
{
|
||||||
|
lowCornerNE |= CornerFill.Clockwise;
|
||||||
|
lowCornerSE |= CornerFill.CounterClockwise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (se)
|
||||||
|
{
|
||||||
|
cornerSE |= CornerFill.Diagonal;
|
||||||
|
|
||||||
|
if (!sel && (sl || el || s && e))
|
||||||
|
{
|
||||||
|
lowCornerSE |= CornerFill.Diagonal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s)
|
||||||
|
{
|
||||||
|
cornerSE |= CornerFill.Clockwise;
|
||||||
|
cornerSW |= CornerFill.CounterClockwise;
|
||||||
|
|
||||||
|
if (!sl)
|
||||||
|
{
|
||||||
|
lowCornerSE |= CornerFill.Clockwise;
|
||||||
|
lowCornerSW |= CornerFill.CounterClockwise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sw)
|
||||||
|
{
|
||||||
|
cornerSW |= CornerFill.Diagonal;
|
||||||
|
|
||||||
|
if (!swl && (sl || wl || s && w))
|
||||||
|
{
|
||||||
|
lowCornerSW |= CornerFill.Diagonal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (w)
|
||||||
|
{
|
||||||
|
cornerSW |= CornerFill.Clockwise;
|
||||||
|
cornerNW |= CornerFill.CounterClockwise;
|
||||||
|
|
||||||
|
if (!wl)
|
||||||
|
{
|
||||||
|
lowCornerSW |= CornerFill.Clockwise;
|
||||||
|
lowCornerNW |= CornerFill.CounterClockwise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nw)
|
||||||
|
{
|
||||||
|
cornerNW |= CornerFill.Diagonal;
|
||||||
|
|
||||||
|
if (!nwl && (nl || wl || n && w))
|
||||||
|
{
|
||||||
|
lowCornerNW |= CornerFill.Diagonal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sprite.LayerSetState(CornerLayers.NE, $"{StateBase}{(int) cornerNE}");
|
||||||
|
Sprite.LayerSetState(CornerLayers.SE, $"{StateBase}{(int) cornerSE}");
|
||||||
|
Sprite.LayerSetState(CornerLayers.SW, $"{StateBase}{(int) cornerSW}");
|
||||||
|
Sprite.LayerSetState(CornerLayers.NW, $"{StateBase}{(int) cornerNW}");
|
||||||
|
|
||||||
|
_overlaySprite.LayerSetState(OverCornerLayers.NE, $"{StateBase}over_{(int) lowCornerNE}");
|
||||||
|
_overlaySprite.LayerSetState(OverCornerLayers.SE, $"{StateBase}over_{(int) lowCornerSE}");
|
||||||
|
_overlaySprite.LayerSetState(OverCornerLayers.SW, $"{StateBase}over_{(int) lowCornerSW}");
|
||||||
|
_overlaySprite.LayerSetState(OverCornerLayers.NW, $"{StateBase}over_{(int) lowCornerNW}");
|
||||||
|
|
||||||
|
LastCornerNE = cornerNE;
|
||||||
|
LastCornerSE = cornerSE;
|
||||||
|
LastCornerSW = cornerSW;
|
||||||
|
LastCornerNW = cornerNW;
|
||||||
|
|
||||||
|
foreach (var entity in SnapGrid.GetLocal())
|
||||||
|
{
|
||||||
|
if (entity.TryGetComponent(out WindowComponent window))
|
||||||
|
{
|
||||||
|
window.UpdateSprite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Pure]
|
||||||
|
private (bool connected, bool lowWall) MatchingWall(IEnumerable<IEntity> candidates)
|
||||||
|
{
|
||||||
|
foreach (var entity in candidates)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out IconSmoothComponent other))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other.SmoothKey == SmoothKey)
|
||||||
|
{
|
||||||
|
return (true, other is LowWallComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
|
private enum OverCornerLayers
|
||||||
|
{
|
||||||
|
SE,
|
||||||
|
NE,
|
||||||
|
NW,
|
||||||
|
SW,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Shared.Preferences.Appearance;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Client.ResourceManagement;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.GameObjects.Components.Renderable;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using static Content.Shared.GameObjects.Components.SharedMagicMirrorComponent;
|
||||||
|
using static Content.Client.StaticIoC;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class MagicMirrorBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
private MagicMirrorWindow _window;
|
||||||
|
|
||||||
|
public MagicMirrorBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_window = new MagicMirrorWindow(this);
|
||||||
|
_window.OnClose += Close;
|
||||||
|
_window.Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||||
|
{
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case MagicMirrorInitialDataMessage initialData:
|
||||||
|
_window.SetInitialData(initialData);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void HairSelected(string name, bool isFacialHair)
|
||||||
|
{
|
||||||
|
SendMessage(new HairSelectedMessage(name, isFacialHair));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void HairColorSelected(Color color, bool isFacialHair)
|
||||||
|
{
|
||||||
|
SendMessage(new HairColorSelectedMessage((color.RByte, color.GByte, color.BByte),
|
||||||
|
isFacialHair));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_window.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FacialHairStylePicker : HairStylePicker
|
||||||
|
{
|
||||||
|
public override void Populate()
|
||||||
|
{
|
||||||
|
var humanFacialHairRSIPath = SharedSpriteComponent.TextureRoot / "Mob/human_facial_hair.rsi";
|
||||||
|
var humanFacialHairRSI = ResC.GetResource<RSIResource>(humanFacialHairRSIPath).RSI;
|
||||||
|
|
||||||
|
var styles = HairStyles.FacialHairStylesMap.ToList();
|
||||||
|
styles.Sort(HairStyles.FacialHairStyleComparer);
|
||||||
|
|
||||||
|
foreach (var (styleName, styleState) in HairStyles.FacialHairStylesMap)
|
||||||
|
{
|
||||||
|
Items.AddItem(styleName, humanFacialHairRSI[styleState].Frame0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HairStylePicker : Control
|
||||||
|
{
|
||||||
|
public event Action<Color> OnHairColorPicked;
|
||||||
|
public event Action<string> OnHairStylePicked;
|
||||||
|
|
||||||
|
protected readonly ItemList Items;
|
||||||
|
|
||||||
|
private readonly ColorSlider _colorSliderR;
|
||||||
|
private readonly ColorSlider _colorSliderG;
|
||||||
|
private readonly ColorSlider _colorSliderB;
|
||||||
|
|
||||||
|
private Color _lastColor;
|
||||||
|
|
||||||
|
public void SetData(Color color, string styleName)
|
||||||
|
{
|
||||||
|
_lastColor = color;
|
||||||
|
|
||||||
|
_colorSliderR.ColorValue = color.RByte;
|
||||||
|
_colorSliderG.ColorValue = color.GByte;
|
||||||
|
_colorSliderB.ColorValue = color.BByte;
|
||||||
|
|
||||||
|
foreach (var item in Items)
|
||||||
|
{
|
||||||
|
item.Selected = item.Text == styleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateStylePickerColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateStylePickerColor()
|
||||||
|
{
|
||||||
|
foreach (var item in Items)
|
||||||
|
{
|
||||||
|
item.IconModulate = _lastColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HairStylePicker()
|
||||||
|
{
|
||||||
|
var vBox = new VBoxContainer();
|
||||||
|
AddChild(vBox);
|
||||||
|
|
||||||
|
vBox.AddChild(_colorSliderR = new ColorSlider(NanoStyle.StyleClassSliderRed));
|
||||||
|
vBox.AddChild(_colorSliderG = new ColorSlider(NanoStyle.StyleClassSliderGreen));
|
||||||
|
vBox.AddChild(_colorSliderB = new ColorSlider(NanoStyle.StyleClassSliderBlue));
|
||||||
|
|
||||||
|
Action colorValueChanged = ColorValueChanged;
|
||||||
|
_colorSliderR.OnValueChanged += colorValueChanged;
|
||||||
|
_colorSliderG.OnValueChanged += colorValueChanged;
|
||||||
|
_colorSliderB.OnValueChanged += colorValueChanged;
|
||||||
|
|
||||||
|
Items = new ItemList
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||||
|
CustomMinimumSize = (300, 250)
|
||||||
|
};
|
||||||
|
vBox.AddChild(Items);
|
||||||
|
Items.OnItemSelected += ItemSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ColorValueChanged()
|
||||||
|
{
|
||||||
|
var newColor = new Color(
|
||||||
|
_colorSliderR.ColorValue,
|
||||||
|
_colorSliderG.ColorValue,
|
||||||
|
_colorSliderB.ColorValue
|
||||||
|
);
|
||||||
|
|
||||||
|
OnHairColorPicked?.Invoke(newColor);
|
||||||
|
_lastColor = newColor;
|
||||||
|
UpdateStylePickerColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Populate()
|
||||||
|
{
|
||||||
|
var humanHairRSIPath = SharedSpriteComponent.TextureRoot / "Mob/human_hair.rsi";
|
||||||
|
var humanHairRSI = ResC.GetResource<RSIResource>(humanHairRSIPath).RSI;
|
||||||
|
|
||||||
|
var styles = HairStyles.HairStylesMap.ToList();
|
||||||
|
styles.Sort(HairStyles.HairStyleComparer);
|
||||||
|
|
||||||
|
foreach (var (styleName, styleState) in styles)
|
||||||
|
{
|
||||||
|
Items.AddItem(styleName, humanHairRSI[styleState].Frame0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ItemSelected(ItemList.ItemListSelectedEventArgs args)
|
||||||
|
{
|
||||||
|
OnHairStylePicked?.Invoke(Items[args.ItemIndex].Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ColorSlider : Control
|
||||||
|
{
|
||||||
|
private readonly Slider _slider;
|
||||||
|
private readonly LineEdit _textBox;
|
||||||
|
private byte _colorValue;
|
||||||
|
private bool _ignoreEvents;
|
||||||
|
|
||||||
|
public event Action OnValueChanged;
|
||||||
|
|
||||||
|
public byte ColorValue
|
||||||
|
{
|
||||||
|
get => _colorValue;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_ignoreEvents = true;
|
||||||
|
_colorValue = value;
|
||||||
|
_slider.Value = value;
|
||||||
|
_textBox.Text = value.ToString();
|
||||||
|
_ignoreEvents = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorSlider(string styleClass)
|
||||||
|
{
|
||||||
|
_slider = new Slider
|
||||||
|
{
|
||||||
|
StyleClasses = {styleClass},
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||||
|
MaxValue = byte.MaxValue
|
||||||
|
};
|
||||||
|
_textBox = new LineEdit
|
||||||
|
{
|
||||||
|
CustomMinimumSize = (50, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
AddChild(new HBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
_slider,
|
||||||
|
_textBox
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_slider.OnValueChanged += _ =>
|
||||||
|
{
|
||||||
|
if (_ignoreEvents)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_colorValue = (byte) _slider.Value;
|
||||||
|
_textBox.Text = _colorValue.ToString();
|
||||||
|
|
||||||
|
OnValueChanged?.Invoke();
|
||||||
|
};
|
||||||
|
|
||||||
|
_textBox.OnTextChanged += ev =>
|
||||||
|
{
|
||||||
|
if (_ignoreEvents)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (int.TryParse(ev.Text, out var result))
|
||||||
|
{
|
||||||
|
result = result.Clamp(0, byte.MaxValue);
|
||||||
|
|
||||||
|
_ignoreEvents = true;
|
||||||
|
_colorValue = (byte) result;
|
||||||
|
_slider.Value = result;
|
||||||
|
_ignoreEvents = false;
|
||||||
|
|
||||||
|
OnValueChanged?.Invoke();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MagicMirrorWindow : SS14Window
|
||||||
|
{
|
||||||
|
private readonly HairStylePicker _hairStylePicker;
|
||||||
|
private readonly FacialHairStylePicker _facialHairStylePicker;
|
||||||
|
|
||||||
|
protected override Vector2? CustomSize => (500, 360);
|
||||||
|
|
||||||
|
public MagicMirrorWindow(MagicMirrorBoundUserInterface owner)
|
||||||
|
{
|
||||||
|
Title = Loc.GetString("Magic Mirror");
|
||||||
|
|
||||||
|
_hairStylePicker = new HairStylePicker {SizeFlagsHorizontal = SizeFlags.FillExpand};
|
||||||
|
_hairStylePicker.Populate();
|
||||||
|
_hairStylePicker.OnHairStylePicked += newStyle => owner.HairSelected(newStyle, false);
|
||||||
|
_hairStylePicker.OnHairColorPicked += newColor => owner.HairColorSelected(newColor, false);
|
||||||
|
|
||||||
|
_facialHairStylePicker = new FacialHairStylePicker {SizeFlagsHorizontal = SizeFlags.FillExpand};
|
||||||
|
_facialHairStylePicker.Populate();
|
||||||
|
_facialHairStylePicker.OnHairStylePicked += newStyle => owner.HairSelected(newStyle, true);
|
||||||
|
_facialHairStylePicker.OnHairColorPicked += newColor => owner.HairColorSelected(newColor, true);
|
||||||
|
|
||||||
|
Contents.AddChild(new HBoxContainer
|
||||||
|
{
|
||||||
|
SeparationOverride = 8,
|
||||||
|
Children = {_hairStylePicker, _facialHairStylePicker}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_hairStylePicker.Dispose();
|
||||||
|
_facialHairStylePicker.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetInitialData(MagicMirrorInitialDataMessage initialData)
|
||||||
|
{
|
||||||
|
_facialHairStylePicker.SetData(initialData.FacialHairColor, initialData.FacialHairName);
|
||||||
|
_hairStylePicker.SetData(initialData.HairColor, initialData.HairName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||||
|
{
|
||||||
|
public class MedicalScannerBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
public MedicalScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private MedicalScannerWindow _window;
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
_window = new MedicalScannerWindow
|
||||||
|
{
|
||||||
|
Title = Owner.Owner.Name,
|
||||||
|
};
|
||||||
|
_window.OnClose += Close;
|
||||||
|
_window.OpenCentered();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
_window.Populate((MedicalScannerBoundUserInterfaceState) state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
||||||
|
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent.MedicalScannerStatus;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||||
|
{
|
||||||
|
public class MedicalScannerVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
if (!component.TryGetData(MedicalScannerVisuals.Status, out MedicalScannerStatus status)) return;
|
||||||
|
sprite.LayerSetState(MedicalScannerVisualLayers.Machine, StatusToMachineStateId(status));
|
||||||
|
sprite.LayerSetState(MedicalScannerVisualLayers.Terminal, StatusToTerminalStateId(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string StatusToMachineStateId(MedicalScannerStatus status)
|
||||||
|
{
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case Off: return "scanner_off";
|
||||||
|
case Open: return "scanner_open";
|
||||||
|
case Red: return "scanner_red";
|
||||||
|
case Death: return "scanner_death";
|
||||||
|
case Green: return "scanner_green";
|
||||||
|
case Yellow: return "scanner_yellow";
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(status), status, "unknown MedicalScannerStatus");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string StatusToTerminalStateId(MedicalScannerStatus status)
|
||||||
|
{
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case Off: return "scanner_terminal_off";
|
||||||
|
case Open: return "scanner_terminal_blue";
|
||||||
|
case Red: return "scanner_terminal_red";
|
||||||
|
case Death: return "scanner_terminal_dead";
|
||||||
|
case Green: return "scanner_terminal_green";
|
||||||
|
case Yellow: return "scanner_terminal_blue";
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(status), status, "unknown MedicalScannerStatus");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MedicalScannerVisualLayers
|
||||||
|
{
|
||||||
|
Machine,
|
||||||
|
Terminal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Only unused on .NET Core due to KeyValuePair.Deconstruct
|
||||||
|
// ReSharper disable once RedundantUsingDirective
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using System.Text;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||||
|
{
|
||||||
|
public class MedicalScannerWindow : SS14Window
|
||||||
|
{
|
||||||
|
protected override Vector2? CustomSize => (485, 90);
|
||||||
|
|
||||||
|
public void Populate(MedicalScannerBoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
Contents.RemoveAllChildren();
|
||||||
|
var text = new StringBuilder();
|
||||||
|
if (state.MaxHealth == 0)
|
||||||
|
{
|
||||||
|
text.Append("No patient data.");
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
text.Append($"Patient's health: {state.CurrentHealth}/{state.MaxHealth}\n");
|
||||||
|
|
||||||
|
if (state.DamageDictionary != null)
|
||||||
|
{
|
||||||
|
foreach (var (dmgType, amount) in state.DamageDictionary)
|
||||||
|
{
|
||||||
|
text.Append($"\n{dmgType}: {amount}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Contents.AddChild(new Label(){Text = text.ToString()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Mobs
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedCameraRecoilComponent))]
|
||||||
|
public sealed class CameraRecoilComponent : SharedCameraRecoilComponent
|
||||||
|
{
|
||||||
|
// Maximum rate of magnitude restore towards 0 kick.
|
||||||
|
private const float RestoreRateMax = 2f;
|
||||||
|
|
||||||
|
// Minimum rate of magnitude restore towards 0 kick.
|
||||||
|
private const float RestoreRateMin = 1f;
|
||||||
|
|
||||||
|
// Time in seconds since the last kick that lerps RestoreRateMin and RestoreRateMax
|
||||||
|
private const float RestoreRateRamp = 0.05f;
|
||||||
|
|
||||||
|
// The maximum magnitude of the kick applied to the camera at any point.
|
||||||
|
private const float KickMagnitudeMax = 0.25f;
|
||||||
|
|
||||||
|
private Vector2 _currentKick;
|
||||||
|
private float _lastKickTime;
|
||||||
|
|
||||||
|
private EyeComponent _eye;
|
||||||
|
|
||||||
|
// Basically I needed a way to chain this effect for the attack lunge animation.
|
||||||
|
// Sorry!
|
||||||
|
public Vector2 BaseOffset { get; set; }
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_eye = Owner.GetComponent<EyeComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Kick(Vector2 recoil)
|
||||||
|
{
|
||||||
|
// Use really bad math to "dampen" kicks when we're already kicked.
|
||||||
|
var existing = _currentKick.Length;
|
||||||
|
var dampen = existing/KickMagnitudeMax;
|
||||||
|
_currentKick += recoil * (1-dampen);
|
||||||
|
if (_currentKick.Length > KickMagnitudeMax)
|
||||||
|
{
|
||||||
|
_currentKick = _currentKick.Normalized * KickMagnitudeMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastKickTime = 0;
|
||||||
|
_updateEye();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
|
||||||
|
{
|
||||||
|
base.HandleMessage(message, netChannel, component);
|
||||||
|
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case RecoilKickMessage msg:
|
||||||
|
Kick(msg.Recoil);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FrameUpdate(float frameTime)
|
||||||
|
{
|
||||||
|
var magnitude = _currentKick.Length;
|
||||||
|
if (magnitude <= 0.005f)
|
||||||
|
{
|
||||||
|
_currentKick = Vector2.Zero;
|
||||||
|
_updateEye();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continually restore camera to 0.
|
||||||
|
var normalized = _currentKick.Normalized;
|
||||||
|
var restoreRate = FloatMath.Lerp(RestoreRateMin, RestoreRateMax, Math.Min(1, _lastKickTime/RestoreRateRamp));
|
||||||
|
var restore = normalized * restoreRate * frameTime;
|
||||||
|
var (x, y) = _currentKick - restore;
|
||||||
|
if (Math.Sign(x) != Math.Sign(_currentKick.X))
|
||||||
|
{
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.Sign(y) != Math.Sign(_currentKick.Y))
|
||||||
|
{
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentKick = (x, y);
|
||||||
|
|
||||||
|
_updateEye();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _updateEye()
|
||||||
|
{
|
||||||
|
_eye.Offset = BaseOffset + _currentKick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Client.Graphics.Overlays;
|
||||||
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Graphics.Overlays;
|
||||||
|
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A character UI component which shows the current damage state of the mob (living/dead)
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedOverlayEffectsComponent))]
|
||||||
|
public sealed class ClientOverlayEffectsComponent : SharedOverlayEffectsComponent//, ICharacterUI
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An enum representing the current state being applied to the user
|
||||||
|
/// </summary>
|
||||||
|
private ScreenEffects _currentEffect = ScreenEffects.None;
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
// Required dependencies
|
||||||
|
[Dependency] private readonly IOverlayManager _overlayManager;
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Holds the screen effects that can be applied mapped ot their relevant overlay
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<ScreenEffects, Overlay> _effectsDictionary;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows calculating if we need to act due to this component being controlled by the current mob
|
||||||
|
/// </summary>
|
||||||
|
private bool CurrentlyControlled => _playerManager.LocalPlayer.ControlledEntity == Owner;
|
||||||
|
|
||||||
|
public override void OnAdd()
|
||||||
|
{
|
||||||
|
base.OnAdd();
|
||||||
|
|
||||||
|
_effectsDictionary = new Dictionary<ScreenEffects, Overlay>()
|
||||||
|
{
|
||||||
|
{ ScreenEffects.CircleMask, new CircleMaskOverlay() },
|
||||||
|
{ ScreenEffects.GradientCircleMask, new GradientCircleMask() }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
|
||||||
|
{
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case PlayerAttachedMsg _:
|
||||||
|
SetOverlay(_currentEffect);
|
||||||
|
break;
|
||||||
|
case PlayerDetachedMsg _:
|
||||||
|
RemoveOverlay();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
base.HandleComponentState(curState, nextState);
|
||||||
|
if (!(curState is OverlayEffectComponentState state) || _currentEffect == state.ScreenEffect) return;
|
||||||
|
SetOverlay(state.ScreenEffect);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetOverlay(ScreenEffects effect)
|
||||||
|
{
|
||||||
|
RemoveOverlay();
|
||||||
|
|
||||||
|
_currentEffect = effect;
|
||||||
|
|
||||||
|
ApplyOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveOverlay()
|
||||||
|
{
|
||||||
|
if (CurrentlyControlled && _currentEffect != ScreenEffects.None)
|
||||||
|
{
|
||||||
|
var appliedEffect = _effectsDictionary[_currentEffect];
|
||||||
|
_overlayManager.RemoveOverlay(appliedEffect.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentEffect = ScreenEffects.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyOverlay()
|
||||||
|
{
|
||||||
|
if (CurrentlyControlled && _currentEffect != ScreenEffects.None)
|
||||||
|
{
|
||||||
|
var overlay = _effectsDictionary[_currentEffect];
|
||||||
|
if (_overlayManager.HasOverlay(overlay.ID))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_overlayManager.AddOverlay(overlay);
|
||||||
|
Logger.InfoS("overlay", $"Changed overlay to {overlay}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Client.Utility;
|
||||||
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.ResourceManagement;
|
||||||
|
using Robust.Client.Interfaces.UserInterface;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Mobs
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class ClientStatusEffectsComponent : SharedStatusEffectsComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager;
|
||||||
|
[Dependency] private readonly IResourceCache _resourceCache;
|
||||||
|
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private StatusEffectsUI _ui;
|
||||||
|
private IDictionary<StatusEffect, string> _icons = new Dictionary<StatusEffect, string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows calculating if we need to act due to this component being controlled by the current mob
|
||||||
|
/// </summary>
|
||||||
|
private bool CurrentlyControlled => _playerManager.LocalPlayer != null && _playerManager.LocalPlayer.ControlledEntity == Owner;
|
||||||
|
|
||||||
|
protected override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
PlayerDetached();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
|
||||||
|
IComponent component = null)
|
||||||
|
{
|
||||||
|
base.HandleMessage(message, netChannel, component);
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case PlayerAttachedMsg _:
|
||||||
|
PlayerAttached();
|
||||||
|
break;
|
||||||
|
case PlayerDetachedMsg _:
|
||||||
|
PlayerDetached();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
base.HandleComponentState(curState, nextState);
|
||||||
|
if (!(curState is StatusEffectComponentState state) || _icons == state.StatusEffects) return;
|
||||||
|
_icons = state.StatusEffects;
|
||||||
|
UpdateIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayerAttached()
|
||||||
|
{
|
||||||
|
if (!CurrentlyControlled || _ui != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ui = new StatusEffectsUI();
|
||||||
|
_userInterfaceManager.StateRoot.AddChild(_ui);
|
||||||
|
UpdateIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayerDetached()
|
||||||
|
{
|
||||||
|
if (!CurrentlyControlled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ui?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateIcons()
|
||||||
|
{
|
||||||
|
if (!CurrentlyControlled || _ui == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ui.VBox.DisposeAllChildren();
|
||||||
|
|
||||||
|
foreach (var effect in _icons.OrderBy(x => (int) x.Key))
|
||||||
|
{
|
||||||
|
TextureRect newIcon = new TextureRect
|
||||||
|
{
|
||||||
|
TextureScale = (2, 2),
|
||||||
|
Texture = _resourceCache.GetTexture(effect.Value)
|
||||||
|
};
|
||||||
|
|
||||||
|
newIcon.Texture = _resourceCache.GetTexture(effect.Value);
|
||||||
|
_ui.VBox.AddChild(newIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveIcon(StatusEffect name)
|
||||||
|
{
|
||||||
|
_icons.Remove(name);
|
||||||
|
UpdateIcons();
|
||||||
|
Logger.InfoS("statuseffects", $"Removed icon {name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Mobs
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class CombatModeComponent : SharedCombatModeComponent
|
||||||
|
{
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool IsInCombatMode { get; private set; }
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public TargetingZone ActiveZone { get; private set; }
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IGameHud _gameHud;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
base.HandleComponentState(curState, nextState);
|
||||||
|
|
||||||
|
var state = (CombatModeComponentState) curState;
|
||||||
|
|
||||||
|
IsInCombatMode = state.IsInCombatMode;
|
||||||
|
ActiveZone = state.TargetingZone;
|
||||||
|
UpdateHud();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
|
||||||
|
{
|
||||||
|
base.HandleMessage(message, netChannel, component);
|
||||||
|
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case PlayerAttachedMsg _:
|
||||||
|
_gameHud.CombatPanelVisible = true;
|
||||||
|
UpdateHud();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PlayerDetachedMsg _:
|
||||||
|
_gameHud.CombatPanelVisible = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateHud()
|
||||||
|
{
|
||||||
|
_gameHud.CombatModeActive = IsInCombatMode;
|
||||||
|
_gameHud.TargetingZone = ActiveZone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.Preferences;
|
||||||
|
using Content.Shared.Preferences.Appearance;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Mobs
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class HumanoidAppearanceComponent : SharedHumanoidAppearanceComponent
|
||||||
|
{
|
||||||
|
public override HumanoidCharacterAppearance Appearance
|
||||||
|
{
|
||||||
|
get => base.Appearance;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.Appearance = value;
|
||||||
|
UpdateLooks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Sex Sex
|
||||||
|
{
|
||||||
|
get => base.Sex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.Sex = value;
|
||||||
|
UpdateLooks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Startup()
|
||||||
|
{
|
||||||
|
base.Startup();
|
||||||
|
|
||||||
|
UpdateLooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateLooks()
|
||||||
|
{
|
||||||
|
if (Appearance is null) return;
|
||||||
|
var sprite = Owner.GetComponent<SpriteComponent>();
|
||||||
|
|
||||||
|
sprite.LayerSetColor(HumanoidVisualLayers.Hair, Appearance.HairColor);
|
||||||
|
sprite.LayerSetColor(HumanoidVisualLayers.FacialHair, Appearance.FacialHairColor);
|
||||||
|
|
||||||
|
sprite.LayerSetState(HumanoidVisualLayers.Chest, Sex == Sex.Male ? "human_chest_m" : "human_chest_f");
|
||||||
|
sprite.LayerSetState(HumanoidVisualLayers.Head, Sex == Sex.Male ? "human_head_m" : "human_head_f");
|
||||||
|
|
||||||
|
sprite.LayerSetVisible(HumanoidVisualLayers.StencilMask, Sex == Sex.Female);
|
||||||
|
|
||||||
|
var hairStyle = Appearance.HairStyleName;
|
||||||
|
if (string.IsNullOrWhiteSpace(hairStyle) || !HairStyles.HairStylesMap.ContainsKey(hairStyle))
|
||||||
|
hairStyle = HairStyles.DefaultHairStyle;
|
||||||
|
sprite.LayerSetState(HumanoidVisualLayers.Hair,
|
||||||
|
HairStyles.HairStylesMap[hairStyle]);
|
||||||
|
|
||||||
|
var facialHairStyle = Appearance.FacialHairStyleName;
|
||||||
|
if (string.IsNullOrWhiteSpace(facialHairStyle) || !HairStyles.FacialHairStylesMap.ContainsKey(facialHairStyle))
|
||||||
|
facialHairStyle = HairStyles.DefaultFacialHairStyle;
|
||||||
|
sprite.LayerSetState(HumanoidVisualLayers.FacialHair,
|
||||||
|
HairStyles.FacialHairStylesMap[facialHairStyle]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Content.Client/GameObjects/Components/Mobs/ICharacterUI.cs
Normal file
21
Content.Client/GameObjects/Components/Mobs/ICharacterUI.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Content.Client.GameObjects.Components.Actor;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Mobs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An interface which is gathered to assemble the character window from multiple components
|
||||||
|
/// </summary>
|
||||||
|
public interface ICharacterUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The godot control which holds the character user interface to be included in the window
|
||||||
|
/// </summary>
|
||||||
|
Control Scene { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The order it will appear in the character UI, higher is lower
|
||||||
|
/// </summary>
|
||||||
|
UIPriority Priority { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Mobs
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class MeleeLungeComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "MeleeLunge";
|
||||||
|
|
||||||
|
private const float ResetTime = 0.3f;
|
||||||
|
private const float BaseOffset = 0.25f;
|
||||||
|
|
||||||
|
private Angle _angle;
|
||||||
|
private float _time;
|
||||||
|
|
||||||
|
public void SetData(Angle angle)
|
||||||
|
{
|
||||||
|
_angle = angle;
|
||||||
|
_time = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(float frameTime)
|
||||||
|
{
|
||||||
|
_time += frameTime;
|
||||||
|
|
||||||
|
var offset = Vector2.Zero;
|
||||||
|
var deleteSelf = false;
|
||||||
|
|
||||||
|
if (_time > ResetTime)
|
||||||
|
{
|
||||||
|
deleteSelf = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
offset = _angle.RotateVec((BaseOffset, 0));
|
||||||
|
offset *= (ResetTime - _time) / ResetTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out CameraRecoilComponent recoilComponent))
|
||||||
|
{
|
||||||
|
recoilComponent.BaseOffset = offset;
|
||||||
|
}
|
||||||
|
else if (Owner.TryGetComponent(out EyeComponent eyeComponent))
|
||||||
|
{
|
||||||
|
eyeComponent.Offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out ISpriteComponent spriteComponent))
|
||||||
|
{
|
||||||
|
// We have to account for rotation so the offset still checks out.
|
||||||
|
// SpriteComponent.Offset is applied before transform rotation (as expected).
|
||||||
|
var worldRotation = Owner.Transform.WorldRotation;
|
||||||
|
spriteComponent.Offset = new Angle(-worldRotation).RotateVec(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteSelf)
|
||||||
|
{
|
||||||
|
Owner.RemoveComponent<MeleeLungeComponent>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Mobs
|
||||||
|
{
|
||||||
|
public class SpeciesVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
if (component.TryGetData<SharedSpeciesComponent.MobState>(SharedSpeciesComponent.MobVisuals.RotationState, out var state))
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case SharedSpeciesComponent.MobState.Stand:
|
||||||
|
sprite.Rotation = 0;
|
||||||
|
break;
|
||||||
|
case SharedSpeciesComponent.MobState.Down:
|
||||||
|
sprite.Rotation = Angle.FromDegrees(90);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Movement
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class HandTeleporterVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
if (!component.TryGetData(TeleporterVisuals.VisualState, out TeleporterVisualState state))
|
||||||
|
{
|
||||||
|
state = TeleporterVisualState.Ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case TeleporterVisualState.Charging:
|
||||||
|
sprite.LayerSetState(0, "charging");
|
||||||
|
break;
|
||||||
|
case TeleporterVisualState.Ready:
|
||||||
|
sprite.LayerSetState(0, "ready");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Movement
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class PortalVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
public override void InitializeEntity(IEntity entity)
|
||||||
|
{
|
||||||
|
base.InitializeEntity(entity);
|
||||||
|
|
||||||
|
var sprite = entity.GetComponent<ISpriteComponent>();
|
||||||
|
|
||||||
|
sprite.LayerMapSet(Layers.Portal, sprite.AddLayerState("portal-pending"));
|
||||||
|
sprite.LayerSetShader(Layers.Portal, "unshaded");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
if (component.TryGetData<PortalState>(PortalVisuals.State, out var state))
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case PortalState.Pending:
|
||||||
|
sprite.LayerSetState(Layers.Portal, "portal-pending");
|
||||||
|
break;
|
||||||
|
// TODO: Spritework here?
|
||||||
|
case PortalState.UnableToTeleport:
|
||||||
|
sprite.LayerSetState(Layers.Portal, "portal-unconnected");
|
||||||
|
break;
|
||||||
|
case PortalState.RecentlyTeleported:
|
||||||
|
sprite.LayerSetState(Layers.Portal, "portal-unconnected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprite.LayerSetState(Layers.Portal, "portal-pending");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Layers
|
||||||
|
{
|
||||||
|
Portal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.GameObjects.Components.Nutrition;
|
||||||
|
using Content.Shared.Utility;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Nutrition
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class DrinkFoodContainerVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
private string _baseState;
|
||||||
|
private int _steps;
|
||||||
|
private DrinkFoodContainerVisualMode _mode;
|
||||||
|
|
||||||
|
public override void LoadData(YamlMappingNode node)
|
||||||
|
{
|
||||||
|
base.LoadData(node);
|
||||||
|
|
||||||
|
_baseState = node.GetNode("base_state").AsString();
|
||||||
|
_steps = node.GetNode("steps").AsInt();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_mode = node.GetNode("mode").AsEnum<DrinkFoodContainerVisualMode>();
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
_mode = DrinkFoodContainerVisualMode.Rounded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
|
||||||
|
if (!component.TryGetData<int>(DrinkFoodContainerVisuals.Current, out var current))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!component.TryGetData<int>(DrinkFoodContainerVisuals.Capacity, out var capacity))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int step;
|
||||||
|
|
||||||
|
switch (_mode)
|
||||||
|
{
|
||||||
|
case DrinkFoodContainerVisualMode.Discrete:
|
||||||
|
step = Math.Min(_steps - 1, current);
|
||||||
|
break;
|
||||||
|
case DrinkFoodContainerVisualMode.Rounded:
|
||||||
|
step = ContentHelpers.RoundToLevels(current, capacity, _steps);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NullReferenceException();
|
||||||
|
}
|
||||||
|
|
||||||
|
sprite.LayerSetState(0, $"{_baseState}-{step}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Nutrition;
|
||||||
|
using Content.Shared.Utility;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Nutrition
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class DrinkFoodVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
private int _steps;
|
||||||
|
|
||||||
|
public override void LoadData(YamlMappingNode node)
|
||||||
|
{
|
||||||
|
base.LoadData(node);
|
||||||
|
|
||||||
|
_steps = node.GetNode("steps").AsInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
if (!component.TryGetData<int>(SharedFoodComponent.FoodVisuals.MaxUses, out var maxUses))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.TryGetData<int>(SharedFoodComponent.FoodVisuals.Visual, out var usesLeft))
|
||||||
|
{
|
||||||
|
var step = ContentHelpers.RoundToLevels(usesLeft, maxUses, _steps);
|
||||||
|
sprite.LayerSetState(0, $"icon-{step}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprite.LayerSetState(0, "icon-0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Shared.GameObjects.Components.Power;
|
||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Client.Graphics.Drawing;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Power
|
||||||
|
{
|
||||||
|
public class ApcBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
private ApcWindow _window;
|
||||||
|
private BaseButton _breakerButton;
|
||||||
|
private Label _externalPowerStateLabel;
|
||||||
|
private ProgressBar _chargeBar;
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_window = new ApcWindow();
|
||||||
|
_window.OnClose += Close;
|
||||||
|
_window.OpenCenteredMinSize();
|
||||||
|
|
||||||
|
_breakerButton = _window.BreakerButton;
|
||||||
|
_breakerButton.OnPressed += _ => SendMessage(new ApcToggleMainBreakerMessage());
|
||||||
|
|
||||||
|
_externalPowerStateLabel = _window.ExternalPowerStateLabel;
|
||||||
|
_chargeBar = _window.ChargeBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApcBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
var castState = (ApcBoundInterfaceState) state;
|
||||||
|
|
||||||
|
_breakerButton.Pressed = castState.MainBreaker;
|
||||||
|
switch (castState.ApcExternalPower)
|
||||||
|
{
|
||||||
|
case ApcExternalPowerState.None:
|
||||||
|
_externalPowerStateLabel.Text = "None";
|
||||||
|
_externalPowerStateLabel.SetOnlyStyleClass(NanoStyle.StyleClassPowerStateNone);
|
||||||
|
break;
|
||||||
|
case ApcExternalPowerState.Low:
|
||||||
|
_externalPowerStateLabel.Text = "Low";
|
||||||
|
_externalPowerStateLabel.SetOnlyStyleClass(NanoStyle.StyleClassPowerStateLow);
|
||||||
|
break;
|
||||||
|
case ApcExternalPowerState.Good:
|
||||||
|
_externalPowerStateLabel.Text = "Good";
|
||||||
|
_externalPowerStateLabel.SetOnlyStyleClass(NanoStyle.StyleClassPowerStateGood);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
_chargeBar.Value = castState.Charge;
|
||||||
|
UpdateChargeBarColor(castState.Charge);
|
||||||
|
var chargePercentage = (castState.Charge / _chargeBar.MaxValue) * 100.0f;
|
||||||
|
_window.ChargePercentage.Text = " " + chargePercentage.ToString("0.00") + "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateChargeBarColor(float charge)
|
||||||
|
{
|
||||||
|
var normalizedCharge = charge / _chargeBar.MaxValue;
|
||||||
|
|
||||||
|
const float leftHue = 0.0f; // Red
|
||||||
|
const float middleHue = 0.066f; // Orange
|
||||||
|
const float rightHue = 0.33f; // Green
|
||||||
|
const float saturation = 1.0f; // Uniform saturation
|
||||||
|
const float value = 0.8f; // Uniform value / brightness
|
||||||
|
const float alpha = 1.0f; // Uniform alpha
|
||||||
|
|
||||||
|
// These should add up to 1.0 or your transition won't be smooth
|
||||||
|
const float leftSideSize = 0.5f; // Fraction of _chargeBar lerped from leftHue to middleHue
|
||||||
|
const float rightSideSize = 0.5f; // Fraction of _chargeBar lerped from middleHue to rightHue
|
||||||
|
|
||||||
|
float finalHue;
|
||||||
|
if (normalizedCharge <= leftSideSize)
|
||||||
|
{
|
||||||
|
normalizedCharge /= leftSideSize; // Adjust range to 0.0 to 1.0
|
||||||
|
finalHue = FloatMath.Lerp(leftHue, middleHue, normalizedCharge);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
normalizedCharge = (normalizedCharge - leftSideSize) / rightSideSize; // Adjust range to 0.0 to 1.0.
|
||||||
|
finalHue = FloatMath.Lerp(middleHue, rightHue, normalizedCharge);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if null first to avoid repeatedly creating this.
|
||||||
|
if (_chargeBar.ForegroundStyleBoxOverride == null)
|
||||||
|
{
|
||||||
|
_chargeBar.ForegroundStyleBoxOverride = new StyleBoxFlat();
|
||||||
|
}
|
||||||
|
|
||||||
|
var foregroundStyleBoxOverride = (StyleBoxFlat) _chargeBar.ForegroundStyleBoxOverride;
|
||||||
|
foregroundStyleBoxOverride.BackgroundColor =
|
||||||
|
Color.FromHsv(new Vector4(finalHue, saturation, value, alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_window.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ApcWindow : SS14Window
|
||||||
|
{
|
||||||
|
public Button BreakerButton { get; set; }
|
||||||
|
public Label ExternalPowerStateLabel { get; set; }
|
||||||
|
public ProgressBar ChargeBar { get; set; }
|
||||||
|
public Label ChargePercentage { get; set; }
|
||||||
|
|
||||||
|
public ApcWindow()
|
||||||
|
{
|
||||||
|
Title = "APC";
|
||||||
|
var rows = new VBoxContainer();
|
||||||
|
|
||||||
|
var statusHeader = new Label {Text = "Power Status: "};
|
||||||
|
rows.AddChild(statusHeader);
|
||||||
|
|
||||||
|
var breaker = new HBoxContainer();
|
||||||
|
var breakerLabel = new Label {Text = "Main Breaker: "};
|
||||||
|
BreakerButton = new CheckButton {Text = "Toggle"};
|
||||||
|
breaker.AddChild(breakerLabel);
|
||||||
|
breaker.AddChild(BreakerButton);
|
||||||
|
rows.AddChild(breaker);
|
||||||
|
|
||||||
|
var externalStatus = new HBoxContainer();
|
||||||
|
var externalStatusLabel = new Label {Text = "External Power: "};
|
||||||
|
ExternalPowerStateLabel = new Label {Text = "Good"};
|
||||||
|
ExternalPowerStateLabel.SetOnlyStyleClass(NanoStyle.StyleClassPowerStateGood);
|
||||||
|
externalStatus.AddChild(externalStatusLabel);
|
||||||
|
externalStatus.AddChild(ExternalPowerStateLabel);
|
||||||
|
rows.AddChild(externalStatus);
|
||||||
|
|
||||||
|
var charge = new HBoxContainer();
|
||||||
|
var chargeLabel = new Label {Text = "Charge:"};
|
||||||
|
ChargeBar = new ProgressBar
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
MinValue = 0.0f,
|
||||||
|
MaxValue = 1.0f,
|
||||||
|
Page = 0.0f,
|
||||||
|
Value = 0.5f
|
||||||
|
};
|
||||||
|
ChargePercentage = new Label();
|
||||||
|
charge.AddChild(chargeLabel);
|
||||||
|
charge.AddChild(ChargeBar);
|
||||||
|
charge.AddChild(ChargePercentage);
|
||||||
|
rows.AddChild(charge);
|
||||||
|
|
||||||
|
Contents.AddChild(rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Power;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Power
|
||||||
|
{
|
||||||
|
public class ApcVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
public override void InitializeEntity(IEntity entity)
|
||||||
|
{
|
||||||
|
base.InitializeEntity(entity);
|
||||||
|
|
||||||
|
var sprite = entity.GetComponent<ISpriteComponent>();
|
||||||
|
|
||||||
|
sprite.LayerMapSet(Layers.ChargeState, sprite.AddLayerState("apco3-0"));
|
||||||
|
sprite.LayerSetShader(Layers.ChargeState, "unshaded");
|
||||||
|
|
||||||
|
sprite.LayerMapSet(Layers.Lock, sprite.AddLayerState("apcox-0"));
|
||||||
|
sprite.LayerSetShader(Layers.Lock, "unshaded");
|
||||||
|
|
||||||
|
sprite.LayerMapSet(Layers.Equipment, sprite.AddLayerState("apco0-3"));
|
||||||
|
sprite.LayerSetShader(Layers.Equipment, "unshaded");
|
||||||
|
|
||||||
|
sprite.LayerMapSet(Layers.Lighting, sprite.AddLayerState("apco1-3"));
|
||||||
|
sprite.LayerSetShader(Layers.Lighting, "unshaded");
|
||||||
|
|
||||||
|
sprite.LayerMapSet(Layers.Environment, sprite.AddLayerState("apco2-3"));
|
||||||
|
sprite.LayerSetShader(Layers.Environment, "unshaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
if (component.TryGetData<ApcChargeState>(ApcVisuals.ChargeState, out var state))
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ApcChargeState.Lack:
|
||||||
|
sprite.LayerSetState(Layers.ChargeState, "apco3-0");
|
||||||
|
break;
|
||||||
|
case ApcChargeState.Charging:
|
||||||
|
sprite.LayerSetState(Layers.ChargeState, "apco3-1");
|
||||||
|
break;
|
||||||
|
case ApcChargeState.Full:
|
||||||
|
sprite.LayerSetState(Layers.ChargeState, "apco3-2");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprite.LayerSetState(Layers.ChargeState, "apco3-0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Layers
|
||||||
|
{
|
||||||
|
ChargeState,
|
||||||
|
Lock,
|
||||||
|
Equipment,
|
||||||
|
Lighting,
|
||||||
|
Environment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Power;
|
||||||
|
using Content.Shared.Utility;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Power
|
||||||
|
{
|
||||||
|
public class PowerCellVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
private string _prefix;
|
||||||
|
|
||||||
|
public override void LoadData(YamlMappingNode node)
|
||||||
|
{
|
||||||
|
base.LoadData(node);
|
||||||
|
|
||||||
|
_prefix = node.GetNode("prefix").AsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void InitializeEntity(IEntity entity)
|
||||||
|
{
|
||||||
|
base.InitializeEntity(entity);
|
||||||
|
|
||||||
|
var sprite = entity.GetComponent<ISpriteComponent>();
|
||||||
|
|
||||||
|
sprite.LayerMapSet(Layers.Charge, sprite.AddLayerState($"{_prefix}_100"));
|
||||||
|
sprite.LayerSetShader(Layers.Charge, "unshaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
if (component.TryGetData(PowerCellVisuals.ChargeLevel, out float fraction))
|
||||||
|
{
|
||||||
|
sprite.LayerSetState(Layers.Charge, $"{_prefix}_{ContentHelpers.RoundToLevels(fraction, 1, 5) * 25}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Layers
|
||||||
|
{
|
||||||
|
Charge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Power;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Power
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class PowerChargerVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
public override void InitializeEntity(IEntity entity)
|
||||||
|
{
|
||||||
|
base.InitializeEntity(entity);
|
||||||
|
|
||||||
|
var sprite = entity.GetComponent<ISpriteComponent>();
|
||||||
|
|
||||||
|
// Base item
|
||||||
|
sprite.LayerMapSet(Layers.Base, sprite.AddLayerState("empty"));
|
||||||
|
|
||||||
|
// Light
|
||||||
|
sprite.LayerMapSet(Layers.Light, sprite.AddLayerState("light-off"));
|
||||||
|
sprite.LayerSetShader(Layers.Light, "unshaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
|
||||||
|
// Update base item
|
||||||
|
if (component.TryGetData(CellVisual.Occupied, out bool occupied))
|
||||||
|
{
|
||||||
|
// TODO: don't throw if it doesn't have a full state
|
||||||
|
sprite.LayerSetState(Layers.Base, occupied ? "full" : "empty");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprite.LayerSetState(Layers.Base, "empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update lighting
|
||||||
|
if (component.TryGetData(CellVisual.Light, out CellChargerStatus status))
|
||||||
|
{
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case CellChargerStatus.Off:
|
||||||
|
sprite.LayerSetState(Layers.Light, "light-off");
|
||||||
|
break;
|
||||||
|
case CellChargerStatus.Empty:
|
||||||
|
sprite.LayerSetState(Layers.Light, "light-empty");
|
||||||
|
break;
|
||||||
|
case CellChargerStatus.Charging:
|
||||||
|
sprite.LayerSetState(Layers.Light, "light-charging");
|
||||||
|
break;
|
||||||
|
case CellChargerStatus.Charged:
|
||||||
|
sprite.LayerSetState(Layers.Light, "light-charged");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sprite.LayerSetState(Layers.Light, "light-off");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprite.LayerSetState(Layers.Light, "light-off");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Layers
|
||||||
|
{
|
||||||
|
Base,
|
||||||
|
Light,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Power;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Power
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class PowerDebugTool : SharedPowerDebugTool
|
||||||
|
{
|
||||||
|
SS14Window LastWindow;
|
||||||
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
|
||||||
|
{
|
||||||
|
base.HandleMessage(message, netChannel, component);
|
||||||
|
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case OpenDataWindowMsg msg:
|
||||||
|
if (LastWindow != null && !LastWindow.Disposed)
|
||||||
|
{
|
||||||
|
LastWindow.Dispose();
|
||||||
|
}
|
||||||
|
LastWindow = new SS14Window()
|
||||||
|
{
|
||||||
|
Title = "Power Debug Tool",
|
||||||
|
};
|
||||||
|
LastWindow.Contents.AddChild(new Label() { Text = msg.Data });
|
||||||
|
LastWindow.Open();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Power;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Power
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class PowerDeviceVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
var powered = component.TryGetData(PowerDeviceVisuals.Powered, out bool poweredVar) && poweredVar;
|
||||||
|
sprite.LayerSetVisible(PowerDeviceVisualLayers.Powered, powered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PowerDeviceVisualLayers
|
||||||
|
{
|
||||||
|
Powered
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Power;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Power
|
||||||
|
{
|
||||||
|
public class SmesVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
public override void InitializeEntity(IEntity entity)
|
||||||
|
{
|
||||||
|
base.InitializeEntity(entity);
|
||||||
|
|
||||||
|
var sprite = entity.GetComponent<ISpriteComponent>();
|
||||||
|
|
||||||
|
sprite.LayerMapSet(Layers.Input, sprite.AddLayerState("smes-oc0"));
|
||||||
|
sprite.LayerSetShader(Layers.Input, "unshaded");
|
||||||
|
sprite.LayerMapSet(Layers.Charge, sprite.AddLayerState("smes-og1"));
|
||||||
|
sprite.LayerSetShader(Layers.Charge, "unshaded");
|
||||||
|
sprite.LayerSetVisible(Layers.Charge, false);
|
||||||
|
sprite.LayerMapSet(Layers.Output, sprite.AddLayerState("smes-op0"));
|
||||||
|
sprite.LayerSetShader(Layers.Output, "unshaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
if (!component.TryGetData<int>(SmesVisuals.LastChargeLevel, out var level) || level == 0)
|
||||||
|
{
|
||||||
|
sprite.LayerSetVisible(Layers.Charge, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprite.LayerSetVisible(Layers.Charge, true);
|
||||||
|
sprite.LayerSetState(Layers.Charge, $"smes-og{level}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.TryGetData<ChargeState>(SmesVisuals.LastChargeState, out var state))
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ChargeState.Still:
|
||||||
|
sprite.LayerSetState(Layers.Input, "smes-oc0");
|
||||||
|
sprite.LayerSetState(Layers.Output, "smes-op1");
|
||||||
|
break;
|
||||||
|
case ChargeState.Charging:
|
||||||
|
sprite.LayerSetState(Layers.Input, "smes-oc1");
|
||||||
|
sprite.LayerSetState(Layers.Output, "smes-op1");
|
||||||
|
break;
|
||||||
|
case ChargeState.Discharging:
|
||||||
|
sprite.LayerSetState(Layers.Input, "smes-oc0");
|
||||||
|
sprite.LayerSetState(Layers.Output, "smes-op2");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprite.LayerSetState(Layers.Input, "smes-oc0");
|
||||||
|
sprite.LayerSetState(Layers.Output, "smes-op1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Layers
|
||||||
|
{
|
||||||
|
Input,
|
||||||
|
Charge,
|
||||||
|
Output,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Client.Research;
|
||||||
|
using Content.Shared.GameObjects.Components.Research;
|
||||||
|
using Content.Shared.Research;
|
||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Research
|
||||||
|
{
|
||||||
|
public class LatheBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0649
|
||||||
|
[Dependency]
|
||||||
|
private IPrototypeManager _prototypeManager;
|
||||||
|
#pragma warning restore
|
||||||
|
[ViewVariables]
|
||||||
|
private LatheMenu menu;
|
||||||
|
[ViewVariables]
|
||||||
|
private LatheQueueMenu queueMenu;
|
||||||
|
|
||||||
|
public MaterialStorageComponent Storage { get; private set; }
|
||||||
|
public SharedLatheComponent Lathe { get; private set; }
|
||||||
|
public SharedLatheDatabaseComponent Database { get; private set; }
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public Queue<LatheRecipePrototype> QueuedRecipes => _queuedRecipes;
|
||||||
|
private Queue<LatheRecipePrototype> _queuedRecipes = new Queue<LatheRecipePrototype>();
|
||||||
|
|
||||||
|
public LatheBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
SendMessage(new SharedLatheComponent.LatheSyncRequestMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
if (!Owner.Owner.TryGetComponent(out MaterialStorageComponent storage)
|
||||||
|
|| !Owner.Owner.TryGetComponent(out SharedLatheComponent lathe)
|
||||||
|
|| !Owner.Owner.TryGetComponent(out SharedLatheDatabaseComponent database)) return;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Storage = storage;
|
||||||
|
Lathe = lathe;
|
||||||
|
Database = database;
|
||||||
|
|
||||||
|
menu = new LatheMenu(this);
|
||||||
|
queueMenu = new LatheQueueMenu { Owner = this };
|
||||||
|
|
||||||
|
menu.OnClose += Close;
|
||||||
|
|
||||||
|
menu.Populate();
|
||||||
|
menu.PopulateMaterials();
|
||||||
|
|
||||||
|
menu.QueueButton.OnPressed += (args) => { queueMenu.OpenCentered(); };
|
||||||
|
|
||||||
|
menu.ServerConnectButton.OnPressed += (args) =>
|
||||||
|
{
|
||||||
|
SendMessage(new SharedLatheComponent.LatheServerSelectionMessage());
|
||||||
|
};
|
||||||
|
|
||||||
|
menu.ServerSyncButton.OnPressed += (args) =>
|
||||||
|
{
|
||||||
|
SendMessage(new SharedLatheComponent.LatheServerSyncMessage());
|
||||||
|
};
|
||||||
|
|
||||||
|
storage.OnMaterialStorageChanged += menu.PopulateDisabled;
|
||||||
|
storage.OnMaterialStorageChanged += menu.PopulateMaterials;
|
||||||
|
|
||||||
|
menu.OpenCentered();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Queue(LatheRecipePrototype recipe, int quantity = 1)
|
||||||
|
{
|
||||||
|
SendMessage(new SharedLatheComponent.LatheQueueRecipeMessage(recipe.ID, quantity));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||||
|
{
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case SharedLatheComponent.LatheProducingRecipeMessage msg:
|
||||||
|
if (!_prototypeManager.TryIndex(msg.ID, out LatheRecipePrototype recipe)) break;
|
||||||
|
queueMenu?.SetInfo(recipe);
|
||||||
|
break;
|
||||||
|
case SharedLatheComponent.LatheStoppedProducingRecipeMessage _:
|
||||||
|
queueMenu?.ClearInfo();
|
||||||
|
break;
|
||||||
|
case SharedLatheComponent.LatheFullQueueMessage msg:
|
||||||
|
_queuedRecipes.Clear();
|
||||||
|
foreach (var id in msg.Recipes)
|
||||||
|
{
|
||||||
|
if (!_prototypeManager.TryIndex(id, out LatheRecipePrototype recipePrototype)) break;
|
||||||
|
_queuedRecipes.Enqueue(recipePrototype);
|
||||||
|
}
|
||||||
|
queueMenu?.PopulateList();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing) return;
|
||||||
|
menu?.Dispose();
|
||||||
|
queueMenu?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Research;
|
||||||
|
using Content.Shared.Research;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Research
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedLatheDatabaseComponent))]
|
||||||
|
public class LatheDatabaseComponent : SharedLatheDatabaseComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0649
|
||||||
|
[Dependency]
|
||||||
|
private IPrototypeManager _prototypeManager;
|
||||||
|
#pragma warning restore
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
base.HandleComponentState(curState, nextState);
|
||||||
|
if (!(curState is LatheDatabaseState state)) return;
|
||||||
|
Clear();
|
||||||
|
foreach (var ID in state.Recipes)
|
||||||
|
{
|
||||||
|
if(!_prototypeManager.TryIndex(ID, out LatheRecipePrototype recipe)) continue;
|
||||||
|
AddRecipe(recipe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.GameObjects.Components.Research;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Research
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedMaterialStorageComponent))]
|
||||||
|
public class MaterialStorageComponent : SharedMaterialStorageComponent
|
||||||
|
{
|
||||||
|
protected override Dictionary<string, int> Storage { get; set; } = new Dictionary<string, int>();
|
||||||
|
|
||||||
|
public event Action OnMaterialStorageChanged;
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
base.HandleComponentState(curState, nextState);
|
||||||
|
if (!(curState is MaterialStorageState state)) return;
|
||||||
|
Storage = state.Storage;
|
||||||
|
OnMaterialStorageChanged?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Shared.GameObjects.Components.Research;
|
||||||
|
using Content.Shared.Research;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Research
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedLatheDatabaseComponent))]
|
||||||
|
public class ProtolatheDatabaseComponent : SharedProtolatheDatabaseComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0649
|
||||||
|
[Dependency]
|
||||||
|
private IPrototypeManager _prototypeManager;
|
||||||
|
#pragma warning restore
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when the database gets updated.
|
||||||
|
/// </summary>
|
||||||
|
public event Action OnDatabaseUpdated;
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
base.HandleComponentState(curState, nextState);
|
||||||
|
if (!(curState is ProtolatheDatabaseState state)) return;
|
||||||
|
Clear();
|
||||||
|
foreach (var ID in state.Recipes)
|
||||||
|
{
|
||||||
|
if(!_prototypeManager.TryIndex(ID, out LatheRecipePrototype recipe)) continue;
|
||||||
|
AddRecipe(recipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnDatabaseUpdated?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Research;
|
||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Research
|
||||||
|
{
|
||||||
|
public class ResearchClientBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
private ResearchClientServerSelectionMenu _menu;
|
||||||
|
|
||||||
|
public ResearchClientBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
SendMessage(new SharedResearchClientComponent.ResearchClientSyncMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_menu = new ResearchClientServerSelectionMenu() { Owner = this };
|
||||||
|
|
||||||
|
_menu.OnClose += Close;
|
||||||
|
|
||||||
|
_menu.OpenCentered();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectServer(int serverId)
|
||||||
|
{
|
||||||
|
SendMessage(new SharedResearchClientComponent.ResearchClientServerSelectedMessage(serverId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeselectServer()
|
||||||
|
{
|
||||||
|
SendMessage(new SharedResearchClientComponent.ResearchClientServerDeselectedMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
if (!(state is SharedResearchClientComponent.ResearchClientBoundInterfaceState rstate)) return;
|
||||||
|
_menu.Populate(rstate.ServerCount, rstate.ServerNames, rstate.ServerIds, rstate.SelectedServerId);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Research
|
||||||
|
{
|
||||||
|
public class ResearchClientServerSelectionMenu : SS14Window
|
||||||
|
{
|
||||||
|
private ItemList _servers;
|
||||||
|
private int _serverCount = 0;
|
||||||
|
private string[] _serverNames = new string[]{};
|
||||||
|
private int[] _serverIds = new int[]{};
|
||||||
|
private int _selectedServerId = -1;
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly ILocalizationManager _localizationManager;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
protected override Vector2? CustomSize => (300, 300);
|
||||||
|
public ResearchClientBoundUserInterface Owner { get; set; }
|
||||||
|
|
||||||
|
public ResearchClientServerSelectionMenu()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
Title = _localizationManager.GetString("Research Server Selection");
|
||||||
|
|
||||||
|
_servers = new ItemList() {SelectMode = ItemList.ItemListSelectMode.Single};
|
||||||
|
|
||||||
|
_servers.OnItemSelected += OnItemSelected;
|
||||||
|
_servers.OnItemDeselected += OnItemDeselected;
|
||||||
|
|
||||||
|
var margin = new MarginContainer()
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
/*MarginTop = 5f,
|
||||||
|
MarginLeft = 5f,
|
||||||
|
MarginRight = -5f,
|
||||||
|
MarginBottom = -5f,*/
|
||||||
|
};
|
||||||
|
|
||||||
|
margin.AddChild(_servers);
|
||||||
|
|
||||||
|
Contents.AddChild(margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnItemSelected(ItemList.ItemListSelectedEventArgs itemListSelectedEventArgs)
|
||||||
|
{
|
||||||
|
Owner.SelectServer(_serverIds[itemListSelectedEventArgs.ItemIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnItemDeselected(ItemList.ItemListDeselectedEventArgs itemListDeselectedEventArgs)
|
||||||
|
{
|
||||||
|
Owner.DeselectServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Populate(int serverCount, string[] serverNames, int[] serverIds, int selectedServerId)
|
||||||
|
{
|
||||||
|
_serverCount = serverCount;
|
||||||
|
_serverNames = serverNames;
|
||||||
|
_serverIds = serverIds;
|
||||||
|
_selectedServerId = selectedServerId;
|
||||||
|
|
||||||
|
// Disable so we can select the new selected server without triggering a new sync request.
|
||||||
|
_servers.OnItemSelected -= OnItemSelected;
|
||||||
|
_servers.OnItemDeselected -= OnItemDeselected;
|
||||||
|
|
||||||
|
_servers.Clear();
|
||||||
|
for (var i = 0; i < _serverCount; i++)
|
||||||
|
{
|
||||||
|
var id = _serverIds[i];
|
||||||
|
_servers.AddItem($"ID: {id} || {_serverNames[i]}");
|
||||||
|
if (id == _selectedServerId)
|
||||||
|
_servers[id].Selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_servers.OnItemSelected += OnItemSelected;
|
||||||
|
_servers.OnItemDeselected += OnItemDeselected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using Content.Client.Research;
|
||||||
|
using Content.Shared.GameObjects.Components.Research;
|
||||||
|
using Content.Shared.Research;
|
||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Research
|
||||||
|
{
|
||||||
|
public class ResearchConsoleBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
public int Points { get; private set; } = 0;
|
||||||
|
public int PointsPerSecond { get; private set; } = 0;
|
||||||
|
private ResearchConsoleMenu _consoleMenu;
|
||||||
|
private TechnologyDatabaseComponent TechnologyDatabase;
|
||||||
|
|
||||||
|
|
||||||
|
public ResearchConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
SendMessage(new SharedResearchConsoleComponent.ConsoleServerSyncMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
if (!Owner.Owner.TryGetComponent(out TechnologyDatabase)) return;
|
||||||
|
|
||||||
|
_consoleMenu = new ResearchConsoleMenu(this);
|
||||||
|
|
||||||
|
_consoleMenu.OnClose += Close;
|
||||||
|
|
||||||
|
_consoleMenu.ServerSyncButton.OnPressed += (args) =>
|
||||||
|
{
|
||||||
|
SendMessage(new SharedResearchConsoleComponent.ConsoleServerSyncMessage());
|
||||||
|
};
|
||||||
|
|
||||||
|
_consoleMenu.ServerSelectionButton.OnPressed += (args) =>
|
||||||
|
{
|
||||||
|
SendMessage(new SharedResearchConsoleComponent.ConsoleServerSelectionMessage());
|
||||||
|
};
|
||||||
|
|
||||||
|
_consoleMenu.UnlockButton.OnPressed += (args) =>
|
||||||
|
{
|
||||||
|
SendMessage(new SharedResearchConsoleComponent.ConsoleUnlockTechnologyMessage(_consoleMenu.TechnologySelected.ID));
|
||||||
|
};
|
||||||
|
|
||||||
|
_consoleMenu.OpenCentered();
|
||||||
|
|
||||||
|
TechnologyDatabase.OnDatabaseUpdated += _consoleMenu.Populate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsTechnologyUnlocked(TechnologyPrototype technology)
|
||||||
|
{
|
||||||
|
return TechnologyDatabase.IsTechnologyUnlocked(technology);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanUnlockTechnology(TechnologyPrototype technology)
|
||||||
|
{
|
||||||
|
return TechnologyDatabase.CanUnlockTechnology(technology);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
var castState = (SharedResearchConsoleComponent.ResearchConsoleBoundInterfaceState)state;
|
||||||
|
Points = castState.Points;
|
||||||
|
PointsPerSecond = castState.PointsPerSecond;
|
||||||
|
// We update the user interface here.
|
||||||
|
_consoleMenu?.PopulatePoints();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing) return;
|
||||||
|
_consoleMenu?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Shared.GameObjects.Components.Research;
|
||||||
|
using Content.Shared.Research;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Research
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class TechnologyDatabaseComponent : SharedTechnologyDatabaseComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event called when the database is updated.
|
||||||
|
/// </summary>
|
||||||
|
public event Action OnDatabaseUpdated;
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
base.HandleComponentState(curState, nextState);
|
||||||
|
if (!(curState is TechnologyDatabaseState state)) return;
|
||||||
|
_technologies.Clear();
|
||||||
|
var protoManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
foreach (var techID in state.Technologies)
|
||||||
|
{
|
||||||
|
if (!protoManager.TryIndex(techID, out TechnologyPrototype technology)) continue;
|
||||||
|
_technologies.Add(technology);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnDatabaseUpdated?.Invoke();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
Content.Client/GameObjects/Components/Sound/SoundComponent.cs
Normal file
109
Content.Client/GameObjects/Components/Sound/SoundComponent.cs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.GameObjects.Components.Sound;
|
||||||
|
using Robust.Client.GameObjects.EntitySystems;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Timers;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Sound
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class SoundComponent : SharedSoundComponent
|
||||||
|
{
|
||||||
|
private readonly List<ScheduledSound> _schedules = new List<ScheduledSound>();
|
||||||
|
private AudioSystem _audioSystem;
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IRobustRandom _random;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
public override void StopAllSounds()
|
||||||
|
{
|
||||||
|
foreach (var schedule in _schedules)
|
||||||
|
{
|
||||||
|
schedule.Play = false;
|
||||||
|
}
|
||||||
|
_schedules.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void StopScheduledSound(string filename)
|
||||||
|
{
|
||||||
|
foreach (var schedule in _schedules.ToArray())
|
||||||
|
{
|
||||||
|
if (schedule.Filename != filename) continue;
|
||||||
|
schedule.Play = false;
|
||||||
|
_schedules.Remove(schedule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AddScheduledSound(ScheduledSound schedule)
|
||||||
|
{
|
||||||
|
_schedules.Add(schedule);
|
||||||
|
Play(schedule);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Play(ScheduledSound schedule)
|
||||||
|
{
|
||||||
|
if (!schedule.Play) return;
|
||||||
|
|
||||||
|
Timer.Spawn((int) schedule.Delay + (_random.Next((int) schedule.RandomDelay)),() =>
|
||||||
|
{
|
||||||
|
if (!schedule.Play) return; // We make sure this hasn't changed.
|
||||||
|
if (_audioSystem == null) _audioSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>();
|
||||||
|
_audioSystem.Play(schedule.Filename, Owner, schedule.AudioParams);
|
||||||
|
|
||||||
|
if (schedule.Times == 0)
|
||||||
|
{
|
||||||
|
_schedules.Remove(schedule);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schedule.Times > 0)
|
||||||
|
schedule.Times--;
|
||||||
|
|
||||||
|
Play(schedule);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
|
||||||
|
{
|
||||||
|
base.HandleMessage(message, netChannel, component);
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case ScheduledSoundMessage msg:
|
||||||
|
AddScheduledSound(msg.Schedule);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StopSoundScheduleMessage msg:
|
||||||
|
StopScheduledSound(msg.Filename);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StopAllSoundsMessage _:
|
||||||
|
StopAllSounds();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
IoCManager.Resolve<IEntitySystemManager>().TryGetEntitySystem(out _audioSystem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
if (serializer.Writing) return;
|
||||||
|
serializer.TryReadDataField("schedules", out List<ScheduledSound> schedules);
|
||||||
|
if (schedules == null) return;
|
||||||
|
foreach (var schedule in schedules)
|
||||||
|
{
|
||||||
|
if (schedule == null) continue;
|
||||||
|
AddScheduledSound(schedule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
Content.Client/GameObjects/Components/StackComponent.cs
Normal file
61
Content.Client/GameObjects/Components/StackComponent.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Client.Utility;
|
||||||
|
using Content.Shared.GameObjects.Components;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class StackComponent : SharedStackComponent, IItemStatus
|
||||||
|
{
|
||||||
|
[ViewVariables] public int Count { get; private set; }
|
||||||
|
[ViewVariables] public int MaxCount { get; private set; }
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
|
||||||
|
|
||||||
|
public Control MakeControl() => new StatusControl(this);
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
var cast = (StackComponentState) curState;
|
||||||
|
|
||||||
|
Count = cast.Count;
|
||||||
|
MaxCount = cast.MaxCount;
|
||||||
|
_uiUpdateNeeded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class StatusControl : Control
|
||||||
|
{
|
||||||
|
private readonly StackComponent _parent;
|
||||||
|
private readonly RichTextLabel _label;
|
||||||
|
|
||||||
|
public StatusControl(StackComponent parent)
|
||||||
|
{
|
||||||
|
_parent = parent;
|
||||||
|
_label = new RichTextLabel {StyleClasses = {NanoStyle.StyleClassItemStatus}};
|
||||||
|
AddChild(_label);
|
||||||
|
|
||||||
|
parent._uiUpdateNeeded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update(FrameEventArgs args)
|
||||||
|
{
|
||||||
|
base.Update(args);
|
||||||
|
|
||||||
|
if (!_parent._uiUpdateNeeded)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_parent._uiUpdateNeeded = false;
|
||||||
|
|
||||||
|
_label.SetMarkup(Loc.GetString("Count: [color=white]{0}[/color]", _parent.Count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.GameObjects.Components.Storage;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Storage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Client version of item storage containers, contains a UI which displays stored entities and their size
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public class ClientStorageComponent : SharedStorageComponent
|
||||||
|
{
|
||||||
|
private Dictionary<EntityUid, int> StoredEntities { get; set; } = new Dictionary<EntityUid, int>();
|
||||||
|
private int StorageSizeUsed;
|
||||||
|
private int StorageCapacityMax;
|
||||||
|
private StorageWindow Window;
|
||||||
|
|
||||||
|
public override void OnAdd()
|
||||||
|
{
|
||||||
|
base.OnAdd();
|
||||||
|
|
||||||
|
Window = new StorageWindow()
|
||||||
|
{StorageEntity = this};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
Window.Dispose();
|
||||||
|
base.OnRemove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
|
||||||
|
IComponent component = null)
|
||||||
|
{
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
//Updates what we are storing for the UI
|
||||||
|
case StorageHeldItemsMessage msg:
|
||||||
|
HandleStorageMessage(msg);
|
||||||
|
break;
|
||||||
|
//Opens the UI
|
||||||
|
case OpenStorageUIMessage _:
|
||||||
|
OpenUI();
|
||||||
|
break;
|
||||||
|
case CloseStorageUIMessage _:
|
||||||
|
CloseUI();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies received values from server about contents of storage container
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storagestate"></param>
|
||||||
|
private void HandleStorageMessage(StorageHeldItemsMessage storagestate)
|
||||||
|
{
|
||||||
|
StoredEntities = new Dictionary<EntityUid, int>(storagestate.StoredEntities);
|
||||||
|
StorageSizeUsed = storagestate.StorageSizeUsed;
|
||||||
|
StorageCapacityMax = storagestate.StorageSizeMax;
|
||||||
|
Window.BuildEntityList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the storage UI
|
||||||
|
/// </summary>
|
||||||
|
private void OpenUI()
|
||||||
|
{
|
||||||
|
Window.Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseUI()
|
||||||
|
{
|
||||||
|
Window.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Function for clicking one of the stored entity buttons in the UI, tells server to remove that entity
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entityuid"></param>
|
||||||
|
private void Interact(EntityUid entityuid)
|
||||||
|
{
|
||||||
|
SendNetworkMessage(new RemoveEntityMessage(entityuid));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GUI class for client storage component
|
||||||
|
/// </summary>
|
||||||
|
private class StorageWindow : SS14Window
|
||||||
|
{
|
||||||
|
private Control VSplitContainer;
|
||||||
|
private VBoxContainer EntityList;
|
||||||
|
private Label Information;
|
||||||
|
public ClientStorageComponent StorageEntity;
|
||||||
|
|
||||||
|
protected override Vector2? CustomSize => (180, 320);
|
||||||
|
|
||||||
|
public StorageWindow()
|
||||||
|
{
|
||||||
|
Title = "Storage Item";
|
||||||
|
RectClipContent = true;
|
||||||
|
|
||||||
|
VSplitContainer = new VBoxContainer();
|
||||||
|
Information = new Label
|
||||||
|
{
|
||||||
|
Text = "Items: 0 Volume: 0/0 Stuff",
|
||||||
|
SizeFlagsVertical = SizeFlags.ShrinkCenter
|
||||||
|
};
|
||||||
|
VSplitContainer.AddChild(Information);
|
||||||
|
|
||||||
|
var listScrollContainer = new ScrollContainer
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
HScrollEnabled = true,
|
||||||
|
VScrollEnabled = true
|
||||||
|
};
|
||||||
|
EntityList = new VBoxContainer
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||||
|
};
|
||||||
|
listScrollContainer.AddChild(EntityList);
|
||||||
|
VSplitContainer.AddChild(listScrollContainer);
|
||||||
|
Contents.AddChild(VSplitContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Close()
|
||||||
|
{
|
||||||
|
StorageEntity.SendNetworkMessage(new CloseStorageUIMessage());
|
||||||
|
base.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loops through stored entities creating buttons for each, updates information labels
|
||||||
|
/// </summary>
|
||||||
|
public void BuildEntityList()
|
||||||
|
{
|
||||||
|
EntityList.DisposeAllChildren();
|
||||||
|
|
||||||
|
var storagelist = StorageEntity.StoredEntities;
|
||||||
|
|
||||||
|
foreach (var entityuid in storagelist)
|
||||||
|
{
|
||||||
|
var entity = IoCManager.Resolve<IEntityManager>().GetEntity(entityuid.Key);
|
||||||
|
|
||||||
|
var button = new EntityButton()
|
||||||
|
{
|
||||||
|
EntityuID = entityuid.Key
|
||||||
|
};
|
||||||
|
button.ActualButton.OnToggled += OnItemButtonToggled;
|
||||||
|
//Name and Size labels set
|
||||||
|
button.EntityName.Text = entity.Name;
|
||||||
|
button.EntitySize.Text = string.Format("{0}", entityuid.Value);
|
||||||
|
|
||||||
|
//Gets entity sprite and assigns it to button texture
|
||||||
|
if (entity.TryGetComponent(out ISpriteComponent sprite))
|
||||||
|
{
|
||||||
|
button.EntitySpriteView.Sprite = sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityList.AddChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sets information about entire storage container current capacity
|
||||||
|
if (StorageEntity.StorageCapacityMax != 0)
|
||||||
|
{
|
||||||
|
Information.Text = String.Format("Items: {0}, Stored: {1}/{2}", storagelist.Count,
|
||||||
|
StorageEntity.StorageSizeUsed, StorageEntity.StorageCapacityMax);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Information.Text = String.Format("Items: {0}", storagelist.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Function assigned to button toggle which removes the entity from storage
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
private void OnItemButtonToggled(BaseButton.ButtonToggledEventArgs args)
|
||||||
|
{
|
||||||
|
var control = (EntityButton) args.Button.Parent;
|
||||||
|
args.Button.Pressed = false;
|
||||||
|
StorageEntity.Interact(control.EntityuID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Button created for each entity that represents that item in the storage UI, with a texture, and name and size label
|
||||||
|
/// </summary>
|
||||||
|
private class EntityButton : PanelContainer
|
||||||
|
{
|
||||||
|
public EntityUid EntityuID { get; set; }
|
||||||
|
public Button ActualButton { get; }
|
||||||
|
public SpriteView EntitySpriteView { get; }
|
||||||
|
public Control EntityControl { get; }
|
||||||
|
public Label EntityName { get; }
|
||||||
|
public Label EntitySize { get; }
|
||||||
|
|
||||||
|
public EntityButton()
|
||||||
|
{
|
||||||
|
ActualButton = new Button
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||||
|
ToggleMode = true,
|
||||||
|
MouseFilter = MouseFilterMode.Stop
|
||||||
|
};
|
||||||
|
AddChild(ActualButton);
|
||||||
|
|
||||||
|
var hBoxContainer = new HBoxContainer {MouseFilter = MouseFilterMode.Ignore};
|
||||||
|
EntitySpriteView = new SpriteView
|
||||||
|
{
|
||||||
|
CustomMinimumSize = new Vector2(32.0f, 32.0f), MouseFilter = MouseFilterMode.Ignore
|
||||||
|
};
|
||||||
|
EntityName = new Label
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||||
|
Text = "Backpack",
|
||||||
|
MouseFilter = MouseFilterMode.Ignore
|
||||||
|
};
|
||||||
|
hBoxContainer.AddChild(EntitySpriteView);
|
||||||
|
hBoxContainer.AddChild(EntityName);
|
||||||
|
|
||||||
|
EntityControl = new Control
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand, MouseFilter = MouseFilterMode.Ignore
|
||||||
|
};
|
||||||
|
EntitySize = new Label
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||||
|
Text = "Size 6",
|
||||||
|
Align = Label.AlignMode.Right,
|
||||||
|
/*AnchorLeft = 1.0f,
|
||||||
|
AnchorRight = 1.0f,
|
||||||
|
AnchorBottom = 0.5f,
|
||||||
|
AnchorTop = 0.5f,
|
||||||
|
MarginLeft = -38.0f,
|
||||||
|
MarginTop = -7.0f,
|
||||||
|
MarginRight = -5.0f,
|
||||||
|
MarginBottom = 7.0f*/
|
||||||
|
};
|
||||||
|
|
||||||
|
EntityControl.AddChild(EntitySize);
|
||||||
|
hBoxContainer.AddChild(EntityControl);
|
||||||
|
AddChild(hBoxContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Storage;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Storage
|
||||||
|
{
|
||||||
|
public sealed class StorageVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
private string _stateOpen;
|
||||||
|
private string _stateClosed;
|
||||||
|
|
||||||
|
public override void LoadData(YamlMappingNode node)
|
||||||
|
{
|
||||||
|
base.LoadData(node);
|
||||||
|
|
||||||
|
if (node.TryGetNode("state_open", out var child))
|
||||||
|
{
|
||||||
|
_stateOpen = child.AsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.TryGetNode("state_closed", out child))
|
||||||
|
{
|
||||||
|
_stateClosed = child.AsString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
if (!component.Owner.TryGetComponent(out ISpriteComponent sprite))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.TryGetData(StorageVisuals.Open, out bool open);
|
||||||
|
sprite.LayerSetState(StorageVisualLayers.Door, open ? _stateOpen : _stateClosed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StorageVisualLayers
|
||||||
|
{
|
||||||
|
Door
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components.Transform;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Simple component that automatically hides the sibling
|
||||||
|
/// <see cref="ISpriteComponent" /> when the tile it's on is not a sub floor
|
||||||
|
/// (plating).
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="P:Content.Shared.Maps.ContentTileDefinition.IsSubFloor" />
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class SubFloorHideComponent : Component
|
||||||
|
{
|
||||||
|
private SnapGridComponent _snapGridComponent;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Name => "SubFloorHide";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_snapGridComponent = Owner.GetComponent<SnapGridComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Startup()
|
||||||
|
{
|
||||||
|
base.Startup();
|
||||||
|
|
||||||
|
_snapGridComponent.OnPositionChanged += SnapGridOnPositionChanged;
|
||||||
|
Owner.EntityManager.EventBus.RaiseEvent(Owner, new SubFloorHideDirtyEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
|
||||||
|
if(Owner.Transform.Running == false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_snapGridComponent.OnPositionChanged -= SnapGridOnPositionChanged;
|
||||||
|
Owner.EntityManager.EventBus.RaiseEvent(Owner, new SubFloorHideDirtyEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SnapGridOnPositionChanged()
|
||||||
|
{
|
||||||
|
Owner.EntityManager.EventBus.RaiseEvent(Owner, new SubFloorHideDirtyEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class SubFloorHideDirtyEvent : EntitySystemMessage { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Shared.GameObjects.Components.Triggers;
|
||||||
|
using Robust.Client.Animations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.GameObjects.Components.Animations;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Doors
|
||||||
|
{
|
||||||
|
public class TimerTriggerVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
private const string AnimationKey = "priming_animation";
|
||||||
|
|
||||||
|
private Animation PrimingAnimation;
|
||||||
|
|
||||||
|
public override void LoadData(YamlMappingNode node)
|
||||||
|
{
|
||||||
|
base.LoadData(node);
|
||||||
|
|
||||||
|
var countdownSound = node.GetNode("countdown_sound").AsString();
|
||||||
|
|
||||||
|
|
||||||
|
PrimingAnimation = new Animation { Length = TimeSpan.MaxValue };
|
||||||
|
{
|
||||||
|
var flick = new AnimationTrackSpriteFlick();
|
||||||
|
PrimingAnimation.AnimationTracks.Add(flick);
|
||||||
|
flick.LayerKey = TriggerVisualLayers.Base;
|
||||||
|
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("primed", 0f));
|
||||||
|
|
||||||
|
var sound = new AnimationTrackPlaySound();
|
||||||
|
PrimingAnimation.AnimationTracks.Add(sound);
|
||||||
|
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(countdownSound, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void InitializeEntity(IEntity entity)
|
||||||
|
{
|
||||||
|
if (!entity.HasComponent<AnimationPlayerComponent>())
|
||||||
|
{
|
||||||
|
entity.AddComponent<AnimationPlayerComponent>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
var animPlayer = component.Owner.GetComponent<AnimationPlayerComponent>();
|
||||||
|
if (!component.TryGetData(TriggerVisuals.VisualState, out TriggerVisualState state))
|
||||||
|
{
|
||||||
|
state = TriggerVisualState.Unprimed;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case TriggerVisualState.Primed:
|
||||||
|
if (!animPlayer.HasRunningAnimation(AnimationKey))
|
||||||
|
{
|
||||||
|
animPlayer.Play(PrimingAnimation, AnimationKey);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TriggerVisualState.Unprimed:
|
||||||
|
sprite.LayerSetState(0, "icon");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public enum TriggerVisualLayers
|
||||||
|
{
|
||||||
|
Base
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user