Compare commits
1007 Commits
pre-godot
...
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 |
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
|
||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -261,10 +261,22 @@ __pycache__/
|
||||
*.pyc
|
||||
|
||||
# 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/
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -1,4 +1,4 @@
|
||||
[submodule "engine"]
|
||||
path = engine
|
||||
url = https://github.com/space-wizards/space-station-14.git
|
||||
[submodule "RobustToolbox"]
|
||||
path = RobustToolbox
|
||||
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
|
||||
DISABLE_SUBMODULE_AUTOUPDATE
|
||||
*.nuget*
|
||||
project.assets.json
|
||||
project.packagespec.json
|
||||
@@ -17,6 +17,7 @@ https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild
|
||||
<Python>python3</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>
|
||||
@@ -24,6 +25,9 @@ https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild
|
||||
<PropertyGroup>
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<Target Name="Build">
|
||||
<Exec Command="$(Python) git_helper.py" CustomErrorRegularExpression="^Error" />
|
||||
</Target>
|
||||
@@ -31,4 +35,8 @@ https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild
|
||||
<Target Name="Clean">
|
||||
<Message Importance="low" Text="Ignoring 'Clean' target." />
|
||||
</Target>
|
||||
</Project>
|
||||
<Target Name="Compile">
|
||||
</Target>
|
||||
<Target Name="CoreCompile">
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
@@ -8,10 +8,12 @@ import shutil
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
SOLUTION_PATH = Path("..") / "SpaceStation14Content.sln"
|
||||
CURRENT_HOOKS_VERSION = "1" # If this doesn't match the saved version we overwrite them all.
|
||||
SOLUTION_PATH = Path("..") / "SpaceStation14.sln"
|
||||
# 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"
|
||||
|
||||
|
||||
def run_command(command: List[str], capture: bool = False) -> subprocess.CompletedProcess:
|
||||
"""
|
||||
Runs a command with pretty output.
|
||||
@@ -40,6 +42,9 @@ def update_submodules():
|
||||
Updates all submodules.
|
||||
"""
|
||||
|
||||
if os.path.isfile("DISABLE_SUBMODULE_AUTOUPDATE"):
|
||||
return
|
||||
|
||||
# If the status doesn't match, force VS to reload the solution.
|
||||
# status = run_command(["git", "submodule", "status"], capture=True)
|
||||
run_command(["git", "submodule", "update", "--init", "--recursive"])
|
||||
@@ -50,6 +55,7 @@ def update_submodules():
|
||||
# print("Git submodules changed. Reloading solution.")
|
||||
# reset_solution()
|
||||
|
||||
|
||||
def install_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)):
|
||||
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():
|
||||
@@ -91,6 +98,7 @@ def reset_solution():
|
||||
with SOLUTION_PATH.open("w") as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
install_hooks()
|
||||
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
@@ -1,9 +1,8 @@
|
||||
#!/usr/bin/env -i bash
|
||||
#!/bin/sh
|
||||
|
||||
# cd to file containing script or something?
|
||||
BASEDIR=$(dirname "$0")
|
||||
echo "$BASEDIR"
|
||||
cd "$BASEDIR"
|
||||
|
||||
# TODO: unhardcode this, probably ship Mono with SS14.
|
||||
/Library/Frameworks/Mono.framework/Versions/Current/Commands/mono --arch=64 ./SS14.Client.exe
|
||||
exec ../Resources/Robust.Client "$@"
|
||||
|
||||
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,105 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{A2E5F175-78AF-4DDD-8F97-E2D2552372ED}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Content.Client</RootNamespace>
|
||||
<AssemblyName>Content.Client</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ContentAssemblyTarget>..\bin\Client\Resources\Assemblies\</ContentAssemblyTarget>
|
||||
<!--
|
||||
This copies all dependencies,
|
||||
but on the plus side it's automatically located in the right place.
|
||||
-->
|
||||
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<LangVersion>8</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Platforms>x64</Platforms>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputPath>..\bin\Content.Client\</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.DefineConstants.targets" />
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)packages\OpenTK.3.0.0-pre\lib\net20\OpenTK.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="YamlDotNet, Version=4.3.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)packages\YamlDotNet.4.3.0\lib\net45\YamlDotNet.dll</HintPath>
|
||||
</Reference>
|
||||
<PackageReference Include="Nett" Version="0.13.0" />
|
||||
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0007" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0006" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.1.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="EntryPoint.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="GameObjects\Components\Items\ClientHandsComponent.cs" />
|
||||
<Compile Include="GameObjects\Components\Doors\ClientDoorComponent.cs" />
|
||||
<Compile Include="Interfaces\GameObjects\Components\Items\IHandsComponent.cs" />
|
||||
<Compile Include="UserInterface\HandsGui.cs" />
|
||||
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
|
||||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Engine.targets" />
|
||||
<Target Name="ContentAfterBuild" DependsOnTargets="ClientAfterBuild" AfterTargets="Build" />
|
||||
</Project>
|
||||
|
||||
@@ -1,45 +1,246 @@
|
||||
using Content.Client.GameObjects;
|
||||
using Content.Client.Interfaces.GameObjects;
|
||||
using SS14.Shared.ContentPack;
|
||||
using SS14.Shared.Interfaces.GameObjects;
|
||||
using SS14.Shared.IoC;
|
||||
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
|
||||
{
|
||||
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()
|
||||
{
|
||||
var factory = IoCManager.Resolve<IComponentFactory>();
|
||||
var prototypes = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
factory.RegisterIgnore("Inventory");
|
||||
factory.RegisterIgnore("Item");
|
||||
factory.RegisterIgnore("Interactable");
|
||||
factory.RegisterIgnore("Damageable");
|
||||
factory.RegisterIgnore("Destructible");
|
||||
factory.RegisterIgnore("Temperature");
|
||||
factory.RegisterIgnore("PowerTransfer");
|
||||
factory.RegisterIgnore("PowerNode");
|
||||
factory.RegisterIgnore("PowerProvider");
|
||||
factory.RegisterIgnore("PowerDevice");
|
||||
factory.RegisterIgnore("PowerStorage");
|
||||
factory.RegisterIgnore("PowerGenerator");
|
||||
factory.DoAutoRegistrations();
|
||||
|
||||
factory.RegisterIgnore("Wirecutter");
|
||||
factory.RegisterIgnore("Screwdriver");
|
||||
factory.RegisterIgnore("Multitool");
|
||||
factory.RegisterIgnore("Welder");
|
||||
factory.RegisterIgnore("Wrench");
|
||||
factory.RegisterIgnore("Crowbar");
|
||||
factory.RegisterIgnore("HitscanWeapon");
|
||||
factory.RegisterIgnore("ProjectileWeapon");
|
||||
factory.RegisterIgnore("Projectile");
|
||||
factory.RegisterIgnore("MeleeWeapon");
|
||||
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"
|
||||
};
|
||||
|
||||
factory.Register<HandsComponent>();
|
||||
factory.RegisterReference<HandsComponent, IHandsComponent>();
|
||||
foreach (var ignoreName in registerIgnore)
|
||||
{
|
||||
factory.RegisterIgnore(ignoreName);
|
||||
}
|
||||
|
||||
factory.Register<ClientDoorComponent>();
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
using Content.Shared.GameObjects;
|
||||
using Lidgren.Network;
|
||||
using SS14.Client.GameObjects;
|
||||
using SS14.Shared.GameObjects;
|
||||
using SS14.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Client.GameObjects
|
||||
{
|
||||
public class ClientDoorComponent : SharedDoorComponent
|
||||
{
|
||||
public bool Opened { get; private set; }
|
||||
private SpriteComponent spriteComponent;
|
||||
|
||||
private string OpenSprite = "door_ewo";
|
||||
private string CloseSprite = "door_ew";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
spriteComponent = Owner.GetComponent<SpriteComponent>();
|
||||
}
|
||||
|
||||
private void Open()
|
||||
{
|
||||
Opened = true;
|
||||
spriteComponent.SetSpriteByKey(OpenSprite);
|
||||
}
|
||||
|
||||
private void Close()
|
||||
{
|
||||
Opened = false;
|
||||
spriteComponent.SetSpriteByKey(CloseSprite);
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState state)
|
||||
{
|
||||
var castState = (DoorComponentState)state;
|
||||
if (castState.Opened == Opened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (castState.Opened)
|
||||
{
|
||||
Open();
|
||||
}
|
||||
else
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
public override void LoadParameters(YamlMappingNode mapping)
|
||||
{
|
||||
base.LoadParameters(mapping);
|
||||
|
||||
YamlNode node;
|
||||
if (mapping.TryGetNode("openstate", out node))
|
||||
{
|
||||
OpenSprite = node.AsString();
|
||||
}
|
||||
|
||||
if (mapping.TryGetNode("closestate", out node))
|
||||
{
|
||||
CloseSprite = node.AsString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,64 @@
|
||||
using Content.Client.Interfaces.GameObjects;
|
||||
// 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 SS14.Client.Interfaces.UserInterface;
|
||||
using SS14.Shared.GameObjects;
|
||||
using SS14.Shared.Interfaces.GameObjects;
|
||||
using SS14.Shared.IoC;
|
||||
using System.Collections.Generic;
|
||||
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 readonly Dictionary<string, IEntity> hands = new Dictionary<string, IEntity>();
|
||||
public string ActiveIndex { get; private set; }
|
||||
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))
|
||||
if (_hands.TryGetValue(index, out var entity))
|
||||
{
|
||||
return entity;
|
||||
}
|
||||
@@ -24,30 +66,134 @@ namespace Content.Client.GameObjects
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState state)
|
||||
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||
{
|
||||
var cast = (HandsComponentState)state;
|
||||
hands.Clear();
|
||||
foreach (var hand in cast.Hands)
|
||||
if (curState == null)
|
||||
return;
|
||||
|
||||
var cast = (HandsComponentState) curState;
|
||||
foreach (var (slot, uid) in cast.Hands)
|
||||
{
|
||||
hands[hand.Key] = Owner.EntityManager.GetEntity(hand.Value);
|
||||
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;
|
||||
|
||||
// Tell UI to update.
|
||||
var uiMgr = IoCManager.Resolve<IUserInterfaceManager>();
|
||||
if (!uiMgr.TryGetSingleComponent<HandsGui>(out var component))
|
||||
_gui?.UpdateHandIcons();
|
||||
}
|
||||
|
||||
private void _setHand(string hand, IEntity entity)
|
||||
{
|
||||
if (_sprite == null)
|
||||
{
|
||||
component = new HandsGui();
|
||||
uiMgr.AddComponent(component);
|
||||
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;
|
||||
}
|
||||
component.UpdateHandIcons();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Content.Client.VendingMachines;
|
||||
using Content.Shared.GameObjects.Components.VendingMachines;
|
||||
using Robust.Client.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.VendingMachines
|
||||
{
|
||||
class VendingMachineBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private VendingMachineMenu _menu;
|
||||
|
||||
public SharedVendingMachineComponent VendingMachine { get; private set; }
|
||||
|
||||
public VendingMachineBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
SendMessage(new SharedVendingMachineComponent.InventorySyncRequestMessage());
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
if(!Owner.Owner.TryGetComponent(out SharedVendingMachineComponent vendingMachine))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VendingMachine = vendingMachine;
|
||||
|
||||
_menu = new VendingMachineMenu() { Owner = this, Title = Owner.Owner.Name };
|
||||
_menu.Populate(VendingMachine.Inventory);
|
||||
|
||||
_menu.OnClose += Close;
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
public void Eject(string ID)
|
||||
{
|
||||
SendMessage(new SharedVendingMachineComponent.VendingMachineEjectMessage(ID));
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
switch(message)
|
||||
{
|
||||
case SharedVendingMachineComponent.VendingMachineInventoryMessage msg:
|
||||
_menu.Populate(msg.Inventory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if(!disposing) { return; }
|
||||
_menu?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
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 YamlDotNet.RepresentationModel;
|
||||
using static Content.Shared.GameObjects.Components.VendingMachines.SharedVendingMachineComponent;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.VendingMachines
|
||||
{
|
||||
public class VendingMachineVisualizer2D : AppearanceVisualizer
|
||||
{
|
||||
// TODO: The length of these animations is supposed to be dictated
|
||||
// by the vending machine's pack prototype's `AnimationDuration`
|
||||
// but we have no good way of passing that data from the server
|
||||
// to the client at the moment. Rework Visualizers?
|
||||
private const string DeniedAnimationKey = "deny";
|
||||
private const string EjectAnimationKey = "eject";
|
||||
|
||||
private Animation _deniedAnimation;
|
||||
private Animation _ejectAnimation;
|
||||
|
||||
public override void LoadData(YamlMappingNode node)
|
||||
{
|
||||
base.LoadData(node);
|
||||
_deniedAnimation = new Animation {Length = TimeSpan.FromSeconds(1.2f)};
|
||||
{
|
||||
var flick = new AnimationTrackSpriteFlick();
|
||||
_deniedAnimation.AnimationTracks.Add(flick);
|
||||
flick.LayerKey = VendingMachineVisualLayers.Base;
|
||||
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("deny", 0f));
|
||||
}
|
||||
|
||||
_ejectAnimation = new Animation {Length = TimeSpan.FromSeconds(1.2f)};
|
||||
{
|
||||
var flick = new AnimationTrackSpriteFlick();
|
||||
_ejectAnimation.AnimationTracks.Add(flick);
|
||||
flick.LayerKey = VendingMachineVisualLayers.Base;
|
||||
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("eject", 0f));
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeEntity(IEntity entity)
|
||||
{
|
||||
base.InitializeEntity(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(VendingMachineVisuals.VisualState, out VendingMachineVisualState state))
|
||||
{
|
||||
state = VendingMachineVisualState.Normal;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case VendingMachineVisualState.Normal:
|
||||
sprite.LayerSetState(VendingMachineVisualLayers.Base, "normal");
|
||||
break;
|
||||
case VendingMachineVisualState.Off:
|
||||
sprite.LayerSetState(VendingMachineVisualLayers.Base, "off");
|
||||
break;
|
||||
case VendingMachineVisualState.Broken:
|
||||
sprite.LayerSetState(VendingMachineVisualLayers.Base, "broken");
|
||||
break;
|
||||
case VendingMachineVisualState.Deny:
|
||||
if (!animPlayer.HasRunningAnimation(DeniedAnimationKey))
|
||||
{
|
||||
animPlayer.Play(_deniedAnimation, DeniedAnimationKey);
|
||||
}
|
||||
|
||||
break;
|
||||
case VendingMachineVisualState.Eject:
|
||||
if (!animPlayer.HasRunningAnimation(EjectAnimationKey))
|
||||
{
|
||||
animPlayer.Play(_ejectAnimation, EjectAnimationKey);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public enum VendingMachineVisualLayers
|
||||
{
|
||||
Base,
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user