Compare commits
1002 Commits
janisslsm/
...
main
Author | SHA1 | Date | |
---|---|---|---|
8520650018 | |||
b958c108f9 | |||
8ae5fcfad0 | |||
468efed71c | |||
4926b2f2be | |||
562ddd513f | |||
35d5c01203 | |||
8f8bc5b364 | |||
a8227ce54c | |||
ec9dc2aa5f | |||
f7906c91e3 | |||
159598979d | |||
4983417201 | |||
9b652f5c3c | |||
19b04533df | |||
cddd4bdf5c | |||
12b6e5d16e | |||
8eefd67d79 | |||
c4b2248df5 | |||
2c3043f40e | |||
7d02906656 | |||
ed54e00a03 | |||
3d6c880c96 | |||
660768b53b | |||
3de68e51d5 | |||
c06abded11 | |||
9468768947 | |||
0af7f41201 | |||
de1e2a25f2 | |||
1cf7b41d3f | |||
ab9cc685eb | |||
743b784754 | |||
5df533a7fb | |||
9417aa3c84 | |||
a1872e2b07 | |||
9042e85355 | |||
66ee550ccd | |||
7a295a86ec | |||
88d00eaaa1 | |||
1e8f2fc766 | |||
0d842ade90 | |||
4e3a2e17ee | |||
61864b2be1 | |||
45748fa8be | |||
afec59e8a6 | |||
ee1a49f5f2 | |||
9e94083875 | |||
db0e0d80dd | |||
5cda2e2d08 | |||
e23d865044 | |||
c7658b5b20 | |||
9993500eca | |||
267357871b | |||
cf5ed0442d | |||
de36e2ee8d | |||
ca1b6c31b6 | |||
d66c474bfc | |||
781f01520f | |||
ac37702468 | |||
75c011e3cb | |||
4d4f885c8e | |||
66d1a65e63 | |||
48eefd8db1 | |||
4a6a5ea9cc | |||
95c0ad7892 | |||
a90d3a5156 | |||
d0c9409a2d | |||
bbde7b2141 | |||
5271123090 | |||
f3e56480e5 | |||
6f46ace40c | |||
883426e429 | |||
13432bf034 | |||
a1267e5f64 | |||
2058207b6a | |||
c7c416c100 | |||
90e97d7888 | |||
3f6734ac1c | |||
6f64690b91 | |||
fb5a7320bb | |||
143b358a03 | |||
0b75757277 | |||
fd7f4c9e92 | |||
fa6fac494b | |||
6b3f524574 | |||
506365f97e | |||
70646160c3 | |||
3ffa4a7fd3 | |||
826a09a473 | |||
100aefcee4 | |||
409c089d11 | |||
8c32dc2670 | |||
a67f99b665 | |||
756a01d270 | |||
efc7467a99 | |||
99e1a66da8 | |||
370f8c1008 | |||
f039998d71 | |||
eb594af9d8 | |||
bb8596fa87 | |||
a85539a686 | |||
ada6a4bad0 | |||
948104a9a6 | |||
7a8b12b372 | |||
26d644a982 | |||
d6750cd84b | |||
f3601ec43e | |||
15aaa28a4f | |||
ce5b0fc9e2 | |||
64290b72c0 | |||
570c6fe0d1 | |||
146dbd1b89 | |||
e17d43dcb6 | |||
daacbf6f7b | |||
32bb6d4ccb | |||
23dafb53d1 | |||
3aa853f953 | |||
409f41d3bf | |||
c4b8a71c5a | |||
3b20a109f6 | |||
6d93ae9f2d | |||
ad2f143f15 | |||
03590c7360 | |||
e3fca682d6 | |||
c94bc3ef90 | |||
731be0d5e3 | |||
a49edefbd1 | |||
e3a34399e5 | |||
ec6729db4d | |||
72b28f1d75 | |||
bdf0ac722b | |||
98aebba677 | |||
9912a623b1 | |||
98975edca1 | |||
218df461e1 | |||
86d871537b | |||
11f2ffe64d | |||
8fd7152c41 | |||
0f3d9f6c2c | |||
c2a633b549 | |||
7040d422a2 | |||
ba1380ec4c | |||
26f37f58e5 | |||
e59bdcdfbc | |||
c1ca303310 | |||
8afb515231 | |||
5eecf11b1a | |||
37ac10acd2 | |||
8b0ba0b84a | |||
cdead6fdf8 | |||
da6067ec43 | |||
a98e18d511 | |||
6394adb0f0 | |||
bc5dc02fc9 | |||
de5fd5fce0 | |||
a6d4fab595 | |||
f549b042d6 | |||
0c34c87d75 | |||
3baf6ad015 | |||
196182f9a8 | |||
379f57be2c | |||
0d8f5ee66c | |||
79492efbb4 | |||
decbbdc81b | |||
41d976d362 | |||
f94ecbfbfc | |||
f4f1e11b31 | |||
e38d52fb1b | |||
419096f603 | |||
76a53bb1f6 | |||
435aafeaae | |||
8a1603a661 | |||
66e34b7be9 | |||
9940024a01 | |||
ed217bae33 | |||
16e850e7ee | |||
850a073594 | |||
66dae6d3f8 | |||
9a50c05205 | |||
379e83a764 | |||
44da0eb50a | |||
deb652ab37 | |||
0ea67ea89a | |||
51b82df5fd | |||
3d1b009bdb | |||
46aef2c00e | |||
7d607b7348 | |||
4cb1ea94e5 | |||
729061951f | |||
38502f10bf | |||
a738dbfa9a | |||
95562a97ad | |||
c13615c4df | |||
eb6b1c1f57 | |||
64fbdf6064 | |||
47551e93b3 | |||
7a53363b1b | |||
ea0ca8c88b | |||
a10c3b061a | |||
3165d9f459 | |||
28d7ca8ca0 | |||
3f0a2bec48 | |||
d28437b658 | |||
a6d2c8b18a | |||
0c884576bd | |||
380f0662a4 | |||
bd83738168 | |||
fa68a1357d | |||
43f3917b09 | |||
8ebb749732 | |||
827ea47468 | |||
c64d466ce1 | |||
c8ae3d688f | |||
4a971841a1 | |||
9472f855b6 | |||
7736a2bf65 | |||
bef3aeed72 | |||
aacd089123 | |||
d281e929ae | |||
729ea0abff | |||
a75e0c59af | |||
b429eed46c | |||
92d2616dda | |||
37ccd33d5c | |||
20326fdaa0 | |||
6a97a0c7c8 | |||
0928b842ad | |||
e0200b2111 | |||
18a13911ba | |||
2c53d17489 | |||
2eb28c4e89 | |||
2187d9cd7e | |||
e5e6f7963b | |||
900c6e9a26 | |||
f0ee1e8aad | |||
5c6b4b5779 | |||
9b330ffd3e | |||
97d27e8110 | |||
525e3067c9 | |||
0c1fa05e9c | |||
946f3129b8 | |||
355de3fa04 | |||
61e168e444 | |||
70fa48ab07 | |||
dde95c2b61 | |||
2ca79ef898 | |||
63e3c96671 | |||
f0351489be | |||
5149d0e382 | |||
ec8982a921 | |||
cc338c2173 | |||
85b5bb438e | |||
9f727789ca | |||
b308b91f44 | |||
fc3ef3a126 | |||
3f47f89b56 | |||
e784b2dfb8 | |||
c0947b8822 | |||
a0b61bec12 | |||
d3620c00e2 | |||
0ffcee5faf | |||
c2ed8b40f0 | |||
feb1dd4715 | |||
540961ff9e | |||
5692a6201e | |||
541b8d32a8 | |||
74f9d1567f | |||
02a4d2b30a | |||
a8f174bce1 | |||
db1dd21924 | |||
005350bde0 | |||
5cd18db7a7 | |||
bb315eaafe | |||
327b834b07 | |||
39be095818 | |||
ef4973e694 | |||
7f69667171 | |||
dcdeb0cd34 | |||
8ce86ad4fd | |||
a2f1469779 | |||
dd32e082f3 | |||
74c7d86090 | |||
7fd4d50e07 | |||
7f805a1dcc | |||
919f12b8f9 | |||
94993a16aa | |||
002b0cb93f | |||
4362a842ff | |||
5702ab5f3b | |||
fac52bfda1 | |||
2ff535e7ab | |||
9698baa979 | |||
ceb7deec06 | |||
fe0b745066 | |||
8f41d3c13f | |||
f906cdb5e8 | |||
ea6facf3fc | |||
b93a4a6dae | |||
64da8c2e50 | |||
b2497ded19 | |||
5f6b2330af | |||
2bdb722986 | |||
3c79f910a2 | |||
65306e0478 | |||
49edebc1eb | |||
62263efde3 | |||
f66c958a3c | |||
2ef59cd570 | |||
6bb74b026a | |||
743a905de4 | |||
5c22949c6b | |||
651640c4d7 | |||
1d1abf5550 | |||
23267aa641 | |||
d94b4fd946 | |||
d5ff349746 | |||
2746e243c9 | |||
b3374eb66e | |||
c18abab9c4 | |||
61062e433f | |||
92e8ffd709 | |||
0b18932dd8 | |||
abeb17ce44 | |||
710470ca2d | |||
5cc991baca | |||
9eadc7fa21 | |||
ed10a89c1d | |||
0c2f72f9b1 | |||
d918b0c982 | |||
05c0c9909c | |||
92cf85084f | |||
cfa9ec775e | |||
d4d887a5a4 | |||
1b7b5a28bc | |||
6dc54ed893 | |||
2173bdb8b8 | |||
74d9428a66 | |||
dd7805cfb2 | |||
c55aa8a0e1 | |||
24ed580a97 | |||
158310bda2 | |||
2b451a19e6 | |||
bf67a4391d | |||
ea9333279b | |||
9e1a5d50af | |||
2091dabfc3 | |||
3a26d788a2 | |||
367dd3f22d | |||
404c747642 | |||
1a4ad8b7a5 | |||
4a3a3de300 | |||
7d5ea680e4 | |||
e2879a7808 | |||
d033c2bc12 | |||
fb58aeb07f | |||
3d69828610 | |||
a0fa41cd58 | |||
9162522962 | |||
42e08faaaf | |||
9e0dd3e0a5 | |||
054abee62c | |||
04d39ed973 | |||
b0f0b61d49 | |||
23f8901505 | |||
d3d966a503 | |||
48598c145f | |||
01f04c287a | |||
9e99d0370c | |||
516f822e43 | |||
fccdbf4a8e | |||
cfc1524619 | |||
3beb1ecc42 | |||
779bc34082 | |||
b6167165fe | |||
b07e89ed72 | |||
f34e1615e2 | |||
c82cad7b02 | |||
725efcc72e | |||
fa34b99976 | |||
d3819c25c5 | |||
4cb883dabf | |||
c376ff25f3 | |||
f7ada5a7e5 | |||
1bdc5126b3 | |||
a7899d1c18 | |||
895b9381ca | |||
9de0aee6f0 | |||
69f544c8d1 | |||
8cdcb209ae | |||
ab0d472c75 | |||
e266f9e36c | |||
6a1e508109 | |||
a167216730 | |||
30ae95bec8 | |||
dcc2b903ac | |||
3a904753f2 | |||
212b7b1ce9 | |||
92e647a0fd | |||
aa7d5067bc | |||
ae5a540975 | |||
eb332d5e32 | |||
b14927d605 | |||
24c288fe61 | |||
aad3a7bcf7 | |||
692dfaf0a5 | |||
36d2b2dda5 | |||
a56ff89bb9 | |||
ba795150a9 | |||
2b9eb1844d | |||
2516af9acc | |||
fd93f34538 | |||
a622393933 | |||
d9b944175a | |||
36c7b6f8f8 | |||
5f9475f750 | |||
7492ddaad7 | |||
926b87dda0 | |||
83b267bcf5 | |||
401f1ed229 | |||
049f709713 | |||
0f7866a575 | |||
aea1787908 | |||
0fc1326255 | |||
8221674098 | |||
5597bfe876 | |||
06ce4ac695 | |||
bfcd928fde | |||
58508a0260 | |||
eccea4ae54 | |||
31c1fc245f | |||
3ba58114b9 | |||
a12e5968da | |||
2ec2b0278a | |||
4afc8bc8c6 | |||
e65393f433 | |||
3e2e73f6eb | |||
ac25ee5118 | |||
0085c20e11 | |||
a77c1906bf | |||
e7605a2e17 | |||
db8bff20fe | |||
19bfffaa7c | |||
d0df9e3731 | |||
8a29f06207 | |||
cf3007b744 | |||
7f5592e00c | |||
c3d7ae33c2 | |||
aa12708738 | |||
cf125b5355 | |||
5277f7cc37 | |||
b5a0a2297e | |||
e0d31b8988 | |||
bc6f03b7c9 | |||
5817b48db9 | |||
5a56c2e9d3 | |||
7414658340 | |||
4b3b551ba7 | |||
b8e3be5018 | |||
57786bfffb | |||
a0453ca61d | |||
16bfcc44d5 | |||
c6a2785175 | |||
beb02bffb0 | |||
468ede680a | |||
42aca103ed | |||
aa95074ee0 | |||
5038095c13 | |||
3b16ff9b54 | |||
6598318fc5 | |||
e83970d326 | |||
3c87dd56ca | |||
7d3f2e8796 | |||
4cd35ef4d9 | |||
9b16dc2c6a | |||
9d90a3ca26 | |||
b761ff1bff | |||
31ad97e215 | |||
9150d036d7 | |||
1b4aee0b90 | |||
88c5999d07 | |||
f0ebeab74e | |||
352c6df339 | |||
6135fdcdb9 | |||
2334e76453 | |||
3986dac8ef | |||
0e1973e246 | |||
ae05172ad8 | |||
6eebf0aa84 | |||
c98d872d52 | |||
2a703de0cb | |||
8728cf3abf | |||
3e460c5728 | |||
f78616980a | |||
b4da457501 | |||
f2afa6bb55 | |||
7b866a2f71 | |||
3eb5c366df | |||
6f3f1fe5b9 | |||
0be54dd7ce | |||
1d091e3c4c | |||
6d12d90877 | |||
943edf7065 | |||
1d23f2736f | |||
05356af9bd | |||
818e09d4af | |||
c3a9b42fa2 | |||
651ab5f6f1 | |||
b7f05e851c | |||
ecc2e35535 | |||
ab11f67f0b | |||
56a372ee6f | |||
56fecef1bf | |||
adddc11b6f | |||
2d6e096fde | |||
2f59b3d775 | |||
294bedd29a | |||
ae9a98ca8b | |||
2891e2fef5 | |||
db20369eb9 | |||
25dfbf4724 | |||
114e175efb | |||
0facdd1af9 | |||
236cccc137 | |||
0c06776985 | |||
6508d16190 | |||
3a995ef6d1 | |||
de4fe0311c | |||
292ac9d41b | |||
a029c288b7 | |||
6490fadcae | |||
b7800b6d20 | |||
516df61633 | |||
2ad95aecb6 | |||
5ca72d75e2 | |||
073eddc050 | |||
42799fee7b | |||
02ce0f57a6 | |||
8daf0c9eda | |||
be6e5ce250 | |||
7acb54922f | |||
4e0494f15d | |||
d24aac2ab2 | |||
1b54bcd1e0 | |||
38dfe14776 | |||
ead7b67efc | |||
fae6615df4 | |||
00f6a8bd6d | |||
b553097fe4 | |||
29275fcfdd | |||
b0b52ccabe | |||
cadb6bc97b | |||
4937cf7f59 | |||
758135d19b | |||
1ae1cf5170 | |||
0ffa9c6bc4 | |||
814f4cfdad | |||
d5feec2c37 | |||
1c276ce133 | |||
6b35408144 | |||
f6513420be | |||
3da02385f9 | |||
3af15881f5 | |||
92d53e1c00 | |||
3853fda60d | |||
901263ada3 | |||
d7e3f33ecf | |||
7fdb37f2e8 | |||
457663f14a | |||
ec1f504bae | |||
6142b8d2dc | |||
537fe5dcd1 | |||
5a843dfe53 | |||
6c7e8e908e | |||
9acad90b12 | |||
f7c2c74437 | |||
137213520e | |||
1ad26db331 | |||
e4a3b13160 | |||
59fd816b0c | |||
530713ce5c | |||
b8b1c5e008 | |||
57b3a5b9b3 | |||
95dedaf976 | |||
519cb26044 | |||
77aa1caa8f | |||
6daa8ab5da | |||
c4ab496aa3 | |||
97b61b51b7 | |||
0de0416ba3 | |||
0a2b2f5218 | |||
bafc6322c2 | |||
0869bbfb27 | |||
fba1808b07 | |||
2ec110733f | |||
f3f1bfc890 | |||
67a275a009 | |||
77cadc732c | |||
f97bdea447 | |||
3442f15c6d | |||
b3003b9fb3 | |||
36d12e08c7 | |||
d7ec259e2d | |||
0798d8c6b4 | |||
8a6f36a9b0 | |||
9158209059 | |||
70cd088ffa | |||
bbc40d5534 | |||
da2b50d537 | |||
32cc8dc61b | |||
c971a484ef | |||
8c662fa1ec | |||
4205364bd8 | |||
caec5a6cbf | |||
79147786f6 | |||
cfaafc2cc3 | |||
1468c6b1d2 | |||
28b9e35d8d | |||
05c0a91f82 | |||
08a4dba80b | |||
d63bab1bf4 | |||
526ce1529b | |||
9267c9929e | |||
a8c7eebf6d | |||
fac3d2f901 | |||
550ad360d7 | |||
ca55b21a2a | |||
08f4137d71 | |||
58ec63f7b9 | |||
a5c45bb646 | |||
4471b2d64d | |||
e2ee1172ed | |||
6a6e333011 | |||
9893fa957f | |||
de794f47ba | |||
5ce2e26683 | |||
28a36052d9 | |||
d7628d46e9 | |||
2b8da4af60 | |||
8fea608b76 | |||
c13cf70814 | |||
3945359e7d | |||
a27f1c5e01 | |||
93afc2645c | |||
b5b088249c | |||
e6ec144f1f | |||
3d82fee99e | |||
39f0f7de9a | |||
f672f05db9 | |||
4d9e6a35ab | |||
c29bf6aab5 | |||
bc07978846 | |||
2efe0df2f2 | |||
38b255d41a | |||
421164986a | |||
045d933458 | |||
9de57668ab | |||
ebb28d56d5 | |||
d69cba6bef | |||
50d687e59a | |||
1274304647 | |||
837e041db8 | |||
84d7b5a62e | |||
3c2d194302 | |||
e1af6bd598 | |||
0142aa72a8 | |||
02c0c1c2f2 | |||
bf7fd42198 | |||
9203e0bf4d | |||
a3873a1710 | |||
73f8f93b17 | |||
df70050cfd | |||
9f0be223e6 | |||
eb56442d63 | |||
a259afe912 | |||
4d7b3b543b | |||
78548a2ebe | |||
815d18623e | |||
ac6ac19199 | |||
b4e780baa3 | |||
fb8d176fbe | |||
b551563681 | |||
ca4017ad1e | |||
dee302c996 | |||
6acb0f5dca | |||
00a75a33fa | |||
1413a6bcc2 | |||
87cc2594c8 | |||
cd100c87b8 | |||
c8542c9d75 | |||
a62e8eebc2 | |||
0e7c124d26 | |||
7ee8252d0e | |||
edddc80bd8 | |||
7e7e4e2eea | |||
eace26b4b3 | |||
947dcdcec5 | |||
2dade02f3e | |||
cf50738d34 | |||
dc4d592b5a | |||
b3b2ce5524 | |||
61471d6785 | |||
30061fb0e3 | |||
a03c987f69 | |||
7863833850 | |||
4398d37566 | |||
7c59d4fe3f | |||
4504b95977 | |||
90b6d13923 | |||
3d62fc4259 | |||
d4c5e367b4 | |||
eb3acad598 | |||
079f9ebbdf | |||
0c1624cc03 | |||
9539bcf8ee | |||
9bff05a635 | |||
e8559bc09c | |||
0f4c14531b | |||
0fbf300d3e | |||
1fd801403f | |||
78032f191c | |||
13c68a75c1 | |||
8175deb023 | |||
1c82b90033 | |||
d396fe8b5c | |||
1351e73961 | |||
4353c67867 | |||
8633696dc8 | |||
a5d74b92c8 | |||
f15f2bfdbd | |||
c1fcd3042e | |||
fb232f74bd | |||
c267ce47c3 | |||
3537c7e436 | |||
3b3edaced4 | |||
e46b3c7d29 | |||
241f0c894a | |||
9823729aa8 | |||
07451dcef0 | |||
d62ef9bbf3 | |||
5460ccf93d | |||
53ce6ccce2 | |||
edc3171eee | |||
0f250c6103 | |||
61e4ab1934 | |||
01d369bf38 | |||
aca0b0fe4c | |||
9ab0d8d15e | |||
3a7cb5d9b1 | |||
9de87f0959 | |||
50c280cf01 | |||
cf196430b7 | |||
de7758684b | |||
97bec71b05 | |||
cb7c15a382 | |||
6a427018e3 | |||
b72a0d12ef | |||
57061073be | |||
080b466bfc | |||
3cd66391b6 | |||
5649c5bf86 | |||
249d2056ed | |||
ebd51cc380 | |||
8b836020bf | |||
61f63dd40f | |||
4e8c079171 | |||
efcaaa56c4 | |||
7716c945d0 | |||
ef2708b510 | |||
8858b15693 | |||
90f05c477b | |||
25a099970e | |||
1ba3378574 | |||
7b78f5a997 | |||
10100ae2ca | |||
![]() |
701cd19a17 | ||
7bb857a17a | |||
3eab0c187c | |||
62a4ac0652 | |||
a4c44e8bb0 | |||
c9b48ace36 | |||
ee0bee5d7b | |||
ceba8252b0 | |||
b19fda66a2 | |||
86a2b57e22 | |||
3c99b748dc | |||
1c186450e1 | |||
f310028e42 | |||
16922279f9 | |||
4ce03ad523 | |||
45c32b087d | |||
15f36263cd | |||
ae832d0125 | |||
d25a969269 | |||
5d4c454b0b | |||
73df848f11 | |||
16c2b8f83c | |||
7af5cd9811 | |||
a10bdeb497 | |||
fc8537ba4d | |||
a8fb9095c5 | |||
f1c3dcbefc | |||
734ca84557 | |||
79299db475 | |||
15193603e3 | |||
1e4092e7f8 | |||
9fd2fb6ba2 | |||
79f1937483 | |||
0ace5eb446 | |||
b3d2345894 | |||
d5d60bcbff | |||
29206f142d | |||
1a8e0f33b9 | |||
d8845bc478 | |||
534f7d8cce | |||
6ee28e5864 | |||
9633d307a2 | |||
a545d4f047 | |||
7d7466cbc1 | |||
fc7eaa0283 | |||
d73d14bc48 | |||
215b83974c | |||
eab4eb2e5b | |||
26f20bfbb5 | |||
![]() |
4698578599 | ||
![]() |
a8d5bafc29 | ||
![]() |
a988f3e899 | ||
5d43627805 | |||
e201279eee | |||
5cbececb04 | |||
8ebd7068e2 | |||
2cd47c8ae2 | |||
53d5e7c3f0 | |||
f6265d57ec | |||
25459503d1 | |||
e8e918ff0c | |||
fb8e19403e | |||
eafdd9f755 | |||
1c654650d4 | |||
c07f7502a4 | |||
56906a0f69 | |||
cd5aaaa6cf | |||
eeaa339090 | |||
e4476d7136 | |||
![]() |
abf312a41b | ||
172db2337f | |||
f56fc232f2 | |||
83617ae287 | |||
3903b973e2 | |||
8d7f69ce80 | |||
2c875caddc | |||
3a4eea379d | |||
05fd3c4cec | |||
69c65f3ce2 | |||
709c2a401b | |||
eb6baa5e15 | |||
82621ebe0f | |||
9d115a4d02 | |||
d69ebf89ec | |||
1bab76f58b | |||
8154f9bc36 | |||
06bc0123ba | |||
506e77db6c | |||
6baad5d008 | |||
05d16f09b6 | |||
e42e2eb258 | |||
595305081a | |||
ea59665a0c | |||
bd7baef002 | |||
27ddada3f3 | |||
571d244985 | |||
76d40964db | |||
e77f8b0e51 | |||
74ed098692 | |||
7a6ffd94dc | |||
4756f54f40 | |||
e6432b5052 | |||
69734ea101 | |||
0523fbdaae | |||
e0ff240d60 | |||
c80dd1bbd0 | |||
f1c0c5a429 | |||
ff4b1e5c29 | |||
80e00b8825 | |||
b8ceb78c98 | |||
e7a9f2e2b8 | |||
52d1b72701 | |||
0c6f6e556f | |||
48aa145a20 | |||
3e54977d4b | |||
a16158aedd | |||
e4613069b3 | |||
16d98636e9 | |||
ddb1a8d665 | |||
0e1ee0c669 | |||
230c0303b1 | |||
b8ef39bada | |||
d930c3d957 | |||
![]() |
7ea02d142f | ||
![]() |
5b1e162c1c | ||
1024d0350f | |||
e41022f176 | |||
9455703bdc | |||
1c436d9466 | |||
212a5e7035 | |||
02f4d0e821 | |||
f0eea818f9 | |||
412968026e | |||
d31f9f8d24 | |||
05be199927 | |||
25c8179a88 | |||
9e21105474 | |||
00bcf5c3c5 | |||
8a4f2f4d0e | |||
44b78ecfe8 | |||
607ec836e9 | |||
9dbb0fe4bf | |||
27af54d039 | |||
b0c3e725f8 | |||
dc85be8f37 | |||
b5e0712675 | |||
3ae2338c13 | |||
494f219db3 | |||
4d1bbff99e | |||
735f0b885d | |||
8fe9b89143 | |||
3a1b407a81 | |||
8ad979ab11 | |||
7fdb59f6c9 | |||
063adb3519 | |||
45cb9c6da0 | |||
7dcb1f4fa4 | |||
42f11b2d30 | |||
103e9bc431 | |||
eeaac6f07e | |||
![]() |
d50c6b8c76 | ||
![]() |
11d1daf206 | ||
77c7522023 | |||
9be89fa9b7 | |||
0a4d620652 | |||
dda41875ae | |||
918e33f126 | |||
68335aa91b | |||
ba7da656a8 | |||
066d07f8ba | |||
d5c829e4fe | |||
412de02680 | |||
c421c7021c | |||
d1d221bb58 | |||
2175e003cc | |||
ce94c78cc1 | |||
0a31ff7b5c | |||
52c0a3123e | |||
b84258a893 | |||
9fd6ed3b21 | |||
ac09fcec5c | |||
95bd07b50f | |||
cbdd1cd0a7 | |||
987b05a334 | |||
febe7ec5e0 | |||
f2ae465dd9 | |||
c2a9fc6609 | |||
c6ed013e23 | |||
7b2c32b723 | |||
37f6fe9323 | |||
0398691e01 | |||
c6c3e1c005 | |||
7b894823cc | |||
![]() |
d1cf2953df | ||
![]() |
b544d6159b | ||
![]() |
46332bf3e0 | ||
![]() |
5ddc1aea85 | ||
![]() |
fe7051f855 | ||
d9c94664c3 | |||
8a43eae230 | |||
144ac5850c | |||
b0b2d9f6fa | |||
d824b83cf9 | |||
259bfa1362 | |||
0dd98393a5 | |||
cd514d47af | |||
38e7d3d078 | |||
![]() |
e98514a7be | ||
1b95186ab8 | |||
![]() |
1a029ebb4b | ||
c20e3ea01d | |||
ba349535fb | |||
abc3bd8624 | |||
![]() |
76964585eb | ||
![]() |
59679c3d56 | ||
07c2fbcadf | |||
6c4c685690 | |||
cc5713e375 | |||
de6c1da55d | |||
26a5f31ee9 | |||
5fb4b94bb4 | |||
![]() |
533c249e68 | ||
d9c95e676d | |||
0c31eb4b25 | |||
f9ed123cb4 | |||
b7f381ba1d | |||
59389a991b | |||
59cec40443 |
19
.coderabbit.yaml
Normal file
19
.coderabbit.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||||
|
language: "en-US"
|
||||||
|
early_access: false
|
||||||
|
reviews:
|
||||||
|
profile: "chill"
|
||||||
|
request_changes_workflow: false
|
||||||
|
changed_files_summary: false
|
||||||
|
high_level_summary: false
|
||||||
|
poem: false
|
||||||
|
review_status: true
|
||||||
|
commit_status: false
|
||||||
|
collapse_walkthrough: false
|
||||||
|
sequence_diagrams: false
|
||||||
|
related_prs: false
|
||||||
|
auto_review:
|
||||||
|
enabled: true
|
||||||
|
drafts: false
|
||||||
|
chat:
|
||||||
|
auto_reply: true
|
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
**/.dockerignore
|
||||||
|
**/.git
|
||||||
|
Dockerfile*
|
||||||
|
.*
|
||||||
|
docker-data/
|
@ -1,4 +0,0 @@
|
|||||||
# Docker may need a .env file for the following settings:
|
|
||||||
DATABASE_PORT=27017
|
|
||||||
DATABASE_USERNAME=root
|
|
||||||
DATABASE_PASSWORD=database
|
|
15
.eslintrc
15
.eslintrc
@ -12,21 +12,22 @@
|
|||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/explicit-function-return-type": "warn",
|
"@typescript-eslint/explicit-function-return-type": "warn",
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "warn",
|
|
||||||
"@typescript-eslint/restrict-template-expressions": "warn",
|
"@typescript-eslint/restrict-template-expressions": "warn",
|
||||||
"@typescript-eslint/restrict-plus-operands": "warn",
|
"@typescript-eslint/restrict-plus-operands": "warn",
|
||||||
"@typescript-eslint/no-unsafe-member-access": "warn",
|
"@typescript-eslint/no-unsafe-member-access": "warn",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
|
||||||
"@typescript-eslint/no-misused-promises": "warn",
|
|
||||||
"@typescript-eslint/no-unsafe-argument": "error",
|
"@typescript-eslint/no-unsafe-argument": "error",
|
||||||
"@typescript-eslint/no-unsafe-call": "warn",
|
"@typescript-eslint/no-unsafe-call": "warn",
|
||||||
"@typescript-eslint/no-unsafe-assignment": "warn",
|
"@typescript-eslint/no-unsafe-assignment": "warn",
|
||||||
"@typescript-eslint/no-explicit-any": "warn",
|
"@typescript-eslint/no-explicit-any": "warn",
|
||||||
"@typescript-eslint/no-loss-of-precision": "warn",
|
"no-loss-of-precision": "warn",
|
||||||
"no-case-declarations": "warn",
|
"@typescript-eslint/no-unnecessary-condition": "warn",
|
||||||
|
"@typescript-eslint/no-base-to-string": "off",
|
||||||
|
"no-case-declarations": "error",
|
||||||
"prettier/prettier": "error",
|
"prettier/prettier": "error",
|
||||||
"@typescript-eslint/semi": "error",
|
"no-mixed-spaces-and-tabs": "error",
|
||||||
"no-mixed-spaces-and-tabs": "error"
|
"require-await": "off",
|
||||||
|
"@typescript-eslint/require-await": "error"
|
||||||
},
|
},
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
|
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1,4 +1,4 @@
|
|||||||
# Auto detect text files and perform LF normalization
|
# Auto detect text files and perform LF normalization
|
||||||
* text=auto
|
* text=auto eol=lf
|
||||||
|
|
||||||
static/webui/libs/ linguist-vendored
|
static/webui/libs/ linguist-vendored
|
||||||
|
20
.github/workflows/build.yml
vendored
20
.github/workflows/build.yml
vendored
@ -5,18 +5,22 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
version: [18, 20, 22]
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.1.2
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Setup Node.js environment
|
- name: Setup Node.js environment
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.version }}
|
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- run: echo '{"version":"","buildLabel":"","matchmakingBuildId":""}' > static/data/buildConfig.json
|
- run: npm run verify
|
||||||
- run: npm run build
|
- run: npm run lint:ci
|
||||||
- run: npm run lint
|
- run: npm run prettier
|
||||||
|
- run: npm run update-translations
|
||||||
|
- name: Fail if there are uncommitted changes
|
||||||
|
run: |
|
||||||
|
if [[ -n "$(git status --porcelain)" ]]; then
|
||||||
|
echo "Uncommitted changes detected:"
|
||||||
|
git status
|
||||||
|
git --no-pager diff
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
25
.github/workflows/docker.yml
vendored
Normal file
25
.github/workflows/docker.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: Build Docker image
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
if: github.repository == 'OpenWF/SpaceNinjaServer'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Docker buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Log in to container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: openwf
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
openwf/spaceninjaserver:latest
|
||||||
|
openwf/spaceninjaserver:${{ github.sha }}
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -16,3 +16,6 @@ yarn.lock
|
|||||||
|
|
||||||
# MongoDB VSCode extension playground scripts
|
# MongoDB VSCode extension playground scripts
|
||||||
/database_scripts
|
/database_scripts
|
||||||
|
|
||||||
|
# Default Docker directory
|
||||||
|
/docker-data
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
|
src/routes/api.ts
|
||||||
static/webui/libs/
|
static/webui/libs/
|
||||||
*.html
|
*.html
|
||||||
|
*.md
|
||||||
|
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["dbaeumer.vscode-eslint"]
|
||||||
|
}
|
19
.vscode/launch.json
vendored
Normal file
19
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug and Watch",
|
||||||
|
"runtimeArgs": ["-r", "tsconfig-paths/register", "-r", "ts-node/register", "--watch-path", "src"],
|
||||||
|
"args": ["${workspaceFolder}/src/index.ts"],
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
//can use "console": "internalConsole" for VS Code's Debug Console. For that, forceConsole in logger.ts is needed to be true
|
||||||
|
//"internalConsoleOptions": "openOnSessionStart" can be useful then
|
50
Dockerfile
50
Dockerfile
@ -1,5 +1,49 @@
|
|||||||
FROM mongo as base
|
FROM node:18-alpine3.19
|
||||||
|
|
||||||
EXPOSE 27017
|
ENV APP_MONGODB_URL=mongodb://mongodb:27017/openWF
|
||||||
|
ENV APP_MY_ADDRESS=localhost
|
||||||
|
ENV APP_HTTP_PORT=80
|
||||||
|
ENV APP_HTTPS_PORT=443
|
||||||
|
ENV APP_AUTO_CREATE_ACCOUNT=true
|
||||||
|
ENV APP_SKIP_TUTORIAL=false
|
||||||
|
ENV APP_SKIP_ALL_DIALOGUE=false
|
||||||
|
ENV APP_UNLOCK_ALL_SCANS=false
|
||||||
|
ENV APP_UNLOCK_ALL_MISSIONS=false
|
||||||
|
ENV APP_INFINITE_CREDITS=false
|
||||||
|
ENV APP_INFINITE_PLATINUM=false
|
||||||
|
ENV APP_INFINITE_ENDO=false
|
||||||
|
ENV APP_INFINITE_REGAL_AYA=false
|
||||||
|
ENV APP_INFINITE_HELMINTH_MATERIALS=false
|
||||||
|
ENV APP_DONT_SUBTRACT_CONSUMABLES=false
|
||||||
|
ENV APP_UNLOCK_ALL_SHIP_FEATURES=false
|
||||||
|
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=false
|
||||||
|
ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=false
|
||||||
|
ENV APP_UNLOCK_ALL_SKINS=false
|
||||||
|
ENV APP_UNLOCK_ALL_CAPTURA_SCENES=false
|
||||||
|
ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=false
|
||||||
|
ENV APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE=false
|
||||||
|
ENV APP_UNLOCK_EXILUS_EVERYWHERE=false
|
||||||
|
ENV APP_UNLOCK_ARCANES_EVERYWHERE=false
|
||||||
|
ENV APP_NO_DAILY_FOCUS_LIMIT=false
|
||||||
|
ENV APP_NO_ARGON_CRYSTAL_DECAY=false
|
||||||
|
ENV APP_NO_MASTERY_RANK_UP_COOLDOWN=false
|
||||||
|
ENV APP_NO_VENDOR_PURCHASE_LIMITS=true
|
||||||
|
ENV APP_NO_DEATH_MARKS=false
|
||||||
|
ENV APP_NO_KIM_COOLDOWNS=false
|
||||||
|
ENV APP_INSTANT_RESOURCE_EXTRACTOR_DRONES=false
|
||||||
|
ENV APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE=false
|
||||||
|
ENV APP_SKIP_CLAN_KEY_CRAFTING=false
|
||||||
|
ENV APP_NO_DOJO_ROOM_BUILD_STAGE=false
|
||||||
|
ENV APP_NO_DECO_BUILD_STAGE=false
|
||||||
|
ENV APP_FAST_DOJO_ROOM_DESTRUCTION=false
|
||||||
|
ENV APP_NO_DOJO_RESEARCH_COSTS=false
|
||||||
|
ENV APP_NO_DOJO_RESEARCH_TIME=false
|
||||||
|
ENV APP_FAST_CLAN_ASCENSION=false
|
||||||
|
ENV APP_SPOOF_MASTERY_RANK=-1
|
||||||
|
|
||||||
CMD ["mongod"]
|
RUN apk add --no-cache bash sed wget jq
|
||||||
|
|
||||||
|
COPY . /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||||
|
14
README.md
14
README.md
@ -1,3 +1,17 @@
|
|||||||
# Space Ninja Server
|
# Space Ninja Server
|
||||||
|
|
||||||
More information for the moment here: [https://discord.gg/PNNZ3asUuY](https://discord.gg/PNNZ3asUuY)
|
More information for the moment here: [https://discord.gg/PNNZ3asUuY](https://discord.gg/PNNZ3asUuY)
|
||||||
|
|
||||||
|
## Project Status
|
||||||
|
|
||||||
|
This project is in active development at <https://onlyg.it/OpenWF/SpaceNinjaServer>.
|
||||||
|
|
||||||
|
To get an idea of what functionality you can expect to be missing [have a look through the issues](https://onlyg.it/OpenWF/SpaceNinjaServer/issues?q=&type=all&state=open&labels=-4%2C-10&milestone=0&assignee=0&poster=). However, many things have been implemented and *should* work as expected. Please open an issue for anything where that's not the case and/or the server is reporting errors.
|
||||||
|
|
||||||
|
## config.json
|
||||||
|
|
||||||
|
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config.json.example](config.json.example), which has most cheats disabled.
|
||||||
|
|
||||||
|
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
||||||
|
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
|
||||||
|
- `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE.
|
||||||
|
25
UPDATE AND START SERVER.bat
Normal file
25
UPDATE AND START SERVER.bat
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
echo Updating SpaceNinjaServer...
|
||||||
|
git fetch --prune
|
||||||
|
git stash
|
||||||
|
git reset --hard origin/main
|
||||||
|
|
||||||
|
if exist static\data\0\ (
|
||||||
|
echo Updating stripped assets...
|
||||||
|
cd static\data\0\
|
||||||
|
git pull
|
||||||
|
cd ..\..\..\
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Updating dependencies...
|
||||||
|
call npm i --omit=dev
|
||||||
|
|
||||||
|
call npm run build
|
||||||
|
if %errorlevel% == 0 (
|
||||||
|
call npm run start
|
||||||
|
echo SpaceNinjaServer seems to have crashed.
|
||||||
|
)
|
||||||
|
:a
|
||||||
|
pause > nul
|
||||||
|
goto a
|
@ -2,25 +2,55 @@
|
|||||||
"mongodbUrl": "mongodb://127.0.0.1:27017/openWF",
|
"mongodbUrl": "mongodb://127.0.0.1:27017/openWF",
|
||||||
"logger": {
|
"logger": {
|
||||||
"files": true,
|
"files": true,
|
||||||
"level": "trace",
|
"level": "trace"
|
||||||
"__valid_levels": "fatal, error, warn, info, http, debug, trace"
|
|
||||||
},
|
},
|
||||||
"myAddress": "localhost",
|
"myAddress": "localhost",
|
||||||
"httpPort": 80,
|
"httpPort": 80,
|
||||||
"httpsPort": 443,
|
"httpsPort": 443,
|
||||||
|
"NRS": ["localhost"],
|
||||||
|
"administratorNames": [],
|
||||||
"autoCreateAccount": true,
|
"autoCreateAccount": true,
|
||||||
"skipStoryModeChoice": true,
|
"skipTutorial": false,
|
||||||
"skipTutorial": true,
|
"skipAllDialogue": false,
|
||||||
"skipAllDialogue": true,
|
"unlockAllScans": false,
|
||||||
"unlockAllScans": true,
|
"unlockAllMissions": false,
|
||||||
"unlockAllMissions": true,
|
"infiniteCredits": false,
|
||||||
"unlockAllQuests": true,
|
"infinitePlatinum": false,
|
||||||
"completeAllQuests": false,
|
"infiniteEndo": false,
|
||||||
"infiniteResources": true,
|
"infiniteRegalAya": false,
|
||||||
"unlockAllShipFeatures": true,
|
"infiniteHelminthMaterials": false,
|
||||||
"unlockAllShipDecorations": true,
|
"dontSubtractConsumables": false,
|
||||||
"unlockAllFlavourItems": true,
|
"unlockAllShipFeatures": false,
|
||||||
"unlockAllSkins": true,
|
"unlockAllShipDecorations": false,
|
||||||
"universalPolarityEverywhere": true,
|
"unlockAllFlavourItems": false,
|
||||||
"spoofMasteryRank": -1
|
"unlockAllSkins": false,
|
||||||
|
"unlockAllCapturaScenes": false,
|
||||||
|
"universalPolarityEverywhere": false,
|
||||||
|
"unlockDoubleCapacityPotatoesEverywhere": false,
|
||||||
|
"unlockExilusEverywhere": false,
|
||||||
|
"unlockArcanesEverywhere": false,
|
||||||
|
"noDailyStandingLimits": false,
|
||||||
|
"noDailyFocusLimit": false,
|
||||||
|
"noArgonCrystalDecay": false,
|
||||||
|
"noMasteryRankUpCooldown": false,
|
||||||
|
"noVendorPurchaseLimits": true,
|
||||||
|
"noDeathMarks": false,
|
||||||
|
"noKimCooldowns": false,
|
||||||
|
"instantResourceExtractorDrones": false,
|
||||||
|
"noResourceExtractorDronesDamage": false,
|
||||||
|
"skipClanKeyCrafting": false,
|
||||||
|
"noDojoRoomBuildStage": false,
|
||||||
|
"noDecoBuildStage": false,
|
||||||
|
"fastDojoRoomDestruction": false,
|
||||||
|
"noDojoResearchCosts": false,
|
||||||
|
"noDojoResearchTime": false,
|
||||||
|
"fastClanAscension": false,
|
||||||
|
"spoofMasteryRank": -1,
|
||||||
|
"worldState": {
|
||||||
|
"creditBoost": false,
|
||||||
|
"affinityBoost": false,
|
||||||
|
"resourceBoost": false,
|
||||||
|
"starDays": true,
|
||||||
|
"lockTime": 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,64 @@
|
|||||||
version: "3.9"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
mongodb:
|
spaceninjaserver:
|
||||||
container_name: mongodb
|
# build: .
|
||||||
image: mongodb
|
image: openwf/spaceninjaserver:latest
|
||||||
restart: always
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
target: base
|
|
||||||
environment:
|
environment:
|
||||||
MONGO_INITDB_ROOT_USERNAME: ${DATABASE_USERNAME}
|
APP_MONGODB_URL: mongodb://openwfagent:spaceninjaserver@mongodb:27017/
|
||||||
MONGO_INITDB_ROOT_PASSWORD: ${DATABASE_PASSWORD}
|
|
||||||
ports:
|
|
||||||
- ${DATABASE_PORT}:${DATABASE_PORT}
|
|
||||||
expose:
|
|
||||||
- "${DATABASE_PORT}"
|
|
||||||
networks:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
networks:
|
# Following environment variables are set to default image values.
|
||||||
docker:
|
# Uncomment to edit.
|
||||||
external: true
|
|
||||||
|
# APP_MY_ADDRESS: localhost
|
||||||
|
# APP_HTTP_PORT: 80
|
||||||
|
# APP_HTTPS_PORT: 443
|
||||||
|
# APP_AUTO_CREATE_ACCOUNT: true
|
||||||
|
# APP_SKIP_TUTORIAL: false
|
||||||
|
# APP_SKIP_ALL_DIALOGUE: false
|
||||||
|
# APP_UNLOCK_ALL_SCANS: false
|
||||||
|
# APP_UNLOCK_ALL_MISSIONS: false
|
||||||
|
# APP_INFINITE_CREDITS: false
|
||||||
|
# APP_INFINITE_PLATINUM: false
|
||||||
|
# APP_INFINITE_ENDO: false
|
||||||
|
# APP_INFINITE_REGAL_AYA: false
|
||||||
|
# APP_INFINITE_HELMINTH_MATERIALS: false
|
||||||
|
# APP_DONT_SUBTRACT_CONSUMABLES: false
|
||||||
|
# APP_UNLOCK_ALL_SHIP_FEATURES: false
|
||||||
|
# APP_UNLOCK_ALL_SHIP_DECORATIONS: false
|
||||||
|
# APP_UNLOCK_ALL_FLAVOUR_ITEMS: false
|
||||||
|
# APP_UNLOCK_ALL_SKINS: false
|
||||||
|
# APP_UNLOCK_ALL_CAPTURA_SCENES: false
|
||||||
|
# APP_UNIVERSAL_POLARITY_EVERYWHERE: false
|
||||||
|
# APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE: false
|
||||||
|
# APP_UNLOCK_EXILUS_EVERYWHERE: false
|
||||||
|
# APP_UNLOCK_ARCANES_EVERYWHERE: false
|
||||||
|
# APP_NO_DAILY_FOCUS_LIMIT: false
|
||||||
|
# APP_NO_ARGON_CRYSTAL_DECAY: false
|
||||||
|
# APP_NO_MASTERY_RANK_UP_COOLDOWN: false
|
||||||
|
# APP_NO_VENDOR_PURCHASE_LIMITS: true
|
||||||
|
# APP_NO_DEATH_MARKS: false
|
||||||
|
# APP_NO_KIM_COOLDOWNS: false
|
||||||
|
# APP_INSTANT_RESOURCE_EXTRACTOR_DRONES: false
|
||||||
|
# APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE: false
|
||||||
|
# APP_SKIP_CLAN_KEY_CRAFTING: false
|
||||||
|
# APP_NO_DOJO_ROOM_BUILD_STAGE: false
|
||||||
|
# APP_NO_DECO_BUILD_STAGE: false
|
||||||
|
# APP_FAST_DOJO_ROOM_DESTRUCTION: false
|
||||||
|
# APP_NO_DOJO_RESEARCH_COSTS: false
|
||||||
|
# APP_NO_DOJO_RESEARCH_TIME: false
|
||||||
|
# APP_FAST_CLAN_ASCENSION: false
|
||||||
|
# APP_SPOOF_MASTERY_RANK: -1
|
||||||
|
volumes:
|
||||||
|
- ./docker-data/static:/app/static/data
|
||||||
|
- ./docker-data/logs:/app/logs
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
- 443:443
|
||||||
|
depends_on:
|
||||||
|
- mongodb
|
||||||
|
mongodb:
|
||||||
|
image: docker.io/library/mongo:8.0.0-noble
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: openwfagent
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver
|
||||||
|
volumes:
|
||||||
|
- ./docker-data/database:/data/db
|
||||||
|
24
docker-entrypoint.sh
Executable file
24
docker-entrypoint.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Set up the configuration file using environment variables.
|
||||||
|
echo '{
|
||||||
|
"logger": {
|
||||||
|
"files": true,
|
||||||
|
"level": "trace",
|
||||||
|
"__valid_levels": "fatal, error, warn, info, http, debug, trace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' > config.json
|
||||||
|
|
||||||
|
for config in $(env | grep "APP_")
|
||||||
|
do
|
||||||
|
var=$(echo "${config}" | tr '[:upper:]' '[:lower:]' | sed 's/app_//g' | sed -E 's/_([a-z])/\U\1/g' | sed 's/=.*//g')
|
||||||
|
val=$(echo "${config}" | sed 's/.*=//g')
|
||||||
|
jq --arg variable "$var" --arg value "$val" '.[$variable] += try [$value|fromjson][] catch $value' config.json > config.tmp
|
||||||
|
mv config.tmp config.json
|
||||||
|
done
|
||||||
|
|
||||||
|
npm i --omit=dev
|
||||||
|
npm run build
|
||||||
|
exec npm run start
|
2524
package-lock.json
generated
2524
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
@ -4,35 +4,41 @@
|
|||||||
"description": "WF Emulator",
|
"description": "WF Emulator",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --import ./build/src/pathman.js build/src/index.js",
|
"start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js",
|
||||||
"dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
|
"dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
|
||||||
"build": "tsc && copyfiles static/webui/** build",
|
"build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
|
||||||
|
"verify": "tsgo --noEmit",
|
||||||
"lint": "eslint --ext .ts .",
|
"lint": "eslint --ext .ts .",
|
||||||
|
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
|
||||||
"lint:fix": "eslint --fix --ext .ts .",
|
"lint:fix": "eslint --fix --ext .ts .",
|
||||||
"prettier": "prettier --write ."
|
"prettier": "prettier --write .",
|
||||||
|
"update-translations": "cd scripts && node update-translations.js"
|
||||||
},
|
},
|
||||||
"license": "GNU",
|
"license": "GNU",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"copyfiles": "^2.4.1",
|
"@types/express": "^5",
|
||||||
"express": "^5.0.0-beta.3",
|
"@types/morgan": "^1.9.9",
|
||||||
"mongoose": "^8.4.5",
|
"crc-32": "^1.2.2",
|
||||||
"warframe-public-export-plus": "^0.4.4",
|
"express": "^5",
|
||||||
"warframe-riven-info": "^0.1.1",
|
"json-with-bigint": "^3.2.2",
|
||||||
"winston": "^3.13.0",
|
"mongoose": "^8.11.0",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"ncp": "^2.0.0",
|
||||||
|
"typescript": "^5.5",
|
||||||
|
"warframe-public-export-plus": "^0.5.59",
|
||||||
|
"warframe-riven-info": "^0.1.2",
|
||||||
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0"
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.20",
|
"@rxliuli/tsgo": "^2025.3.31",
|
||||||
"@types/morgan": "^1.9.9",
|
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.14",
|
"@typescript-eslint/parser": "^8.28.0",
|
||||||
"@typescript-eslint/parser": "^7.14",
|
"eslint": "^8",
|
||||||
"eslint": "^8.56.0",
|
"eslint-plugin-prettier": "^5.2.5",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"prettier": "^3.5.3",
|
||||||
"morgan": "^1.10.0",
|
|
||||||
"prettier": "^3.3.2",
|
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0"
|
||||||
"typescript": "^5.5"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.15.0",
|
"node": ">=18.15.0",
|
||||||
|
46
scripts/update-translations.js
Normal file
46
scripts/update-translations.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php
|
||||||
|
// Converted via ChatGPT-4o
|
||||||
|
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
function extractStrings(content) {
|
||||||
|
const regex = /([a-zA-Z0-9_]+): `([^`]*)`,/g;
|
||||||
|
let matches;
|
||||||
|
const strings = {};
|
||||||
|
while ((matches = regex.exec(content)) !== null) {
|
||||||
|
strings[matches[1]] = matches[2];
|
||||||
|
}
|
||||||
|
return strings;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = fs.readFileSync("../static/webui/translations/en.js", "utf8");
|
||||||
|
const sourceStrings = extractStrings(source);
|
||||||
|
const sourceLines = source.substring(0, source.length - 1).split("\n");
|
||||||
|
|
||||||
|
fs.readdirSync("../static/webui/translations").forEach(file => {
|
||||||
|
if (fs.lstatSync(`../static/webui/translations/${file}`).isFile() && file !== "en.js") {
|
||||||
|
const content = fs.readFileSync(`../static/webui/translations/${file}`, "utf8");
|
||||||
|
const targetStrings = extractStrings(content);
|
||||||
|
const contentLines = content.split("\n");
|
||||||
|
|
||||||
|
const fileHandle = fs.openSync(`../static/webui/translations/${file}`, "w");
|
||||||
|
fs.writeSync(fileHandle, contentLines[0] + "\n");
|
||||||
|
|
||||||
|
sourceLines.forEach(line => {
|
||||||
|
const strings = extractStrings(line);
|
||||||
|
if (Object.keys(strings).length > 0) {
|
||||||
|
Object.entries(strings).forEach(([key, value]) => {
|
||||||
|
if (targetStrings.hasOwnProperty(key)) {
|
||||||
|
fs.writeSync(fileHandle, ` ${key}: \`${targetStrings[key]}\`,\n`);
|
||||||
|
} else {
|
||||||
|
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fs.writeSync(fileHandle, line + "\n");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.closeSync(fileHandle);
|
||||||
|
}
|
||||||
|
});
|
35
src/app.ts
35
src/app.ts
@ -1,45 +1,44 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
|
|
||||||
|
import bodyParser from "body-parser";
|
||||||
import { unknownEndpointHandler } from "@/src/middleware/middleware";
|
import { unknownEndpointHandler } from "@/src/middleware/middleware";
|
||||||
import { requestLogger } from "@/src/middleware/morgenMiddleware";
|
import { requestLogger } from "@/src/middleware/morgenMiddleware";
|
||||||
|
import { errorHandler } from "@/src/middleware/errorHandler";
|
||||||
|
|
||||||
import { apiRouter } from "@/src/routes/api";
|
import { apiRouter } from "@/src/routes/api";
|
||||||
//import { testRouter } from "@/src/routes/test";
|
|
||||||
import { cacheRouter } from "@/src/routes/cache";
|
import { cacheRouter } from "@/src/routes/cache";
|
||||||
import bodyParser from "body-parser";
|
|
||||||
|
|
||||||
import { steamPacksController } from "@/src/controllers/misc/steamPacksController";
|
|
||||||
import { customRouter } from "@/src/routes/custom";
|
import { customRouter } from "@/src/routes/custom";
|
||||||
import { dynamicController } from "@/src/routes/dynamic";
|
import { dynamicController } from "@/src/routes/dynamic";
|
||||||
|
import { payRouter } from "@/src/routes/pay";
|
||||||
import { statsRouter } from "@/src/routes/stats";
|
import { statsRouter } from "@/src/routes/stats";
|
||||||
import { webuiRouter } from "@/src/routes/webui";
|
import { webuiRouter } from "@/src/routes/webui";
|
||||||
import { connectDatabase } from "@/src/services/mongoService";
|
|
||||||
import { registerLogFileCreationListener } from "@/src/utils/logger";
|
|
||||||
|
|
||||||
void registerLogFileCreationListener();
|
|
||||||
void connectDatabase();
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
app.use((req, _res, next) => {
|
||||||
|
// 38.5.0 introduced "ezip" for encrypted body blobs and "e" for request verification only (encrypted body blobs with no application data).
|
||||||
|
// The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
|
||||||
|
if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
|
||||||
|
req.headers["content-encoding"] = undefined;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
app.use(bodyParser.raw());
|
app.use(bodyParser.raw());
|
||||||
app.use(express.json());
|
app.use(express.json({ limit: "4mb" }));
|
||||||
app.use(bodyParser.text());
|
app.use(bodyParser.text({ limit: "4mb" }));
|
||||||
app.use(requestLogger);
|
app.use(requestLogger);
|
||||||
//app.use(requestLogger);
|
|
||||||
|
|
||||||
app.use("/api", apiRouter);
|
app.use("/api", apiRouter);
|
||||||
//app.use("/test", testRouter);
|
|
||||||
app.use("/", cacheRouter);
|
app.use("/", cacheRouter);
|
||||||
app.use("/custom", customRouter);
|
app.use("/custom", customRouter);
|
||||||
|
app.use("/dynamic", dynamicController);
|
||||||
app.use("/:id/dynamic", dynamicController);
|
app.use("/:id/dynamic", dynamicController);
|
||||||
|
app.use("/pay", payRouter);
|
||||||
app.post("/pay/steamPacks.php", steamPacksController);
|
|
||||||
app.use("/stats", statsRouter);
|
app.use("/stats", statsRouter);
|
||||||
|
|
||||||
app.use("/", webuiRouter);
|
app.use("/", webuiRouter);
|
||||||
|
|
||||||
app.use(unknownEndpointHandler);
|
app.use(unknownEndpointHandler);
|
||||||
|
app.use(errorHandler);
|
||||||
//app.use(errorHandler)
|
|
||||||
|
|
||||||
export { app };
|
export { app };
|
||||||
|
@ -2,15 +2,18 @@ const millisecondsPerSecond = 1000;
|
|||||||
const secondsPerMinute = 60;
|
const secondsPerMinute = 60;
|
||||||
const minutesPerHour = 60;
|
const minutesPerHour = 60;
|
||||||
const hoursPerDay = 24;
|
const hoursPerDay = 24;
|
||||||
|
const daysPerWeek = 7;
|
||||||
|
|
||||||
const unixSecond = millisecondsPerSecond;
|
const unixSecond = millisecondsPerSecond;
|
||||||
const unixMinute = secondsPerMinute * millisecondsPerSecond;
|
const unixMinute = secondsPerMinute * millisecondsPerSecond;
|
||||||
const unixHour = unixMinute * minutesPerHour;
|
const unixHour = unixMinute * minutesPerHour;
|
||||||
const unixDay = hoursPerDay * unixHour;
|
const unixDay = hoursPerDay * unixHour;
|
||||||
|
const unixWeek = daysPerWeek * unixDay;
|
||||||
|
|
||||||
export const unixTimesInMs = {
|
export const unixTimesInMs = {
|
||||||
second: unixSecond,
|
second: unixSecond,
|
||||||
minute: unixMinute,
|
minute: unixMinute,
|
||||||
hour: unixHour,
|
hour: unixHour,
|
||||||
day: unixDay
|
day: unixDay,
|
||||||
|
week: unixWeek
|
||||||
};
|
};
|
||||||
|
11
src/controllers/api/abandonLibraryDailyTaskController.ts
Normal file
11
src/controllers/api/abandonLibraryDailyTaskController.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const abandonLibraryDailyTaskController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
inventory.LibraryActiveDailyTaskInfo = undefined;
|
||||||
|
await inventory.save();
|
||||||
|
res.status(200).end();
|
||||||
|
};
|
46
src/controllers/api/abortDojoComponentController.ts
Normal file
46
src/controllers/api/abortDojoComponentController.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
getDojoClient,
|
||||||
|
getGuildForRequestEx,
|
||||||
|
hasAccessToDojo,
|
||||||
|
hasGuildPermission,
|
||||||
|
removeDojoDeco,
|
||||||
|
removeDojoRoom
|
||||||
|
} from "@/src/services/guildService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const abortDojoComponentController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId LevelKeys");
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
const request = JSON.parse(String(req.body)) as IAbortDojoComponentRequest;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!hasAccessToDojo(inventory) ||
|
||||||
|
!(await hasGuildPermission(
|
||||||
|
guild,
|
||||||
|
accountId,
|
||||||
|
request.DecoId ? GuildPermission.Decorator : GuildPermission.Architect
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
res.json({ DojoRequestStatus: -1 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.DecoId) {
|
||||||
|
removeDojoDeco(guild, request.ComponentId, request.DecoId);
|
||||||
|
} else {
|
||||||
|
await removeDojoRoom(guild, request.ComponentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await guild.save();
|
||||||
|
res.json(await getDojoClient(guild, 0, request.ComponentId));
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IAbortDojoComponentRequest {
|
||||||
|
DecoType?: string;
|
||||||
|
ComponentId: string;
|
||||||
|
DecoId?: string;
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const abortDojoComponentDestructionController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId LevelKeys");
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
|
||||||
|
res.json({ DojoRequestStatus: -1 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const componentId = req.query.componentId as string;
|
||||||
|
|
||||||
|
guild.DojoComponents.id(componentId)!.DestructionTime = undefined;
|
||||||
|
|
||||||
|
await guild.save();
|
||||||
|
res.json(await getDojoClient(guild, 0, componentId));
|
||||||
|
};
|
40
src/controllers/api/activateRandomModController.ts
Normal file
40
src/controllers/api/activateRandomModController.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { addMods, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getRandomElement } from "@/src/services/rngService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportUpgrades } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const activateRandomModController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const request = getJSONfromString<IActiveRandomModRequest>(String(req.body));
|
||||||
|
addMods(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: request.ItemType,
|
||||||
|
ItemCount: -1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType])!;
|
||||||
|
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||||
|
const upgradeIndex =
|
||||||
|
inventory.Upgrades.push({
|
||||||
|
ItemType: rivenType,
|
||||||
|
UpgradeFingerprint: JSON.stringify(fingerprint)
|
||||||
|
}) - 1;
|
||||||
|
await inventory.save();
|
||||||
|
// For some reason, in this response, the UpgradeFingerprint is simply a nested object and not a string
|
||||||
|
res.json({
|
||||||
|
NewMod: {
|
||||||
|
UpgradeFingerprint: fingerprint,
|
||||||
|
ItemType: rivenType,
|
||||||
|
ItemId: toOid(inventory.Upgrades[upgradeIndex]._id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IActiveRandomModRequest {
|
||||||
|
ItemType: string;
|
||||||
|
}
|
@ -1,17 +1,25 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { IUpdateGlyphRequest } from "@/src/types/requestTypes";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
export const addFriendImageController: RequestHandler = async (req, res) => {
|
||||||
const addFriendImageController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const json = getJSONfromString(String(req.body)) as IUpdateGlyphRequest;
|
const json = getJSONfromString<IUpdateGlyphRequest>(String(req.body));
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
inventory.ActiveAvatarImageType = json.AvatarImageType;
|
await Inventory.updateOne(
|
||||||
await inventory.save();
|
{
|
||||||
|
accountOwnerId: accountId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ActiveAvatarImageType: json.AvatarImageType
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
res.json({});
|
res.json({});
|
||||||
};
|
};
|
||||||
|
|
||||||
export { addFriendImageController };
|
interface IUpdateGlyphRequest {
|
||||||
|
AvatarImageType: string;
|
||||||
|
AvatarImage: string;
|
||||||
|
}
|
||||||
|
30
src/controllers/api/addIgnoredUserController.ts
Normal file
30
src/controllers/api/addIgnoredUserController.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Account, Ignore } from "@/src/models/loginModel";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IFriendInfo } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const addIgnoredUserController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const data = getJSONfromString<IAddIgnoredUserRequest>(String(req.body));
|
||||||
|
const ignoreeAccount = await Account.findOne(
|
||||||
|
{ DisplayName: data.playerName.substring(0, data.playerName.length - 1) },
|
||||||
|
"_id"
|
||||||
|
);
|
||||||
|
if (ignoreeAccount) {
|
||||||
|
await Ignore.create({ ignorer: accountId, ignoree: ignoreeAccount._id });
|
||||||
|
res.json({
|
||||||
|
Ignored: {
|
||||||
|
_id: toOid(ignoreeAccount._id),
|
||||||
|
DisplayName: data.playerName
|
||||||
|
} satisfies IFriendInfo
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(400).end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IAddIgnoredUserRequest {
|
||||||
|
playerName: string;
|
||||||
|
}
|
117
src/controllers/api/addToAllianceController.ts
Normal file
117
src/controllers/api/addToAllianceController.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import { getJSONfromString, regexEscape } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportFlavour } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const addToAllianceController: RequestHandler = async (req, res) => {
|
||||||
|
// Check requester is a warlord in their guild
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
|
||||||
|
if (guildMember.rank > 1) {
|
||||||
|
res.status(400).json({ Error: 104 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check guild has invite permissions in the alliance
|
||||||
|
const allianceMember = (await AllianceMember.findOne({
|
||||||
|
allianceId: req.query.allianceId,
|
||||||
|
guildId: guildMember.guildId
|
||||||
|
}))!;
|
||||||
|
if (!(allianceMember.Permissions & GuildPermission.Recruiter)) {
|
||||||
|
res.status(400).json({ Error: 104 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find clan to invite
|
||||||
|
const payload = getJSONfromString<IAddToAllianceRequest>(String(req.body));
|
||||||
|
const guilds = await Guild.find(
|
||||||
|
{
|
||||||
|
Name:
|
||||||
|
payload.clanName.indexOf("#") == -1
|
||||||
|
? new RegExp("^" + regexEscape(payload.clanName) + "#...$")
|
||||||
|
: payload.clanName
|
||||||
|
},
|
||||||
|
"Name"
|
||||||
|
);
|
||||||
|
if (guilds.length == 0) {
|
||||||
|
res.status(400).json({ Error: 101 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (guilds.length > 1) {
|
||||||
|
const choices: IGuildChoice[] = [];
|
||||||
|
for (const guild of guilds) {
|
||||||
|
choices.push({
|
||||||
|
OriginalPlatform: 0,
|
||||||
|
Name: guild.Name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res.json(choices);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add clan as a pending alliance member
|
||||||
|
try {
|
||||||
|
await AllianceMember.insertOne({
|
||||||
|
allianceId: req.query.allianceId,
|
||||||
|
guildId: guilds[0]._id,
|
||||||
|
Pending: true,
|
||||||
|
Permissions: 0
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug(`alliance invite failed due to ${String(e)}`);
|
||||||
|
res.status(400).json({ Error: 102 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send inbox message to founding warlord
|
||||||
|
// TOVERIFY: Should other warlords get this as well?
|
||||||
|
// TOVERIFY: Who/what should the sender be?
|
||||||
|
// TOVERIFY: Should this message be highPriority?
|
||||||
|
const invitedClanOwnerMember = (await GuildMember.findOne({ guildId: guilds[0]._id, rank: 0 }))!;
|
||||||
|
const senderInventory = await getInventory(account._id.toString(), "ActiveAvatarImageType");
|
||||||
|
const senderGuild = (await Guild.findById(allianceMember.guildId, "Name"))!;
|
||||||
|
const alliance = (await Alliance.findById(req.query.allianceId, "Name"))!;
|
||||||
|
await createMessage(invitedClanOwnerMember.accountId, [
|
||||||
|
{
|
||||||
|
sndr: getSuffixedName(account),
|
||||||
|
msg: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Body",
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "THEIR_CLAN",
|
||||||
|
Tag: senderGuild.Name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "CLAN",
|
||||||
|
Tag: guilds[0].Name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "ALLIANCE",
|
||||||
|
Tag: alliance.Name
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sub: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Title",
|
||||||
|
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||||
|
contextInfo: alliance._id.toString(),
|
||||||
|
highPriority: true,
|
||||||
|
acceptAction: "ALLIANCE_INVITE",
|
||||||
|
declineAction: "ALLIANCE_INVITE",
|
||||||
|
hasAccountAction: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IAddToAllianceRequest {
|
||||||
|
clanName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGuildChoice {
|
||||||
|
OriginalPlatform: number;
|
||||||
|
Name: string;
|
||||||
|
}
|
105
src/controllers/api/addToGuildController.ts
Normal file
105
src/controllers/api/addToGuildController.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { Account } from "@/src/models/loginModel";
|
||||||
|
import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
|
||||||
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
|
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportFlavour } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const addToGuildController: RequestHandler = async (req, res) => {
|
||||||
|
const payload = JSON.parse(String(req.body)) as IAddToGuildRequest;
|
||||||
|
|
||||||
|
if ("UserName" in payload) {
|
||||||
|
// Clan recruiter sending an invite
|
||||||
|
|
||||||
|
const account = await Account.findOne({ DisplayName: payload.UserName });
|
||||||
|
if (!account) {
|
||||||
|
res.status(400).json("Username does not exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventory = await getInventory(account._id.toString(), "Settings");
|
||||||
|
// TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
|
||||||
|
if (inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE") {
|
||||||
|
res.status(400).json("Invite restricted");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
|
||||||
|
const senderAccount = await getAccountForRequest(req);
|
||||||
|
if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
|
||||||
|
res.status(400).json("Invalid permission");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await GuildMember.insertOne({
|
||||||
|
accountId: account._id,
|
||||||
|
guildId: payload.GuildId.$oid,
|
||||||
|
status: 2 // outgoing invite
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug(`guild invite failed due to ${String(e)}`);
|
||||||
|
res.status(400).json("User already invited to clan");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const senderInventory = await getInventory(senderAccount._id.toString(), "ActiveAvatarImageType");
|
||||||
|
await createMessage(account._id, [
|
||||||
|
{
|
||||||
|
sndr: getSuffixedName(senderAccount),
|
||||||
|
msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body",
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "clan",
|
||||||
|
Tag: guild.Name
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
|
||||||
|
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||||
|
contextInfo: payload.GuildId.$oid,
|
||||||
|
highPriority: true,
|
||||||
|
acceptAction: "GUILD_INVITE",
|
||||||
|
declineAction: "GUILD_INVITE",
|
||||||
|
hasAccountAction: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const member: IGuildMemberClient = {
|
||||||
|
_id: { $oid: account._id.toString() },
|
||||||
|
DisplayName: account.DisplayName,
|
||||||
|
Rank: 7,
|
||||||
|
Status: 2
|
||||||
|
};
|
||||||
|
await fillInInventoryDataForGuildMember(member);
|
||||||
|
res.json({ NewMember: member });
|
||||||
|
} else if ("RequestMsg" in payload) {
|
||||||
|
// Player applying to join a clan
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
try {
|
||||||
|
await GuildMember.insertOne({
|
||||||
|
accountId,
|
||||||
|
guildId: payload.GuildId.$oid,
|
||||||
|
status: 1, // incoming invite
|
||||||
|
RequestMsg: payload.RequestMsg,
|
||||||
|
RequestExpiry: new Date(Date.now() + 14 * 86400 * 1000) // TOVERIFY: I can't find any good information about this with regards to live, but 2 weeks seem reasonable.
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug(`guild invite failed due to ${String(e)}`);
|
||||||
|
res.status(400).send("Already requested");
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
|
logger.error(`data provided to ${req.path}: ${String(req.body)}`);
|
||||||
|
res.status(400).end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IAddToGuildRequest {
|
||||||
|
UserName?: string;
|
||||||
|
GuildId: IOid;
|
||||||
|
RequestMsg?: string;
|
||||||
|
}
|
76
src/controllers/api/arcaneCommonController.ts
Normal file
76
src/controllers/api/arcaneCommonController.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getInventory, addMods } from "@/src/services/inventoryService";
|
||||||
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
|
|
||||||
|
export const arcaneCommonController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const json = getJSONfromString<IArcaneCommonRequest>(String(req.body));
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const upgrade = inventory.Upgrades.id(json.arcane.ItemId.$oid);
|
||||||
|
if (json.newRank == -1) {
|
||||||
|
// Break down request?
|
||||||
|
if (!upgrade || !upgrade.UpgradeFingerprint) {
|
||||||
|
throw new Error(`Failed to find upgrade with OID ${json.arcane.ItemId.$oid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Upgrade
|
||||||
|
inventory.Upgrades.pull({ _id: json.arcane.ItemId.$oid });
|
||||||
|
|
||||||
|
// Add RawUpgrades
|
||||||
|
const numRawUpgradesToGive = arcaneLevelCounts[(JSON.parse(upgrade.UpgradeFingerprint) as { lvl: number }).lvl];
|
||||||
|
addMods(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: json.arcane.ItemType,
|
||||||
|
ItemCount: numRawUpgradesToGive
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
res.json({ upgradeId: json.arcane.ItemId.$oid, numConsumed: numRawUpgradesToGive });
|
||||||
|
} else {
|
||||||
|
// Upgrade request?
|
||||||
|
let numConsumed = arcaneLevelCounts[json.newRank];
|
||||||
|
let upgradeId = json.arcane.ItemId.$oid;
|
||||||
|
if (upgrade) {
|
||||||
|
// Have an existing Upgrade item?
|
||||||
|
if (upgrade.UpgradeFingerprint) {
|
||||||
|
const existingLevel = (JSON.parse(upgrade.UpgradeFingerprint) as { lvl: number }).lvl;
|
||||||
|
numConsumed -= arcaneLevelCounts[existingLevel];
|
||||||
|
}
|
||||||
|
upgrade.UpgradeFingerprint = JSON.stringify({ lvl: json.newRank });
|
||||||
|
} else {
|
||||||
|
const newLength = inventory.Upgrades.push({
|
||||||
|
ItemType: json.arcane.ItemType,
|
||||||
|
UpgradeFingerprint: JSON.stringify({ lvl: json.newRank })
|
||||||
|
});
|
||||||
|
upgradeId = inventory.Upgrades[newLength - 1]._id.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove RawUpgrades
|
||||||
|
addMods(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: json.arcane.ItemType,
|
||||||
|
ItemCount: numConsumed * -1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
res.json({ newLevel: json.newRank, numConsumed, upgradeId });
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
};
|
||||||
|
|
||||||
|
const arcaneLevelCounts = [0, 3, 6, 10, 15, 21];
|
||||||
|
|
||||||
|
interface IArcaneCommonRequest {
|
||||||
|
arcane: {
|
||||||
|
ItemType: string;
|
||||||
|
ItemId: IOid;
|
||||||
|
FromSKU: boolean;
|
||||||
|
UpgradeFingerprint: string;
|
||||||
|
PendingRerollFingerprint: string;
|
||||||
|
ItemCount: number;
|
||||||
|
LastAdded: IOid;
|
||||||
|
};
|
||||||
|
newRank: number;
|
||||||
|
}
|
51
src/controllers/api/archonFusionController.ts
Normal file
51
src/controllers/api/archonFusionController.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { colorToShard, combineColors, shardToColor } from "@/src/helpers/shardHelper";
|
||||||
|
|
||||||
|
export const archonFusionController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const request = JSON.parse(String(req.body)) as IArchonFusionRequest;
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
request.Consumed.forEach(x => {
|
||||||
|
x.ItemCount *= -1;
|
||||||
|
});
|
||||||
|
addMiscItems(inventory, request.Consumed);
|
||||||
|
const newArchons: IMiscItem[] = [];
|
||||||
|
switch (request.FusionType) {
|
||||||
|
case "AFT_ASCENT":
|
||||||
|
newArchons.push({
|
||||||
|
ItemType: request.Consumed[0].ItemType + "Mythic",
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "AFT_COALESCENT":
|
||||||
|
newArchons.push({
|
||||||
|
ItemType:
|
||||||
|
colorToShard[
|
||||||
|
combineColors(
|
||||||
|
shardToColor[request.Consumed[0].ItemType],
|
||||||
|
shardToColor[request.Consumed[1].ItemType]
|
||||||
|
)
|
||||||
|
],
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`unknown archon fusion type: ${request.FusionType}`);
|
||||||
|
}
|
||||||
|
addMiscItems(inventory, newArchons);
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
NewArchons: newArchons
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IArchonFusionRequest {
|
||||||
|
Consumed: IMiscItem[];
|
||||||
|
FusionType: string;
|
||||||
|
StatResultType: "SRT_NEW_STAT"; // ???
|
||||||
|
}
|
178
src/controllers/api/artifactTransmutationController.ts
Normal file
178
src/controllers/api/artifactTransmutationController.ts
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||||
|
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
||||||
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const artifactTransmutationController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const payload = JSON.parse(String(req.body)) as IArtifactTransmutationRequest;
|
||||||
|
|
||||||
|
inventory.RegularCredits -= payload.Cost;
|
||||||
|
inventory.FusionPoints -= payload.FusionPointCost;
|
||||||
|
|
||||||
|
if (payload.RivenTransmute) {
|
||||||
|
addMiscItems(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientSecretItem",
|
||||||
|
ItemCount: -1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
payload.Consumed.forEach(upgrade => {
|
||||||
|
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
||||||
|
});
|
||||||
|
|
||||||
|
const rawRivenType = getRandomRawRivenType();
|
||||||
|
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType])!;
|
||||||
|
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||||
|
|
||||||
|
const upgradeIndex =
|
||||||
|
inventory.Upgrades.push({
|
||||||
|
ItemType: rivenType,
|
||||||
|
UpgradeFingerprint: JSON.stringify(fingerprint)
|
||||||
|
}) - 1;
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
NewMods: [
|
||||||
|
{
|
||||||
|
ItemId: toOid(inventory.Upgrades[upgradeIndex]._id),
|
||||||
|
ItemType: rivenType,
|
||||||
|
UpgradeFingerprint: fingerprint
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const counts: Record<TRarity, number> = {
|
||||||
|
COMMON: 0,
|
||||||
|
UNCOMMON: 0,
|
||||||
|
RARE: 0,
|
||||||
|
LEGENDARY: 0
|
||||||
|
};
|
||||||
|
let forcedPolarity: string | undefined;
|
||||||
|
payload.Consumed.forEach(upgrade => {
|
||||||
|
const meta = ExportUpgrades[upgrade.ItemType];
|
||||||
|
counts[meta.rarity] += upgrade.ItemCount;
|
||||||
|
if (upgrade.ItemId.$oid != "000000000000000000000000") {
|
||||||
|
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
||||||
|
} else {
|
||||||
|
addMods(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: upgrade.ItemType,
|
||||||
|
ItemCount: upgrade.ItemCount * -1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/AttackTransmuteCore") {
|
||||||
|
forcedPolarity = "AP_ATTACK";
|
||||||
|
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/DefenseTransmuteCore") {
|
||||||
|
forcedPolarity = "AP_DEFENSE";
|
||||||
|
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/TacticTransmuteCore") {
|
||||||
|
forcedPolarity = "AP_TACTIC";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let newModType: string | undefined;
|
||||||
|
for (const specialModSet of specialModSets) {
|
||||||
|
if (specialModSet.indexOf(payload.Consumed[0].ItemType) != -1) {
|
||||||
|
newModType = getRandomElement(specialModSet);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newModType) {
|
||||||
|
// Based on the table on https://wiki.warframe.com/w/Transmutation
|
||||||
|
const weights: Record<TRarity, number> = {
|
||||||
|
COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4,
|
||||||
|
UNCOMMON: counts.COMMON * 4 + counts.UNCOMMON * 80 + counts.RARE * 10,
|
||||||
|
RARE: counts.COMMON * 1 + counts.UNCOMMON * 5 + counts.RARE * 50,
|
||||||
|
LEGENDARY: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: { uniqueName: string; rarity: TRarity }[] = [];
|
||||||
|
Object.entries(ExportUpgrades).forEach(([uniqueName, upgrade]) => {
|
||||||
|
if (upgrade.canBeTransmutation && (!forcedPolarity || upgrade.polarity == forcedPolarity)) {
|
||||||
|
options.push({ uniqueName, rarity: upgrade.rarity });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
newModType = getRandomWeightedReward(options, weights)!.uniqueName;
|
||||||
|
}
|
||||||
|
|
||||||
|
addMods(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: newModType,
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
NewMods: [
|
||||||
|
{
|
||||||
|
ItemType: newModType,
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRandomRawRivenType = (): string => {
|
||||||
|
const pack = ExportBoosterPacks["/Lotus/Types/BoosterPacks/CalendarRivenPack"];
|
||||||
|
return getRandomWeightedRewardUc(pack.components, pack.rarityWeightsPerRoll[0])!.Item;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IArtifactTransmutationRequest {
|
||||||
|
Upgrade: IAgnosticUpgradeClient;
|
||||||
|
LevelDiff: number;
|
||||||
|
Consumed: IAgnosticUpgradeClient[];
|
||||||
|
Cost: number;
|
||||||
|
FusionPointCost: number;
|
||||||
|
RivenTransmute?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAgnosticUpgradeClient {
|
||||||
|
ItemType: string;
|
||||||
|
ItemId: IOid;
|
||||||
|
FromSKU: boolean;
|
||||||
|
UpgradeFingerprint: string;
|
||||||
|
PendingRerollFingerprint: string;
|
||||||
|
ItemCount: number;
|
||||||
|
LastAdded: IOid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const specialModSets: string[][] = [
|
||||||
|
[
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalFourMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalFiveMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalWildcardMod"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusFourMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusFiveMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusSixMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusSevenMod",
|
||||||
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod",
|
||||||
|
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod",
|
||||||
|
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod",
|
||||||
|
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod",
|
||||||
|
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod"
|
||||||
|
]
|
||||||
|
];
|
@ -1,22 +1,70 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { upgradeMod } from "@/src/services/inventoryService";
|
|
||||||
import { IArtifactsRequest } from "@/src/types/requestTypes";
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
import { IInventoryClient, IUpgradeClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { addMods, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
export const artifactsController: RequestHandler = async (req, res) => {
|
||||||
const artifactsController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const artifactsData = getJSONfromString<IArtifactsRequest>(String(req.body));
|
||||||
|
|
||||||
try {
|
const { Upgrade, LevelDiff, Cost, FusionPointCost } = artifactsData;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
|
|
||||||
const artifactsData = getJSONfromString(req.body.toString()) as IArtifactsRequest;
|
const inventory = await getInventory(accountId);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
const { Upgrades } = inventory;
|
||||||
const upgradeModId = await upgradeMod(artifactsData, accountId);
|
const { ItemType, UpgradeFingerprint, ItemId } = Upgrade;
|
||||||
res.send(upgradeModId);
|
|
||||||
} catch (err) {
|
const safeUpgradeFingerprint = UpgradeFingerprint || '{"lvl":0}';
|
||||||
console.error("Error parsing JSON data:", err);
|
const parsedUpgradeFingerprint = JSON.parse(safeUpgradeFingerprint) as { lvl: number };
|
||||||
|
parsedUpgradeFingerprint.lvl += LevelDiff;
|
||||||
|
const stringifiedUpgradeFingerprint = JSON.stringify(parsedUpgradeFingerprint);
|
||||||
|
|
||||||
|
let itemIndex = Upgrades.findIndex(upgrade => upgrade._id.equals(ItemId.$oid));
|
||||||
|
|
||||||
|
if (itemIndex !== -1) {
|
||||||
|
Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
|
||||||
|
inventory.markModified(`Upgrades.${itemIndex}.UpgradeFingerprint`);
|
||||||
|
} else {
|
||||||
|
itemIndex =
|
||||||
|
Upgrades.push({
|
||||||
|
UpgradeFingerprint: stringifiedUpgradeFingerprint,
|
||||||
|
ItemType
|
||||||
|
}) - 1;
|
||||||
|
|
||||||
|
addMods(inventory, [{ ItemType, ItemCount: -1 }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!config.infiniteCredits) {
|
||||||
|
inventory.RegularCredits -= Cost;
|
||||||
|
}
|
||||||
|
if (!config.infiniteEndo) {
|
||||||
|
inventory.FusionPoints -= FusionPointCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (artifactsData.LegendaryFusion) {
|
||||||
|
addMods(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser",
|
||||||
|
ItemCount: -1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const changedInventory = await inventory.save();
|
||||||
|
const itemId = changedInventory.toJSON<IInventoryClient>().Upgrades[itemIndex].ItemId.$oid;
|
||||||
|
|
||||||
|
if (!itemId) {
|
||||||
|
throw new Error("Item Id not found in upgradeMod");
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(itemId);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { artifactsController };
|
interface IArtifactsRequest {
|
||||||
|
Upgrade: IUpgradeClient;
|
||||||
|
LevelDiff: number;
|
||||||
|
Cost: number;
|
||||||
|
FusionPointCost: number;
|
||||||
|
LegendaryFusion?: boolean;
|
||||||
|
}
|
||||||
|
20
src/controllers/api/cancelGuildAdvertisementController.ts
Normal file
20
src/controllers/api/cancelGuildAdvertisementController.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { GuildAd } from "@/src/models/guildModel";
|
||||||
|
import { getGuildForRequestEx, hasGuildPermission } from "@/src/services/guildService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const cancelGuildAdvertisementController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Advertiser))) {
|
||||||
|
res.status(400).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GuildAd.deleteOne({ GuildId: guild._id });
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
93
src/controllers/api/changeDojoRootController.ts
Normal file
93
src/controllers/api/changeDojoRootController.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { GuildPermission, IDojoComponentDatabase } from "@/src/types/guildTypes";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
|
export const changeDojoRootController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId LevelKeys");
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
|
||||||
|
res.json({ DojoRequestStatus: -1 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example POST body: {"pivot":[0, 0, -64],"components":"{\"670429301ca0a63848ccc467\":{\"R\":[0,0,0],\"P\":[0,3,32]},\"6704254a1ca0a63848ccb33c\":{\"R\":[0,0,0],\"P\":[0,9.25,-32]},\"670429461ca0a63848ccc731\":{\"R\":[-90,0,0],\"P\":[-47.999992370605,3,16]}}"}
|
||||||
|
if (req.body) {
|
||||||
|
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||||
|
throw new Error("dojo reparent operation should not need deco repositioning"); // because we always provide SortId
|
||||||
|
}
|
||||||
|
|
||||||
|
const idToNode: Record<string, INode> = {};
|
||||||
|
guild.DojoComponents.forEach(x => {
|
||||||
|
idToNode[x._id.toString()] = {
|
||||||
|
component: x,
|
||||||
|
parent: undefined,
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let oldRoot: INode | undefined;
|
||||||
|
guild.DojoComponents.forEach(x => {
|
||||||
|
const node = idToNode[x._id.toString()];
|
||||||
|
if (x.pi) {
|
||||||
|
idToNode[x.pi.toString()].children.push(node);
|
||||||
|
node.parent = idToNode[x.pi.toString()];
|
||||||
|
} else {
|
||||||
|
oldRoot = node;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
logger.debug("Old tree:\n" + treeToString(oldRoot!));
|
||||||
|
|
||||||
|
const newRoot = idToNode[req.query.newRoot as string];
|
||||||
|
recursivelyTurnParentsIntoChildren(newRoot);
|
||||||
|
newRoot.component.pi = undefined;
|
||||||
|
newRoot.component.op = undefined;
|
||||||
|
newRoot.component.pp = undefined;
|
||||||
|
newRoot.parent = undefined;
|
||||||
|
|
||||||
|
// Set/update SortId in top-to-bottom order
|
||||||
|
const stack: INode[] = [newRoot];
|
||||||
|
while (stack.length != 0) {
|
||||||
|
const top = stack.shift()!;
|
||||||
|
top.component.SortId = new Types.ObjectId();
|
||||||
|
top.children.forEach(x => stack.push(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("New tree:\n" + treeToString(newRoot));
|
||||||
|
|
||||||
|
await guild.save();
|
||||||
|
|
||||||
|
res.json(await getDojoClient(guild, 0));
|
||||||
|
};
|
||||||
|
|
||||||
|
interface INode {
|
||||||
|
component: IDojoComponentDatabase;
|
||||||
|
parent: INode | undefined;
|
||||||
|
children: INode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeToString = (root: INode, depth: number = 0): string => {
|
||||||
|
let str = " ".repeat(depth * 4) + root.component.pf + " (" + root.component._id.toString() + ")\n";
|
||||||
|
root.children.forEach(x => {
|
||||||
|
str += treeToString(x, depth + 1);
|
||||||
|
});
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
const recursivelyTurnParentsIntoChildren = (node: INode): void => {
|
||||||
|
if (node.parent!.parent) {
|
||||||
|
recursivelyTurnParentsIntoChildren(node.parent!);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.parent!.component.pi = node.component._id;
|
||||||
|
node.parent!.component.op = node.component.pp;
|
||||||
|
node.parent!.component.pp = node.component.op;
|
||||||
|
|
||||||
|
node.parent!.parent = node;
|
||||||
|
node.parent!.children.splice(node.parent!.children.indexOf(node), 1);
|
||||||
|
node.children.push(node.parent!);
|
||||||
|
};
|
38
src/controllers/api/changeGuildRankController.ts
Normal file
38
src/controllers/api/changeGuildRankController.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getGuildForRequest, hasGuildPermissionEx } from "@/src/services/guildService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const changeGuildRankController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const member = (await GuildMember.findOne({
|
||||||
|
accountId: accountId,
|
||||||
|
guildId: req.query.guildId as string
|
||||||
|
}))!;
|
||||||
|
const newRank: number = parseInt(req.query.rankChange as string);
|
||||||
|
|
||||||
|
const guild = await getGuildForRequest(req);
|
||||||
|
if (newRank < member.rank || !hasGuildPermissionEx(guild, member, GuildPermission.Promoter)) {
|
||||||
|
res.status(400).json("Invalid permission");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = (await GuildMember.findOne({
|
||||||
|
guildId: req.query.guildId as string,
|
||||||
|
accountId: req.query.targetId as string
|
||||||
|
}))!;
|
||||||
|
target.rank = parseInt(req.query.rankChange as string);
|
||||||
|
await target.save();
|
||||||
|
|
||||||
|
if (newRank == 0) {
|
||||||
|
// If we just promoted someone else to Founding Warlord, we need to demote ourselves to Warlord.
|
||||||
|
member.rank = 1;
|
||||||
|
await member.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
_id: req.query.targetId as string,
|
||||||
|
Rank: newRank
|
||||||
|
});
|
||||||
|
};
|
@ -7,82 +7,143 @@ import { getRecipe } from "@/src/services/itemDataService";
|
|||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getInventory, updateCurrency, addItem, addMiscItems, addRecipes } from "@/src/services/inventoryService";
|
import {
|
||||||
|
getInventory,
|
||||||
|
updateCurrency,
|
||||||
|
addItem,
|
||||||
|
addRecipes,
|
||||||
|
occupySlot,
|
||||||
|
combineInventoryChanges
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
|
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
|
||||||
export interface IClaimCompletedRecipeRequest {
|
interface IClaimCompletedRecipeRequest {
|
||||||
RecipeIds: IOid[];
|
RecipeIds: IOid[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
||||||
const claimCompletedRecipeRequest = getJSONfromString(String(req.body)) as IClaimCompletedRecipeRequest;
|
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
if (!accountId) throw new Error("no account id");
|
if (!accountId) throw new Error("no account id");
|
||||||
|
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const pendingRecipe = inventory.PendingRecipes.find(
|
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
|
||||||
recipe => recipe._id?.toString() === claimCompletedRecipeRequest.RecipeIds[0].$oid
|
|
||||||
);
|
|
||||||
if (!pendingRecipe) {
|
if (!pendingRecipe) {
|
||||||
logger.error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
|
||||||
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
//check recipe is indeed ready to be completed
|
//check recipe is indeed ready to be completed
|
||||||
// if (pendingRecipe.CompletionDate > new Date()) {
|
// if (pendingRecipe.CompletionDate > new Date()) {
|
||||||
// logger.error(`recipe ${pendingRecipe._id} is not ready to be completed`);
|
|
||||||
// throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
|
// throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
inventory.PendingRecipes.pull(pendingRecipe._id);
|
inventory.PendingRecipes.pull(pendingRecipe._id);
|
||||||
await inventory.save();
|
|
||||||
|
|
||||||
const recipe = getRecipe(pendingRecipe.ItemType);
|
const recipe = getRecipe(pendingRecipe.ItemType);
|
||||||
if (!recipe) {
|
if (!recipe) {
|
||||||
logger.error(`no completed item found for recipe ${pendingRecipe._id}`);
|
throw new Error(`no completed item found for recipe ${pendingRecipe._id.toString()}`);
|
||||||
throw new Error(`no completed item found for recipe ${pendingRecipe._id}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.query.cancel) {
|
if (req.query.cancel) {
|
||||||
const currencyChanges = await updateCurrency(recipe.buildPrice * -1, false, accountId);
|
const inventoryChanges: IInventoryChanges = {
|
||||||
|
...updateCurrency(inventory, recipe.buildPrice * -1, false)
|
||||||
|
};
|
||||||
|
|
||||||
const inventory = await getInventory(accountId);
|
const equipmentIngredients = new Set();
|
||||||
addMiscItems(inventory, recipe.ingredients);
|
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
|
||||||
await inventory.save();
|
if (pendingRecipe[category]) {
|
||||||
|
pendingRecipe[category].forEach(item => {
|
||||||
|
const index = inventory[category].push(item) - 1;
|
||||||
|
inventoryChanges[category] ??= [];
|
||||||
|
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
|
||||||
|
equipmentIngredients.add(item.ItemType);
|
||||||
|
|
||||||
// Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
occupySlot(inventory, InventorySlot.WEAPONS, false);
|
||||||
res.json({
|
inventoryChanges.WeaponBin ??= { Slots: 0 };
|
||||||
...currencyChanges,
|
inventoryChanges.WeaponBin.Slots -= 1;
|
||||||
MiscItems: recipe.ingredients
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const ingredient of recipe.ingredients) {
|
||||||
|
if (!equipmentIngredients.has(ingredient.ItemType)) {
|
||||||
|
combineInventoryChanges(
|
||||||
|
inventoryChanges,
|
||||||
|
await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
||||||
let InventoryChanges = {};
|
|
||||||
|
let BrandedSuits: undefined | IOid[];
|
||||||
|
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
||||||
|
inventory.PendingSpectreLoadouts ??= [];
|
||||||
|
inventory.SpectreLoadouts ??= [];
|
||||||
|
|
||||||
|
const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(
|
||||||
|
x => x.ItemType == recipe.resultType
|
||||||
|
);
|
||||||
|
if (pendingLoadoutIndex != -1) {
|
||||||
|
const loadoutIndex = inventory.SpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
|
||||||
|
if (loadoutIndex != -1) {
|
||||||
|
inventory.SpectreLoadouts.splice(loadoutIndex, 1);
|
||||||
|
}
|
||||||
|
logger.debug(
|
||||||
|
"moving spectre loadout from pending to active",
|
||||||
|
inventory.toJSON().PendingSpectreLoadouts![pendingLoadoutIndex]
|
||||||
|
);
|
||||||
|
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
|
||||||
|
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
|
||||||
|
}
|
||||||
|
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
|
||||||
|
inventory.BrandedSuits!.splice(
|
||||||
|
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)];
|
||||||
|
}
|
||||||
|
|
||||||
|
let InventoryChanges: IInventoryChanges = {};
|
||||||
if (recipe.consumeOnUse) {
|
if (recipe.consumeOnUse) {
|
||||||
const recipeChanges = [
|
addRecipes(inventory, [
|
||||||
{
|
{
|
||||||
ItemType: pendingRecipe.ItemType,
|
ItemType: pendingRecipe.ItemType,
|
||||||
ItemCount: -1
|
ItemCount: -1
|
||||||
}
|
}
|
||||||
];
|
]);
|
||||||
|
|
||||||
InventoryChanges = { ...InventoryChanges, Recipes: recipeChanges };
|
|
||||||
|
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
addRecipes(inventory, recipeChanges);
|
|
||||||
await inventory.save();
|
|
||||||
}
|
}
|
||||||
if (req.query.rush) {
|
if (req.query.rush) {
|
||||||
|
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
|
||||||
|
const start = end - recipe.buildTime;
|
||||||
|
const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
|
||||||
|
const progress = secondsElapsed / recipe.buildTime;
|
||||||
|
logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
|
||||||
|
const cost = Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)));
|
||||||
InventoryChanges = {
|
InventoryChanges = {
|
||||||
...InventoryChanges,
|
...InventoryChanges,
|
||||||
...(await updateCurrency(recipe.skipBuildTimePrice, true, accountId))
|
...updateCurrency(inventory, cost, true)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
res.json({
|
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
||||||
InventoryChanges: {
|
InventoryChanges = {
|
||||||
...InventoryChanges,
|
...InventoryChanges,
|
||||||
...(await addItem(accountId, recipe.resultType, recipe.num)).InventoryChanges
|
...(await addItem(
|
||||||
|
inventory,
|
||||||
|
recipe.resultType,
|
||||||
|
recipe.num,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
pendingRecipe.TargetFingerprint
|
||||||
|
))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
await inventory.save();
|
||||||
|
res.json({ InventoryChanges, BrandedSuits });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
31
src/controllers/api/claimLibraryDailyTaskRewardController.ts
Normal file
31
src/controllers/api/claimLibraryDailyTaskRewardController.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { addFusionPoints, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const claimLibraryDailyTaskRewardController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
|
||||||
|
const rewardQuantity = inventory.LibraryActiveDailyTaskInfo!.RewardQuantity;
|
||||||
|
const rewardStanding = inventory.LibraryActiveDailyTaskInfo!.RewardStanding;
|
||||||
|
inventory.LibraryActiveDailyTaskInfo = undefined;
|
||||||
|
inventory.LibraryAvailableDailyTaskInfo = undefined;
|
||||||
|
|
||||||
|
let syndicate = inventory.Affiliations.find(x => x.Tag == "LibrarySyndicate");
|
||||||
|
if (!syndicate) {
|
||||||
|
syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: "LibrarySyndicate", Standing: 0 }) - 1];
|
||||||
|
}
|
||||||
|
syndicate.Standing += rewardStanding;
|
||||||
|
|
||||||
|
addFusionPoints(inventory, 80 * rewardQuantity);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
RewardItem: "/Lotus/StoreItems/Upgrades/Mods/FusionBundles/RareFusionBundle",
|
||||||
|
RewardQuantity: rewardQuantity,
|
||||||
|
StandingAwarded: rewardStanding,
|
||||||
|
InventoryChanges: {
|
||||||
|
FusionPoints: 80 * rewardQuantity
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
25
src/controllers/api/clearDialogueHistoryController.ts
Normal file
25
src/controllers/api/clearDialogueHistoryController.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const clearDialogueHistoryController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const request = JSON.parse(String(req.body)) as IClearDialogueRequest;
|
||||||
|
if (inventory.DialogueHistory && inventory.DialogueHistory.Dialogues) {
|
||||||
|
inventory.DialogueHistory.Resets ??= 0;
|
||||||
|
inventory.DialogueHistory.Resets += 1;
|
||||||
|
for (const dialogueName of request.Dialogues) {
|
||||||
|
const index = inventory.DialogueHistory.Dialogues.findIndex(x => x.DialogueName == dialogueName);
|
||||||
|
if (index != -1) {
|
||||||
|
inventory.DialogueHistory.Dialogues.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IClearDialogueRequest {
|
||||||
|
Dialogues: string[];
|
||||||
|
}
|
6
src/controllers/api/clearNewEpisodeRewardController.ts
Normal file
6
src/controllers/api/clearNewEpisodeRewardController.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
// example req.body: {"NewEpisodeReward":true,"crossPlaySetting":"ENABLED"}
|
||||||
|
export const clearNewEpisodeRewardController: RequestHandler = (_req, res) => {
|
||||||
|
res.status(200).end();
|
||||||
|
};
|
41
src/controllers/api/completeCalendarEventController.ts
Normal file
41
src/controllers/api/completeCalendarEventController.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { getCalendarProgress, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
|
import { getWorldState } from "@/src/services/worldStateService";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
// GET request; query parameters: CompletedEventIdx=0&Iteration=4&Version=19&Season=CST_SUMMER
|
||||||
|
export const completeCalendarEventController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const calendarProgress = getCalendarProgress(inventory);
|
||||||
|
const currentSeason = getWorldState().KnownCalendarSeasons[0];
|
||||||
|
let inventoryChanges: IInventoryChanges = {};
|
||||||
|
let dayIndex = 0;
|
||||||
|
for (const day of currentSeason.Days) {
|
||||||
|
if (day.events.length == 0 || day.events[0].type != "CET_CHALLENGE") {
|
||||||
|
if (dayIndex == calendarProgress.SeasonProgress.LastCompletedDayIdx) {
|
||||||
|
if (day.events.length != 0) {
|
||||||
|
const selection = day.events[parseInt(req.query.CompletedEventIdx as string)];
|
||||||
|
if (selection.type == "CET_REWARD") {
|
||||||
|
inventoryChanges = (await handleStoreItemAcquisition(selection.reward!, inventory))
|
||||||
|
.InventoryChanges;
|
||||||
|
} else if (selection.type == "CET_UPGRADE") {
|
||||||
|
calendarProgress.YearProgress.Upgrades.push(selection.upgrade!);
|
||||||
|
} else if (selection.type != "CET_PLOT") {
|
||||||
|
throw new Error(`unexpected selection type: ${selection.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++dayIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: inventoryChanges,
|
||||||
|
CalendarProgress: inventory.CalendarProgress
|
||||||
|
});
|
||||||
|
};
|
45
src/controllers/api/completeRandomModChallengeController.ts
Normal file
45
src/controllers/api/completeRandomModChallengeController.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { createUnveiledRivenFingerprint } from "@/src/helpers/rivenHelper";
|
||||||
|
import { ExportUpgrades } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const completeRandomModChallengeController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const request = getJSONfromString<ICompleteRandomModChallengeRequest>(String(req.body));
|
||||||
|
let inventoryChanges: IInventoryChanges = {};
|
||||||
|
|
||||||
|
// Remove 20 plat or riven cipher
|
||||||
|
if ((req.query.p as string) == "1") {
|
||||||
|
inventoryChanges = { ...updateCurrency(inventory, 20, true) };
|
||||||
|
} else {
|
||||||
|
const miscItemChanges: IMiscItem[] = [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Items/MiscItems/RivenIdentifier",
|
||||||
|
ItemCount: -1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
inventoryChanges.MiscItems = miscItemChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update riven fingerprint to a randomised unveiled state
|
||||||
|
const upgrade = inventory.Upgrades.id(request.ItemId)!;
|
||||||
|
const meta = ExportUpgrades[upgrade.ItemType];
|
||||||
|
upgrade.UpgradeFingerprint = JSON.stringify(createUnveiledRivenFingerprint(meta));
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: inventoryChanges,
|
||||||
|
Fingerprint: upgrade.UpgradeFingerprint
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ICompleteRandomModChallengeRequest {
|
||||||
|
ItemId: string;
|
||||||
|
}
|
37
src/controllers/api/confirmAllianceInvitationController.ts
Normal file
37
src/controllers/api/confirmAllianceInvitationController.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getAllianceClient } from "@/src/services/guildService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const confirmAllianceInvitationController: RequestHandler = async (req, res) => {
|
||||||
|
// Check requester is a warlord in their guild
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const guildMember = (await GuildMember.findOne({ accountId, status: 0 }))!;
|
||||||
|
if (guildMember.rank > 1) {
|
||||||
|
res.status(400).json({ Error: 104 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allianceMember = await AllianceMember.findOne({
|
||||||
|
allianceId: req.query.allianceId,
|
||||||
|
guildId: guildMember.guildId
|
||||||
|
});
|
||||||
|
if (!allianceMember || !allianceMember.Pending) {
|
||||||
|
res.status(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
allianceMember.Pending = false;
|
||||||
|
|
||||||
|
const guild = (await Guild.findById(guildMember.guildId))!;
|
||||||
|
guild.AllianceId = allianceMember.allianceId;
|
||||||
|
|
||||||
|
await Promise.all([allianceMember.save(), guild.save()]);
|
||||||
|
|
||||||
|
// Give client the new alliance data which uses "AllianceId" instead of "_id" in this response
|
||||||
|
const alliance = (await Alliance.findById(allianceMember.allianceId))!;
|
||||||
|
const { _id, ...rest } = await getAllianceClient(alliance, guild);
|
||||||
|
res.json({
|
||||||
|
AllianceId: _id,
|
||||||
|
...rest
|
||||||
|
});
|
||||||
|
};
|
118
src/controllers/api/confirmGuildInvitationController.ts
Normal file
118
src/controllers/api/confirmGuildInvitationController.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { Account } from "@/src/models/loginModel";
|
||||||
|
import {
|
||||||
|
deleteGuild,
|
||||||
|
getGuildClient,
|
||||||
|
giveClanKey,
|
||||||
|
hasGuildPermission,
|
||||||
|
removeDojoKeyItems
|
||||||
|
} from "@/src/services/guildService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
|
||||||
|
// GET request: A player accepting an invite they got in their inbox.
|
||||||
|
export const confirmGuildInvitationGetController: RequestHandler = async (req, res) => {
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
const invitedGuildMember = await GuildMember.findOne({
|
||||||
|
accountId: account._id,
|
||||||
|
guildId: req.query.clanId as string
|
||||||
|
});
|
||||||
|
if (invitedGuildMember && invitedGuildMember.status == 2) {
|
||||||
|
let inventoryChanges: IInventoryChanges = {};
|
||||||
|
|
||||||
|
// If this account is already in a guild, we need to do cleanup first.
|
||||||
|
const guildMember = await GuildMember.findOneAndDelete({ accountId: account._id, status: 0 });
|
||||||
|
if (guildMember) {
|
||||||
|
const inventory = await getInventory(account._id.toString(), "LevelKeys Recipes");
|
||||||
|
inventoryChanges = removeDojoKeyItems(inventory);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
if (guildMember.rank == 0) {
|
||||||
|
await deleteGuild(guildMember.guildId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we're sure this account is not in a guild right now, we can just proceed with the normal updates.
|
||||||
|
invitedGuildMember.status = 0;
|
||||||
|
await invitedGuildMember.save();
|
||||||
|
|
||||||
|
// Remove pending applications for this account
|
||||||
|
await GuildMember.deleteMany({ accountId: account._id, status: 1 });
|
||||||
|
|
||||||
|
// Update inventory of new member
|
||||||
|
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
|
||||||
|
inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
|
||||||
|
giveClanKey(inventory, inventoryChanges);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
const guild = (await Guild.findById(req.query.clanId as string))!;
|
||||||
|
|
||||||
|
// Add join to clan log
|
||||||
|
guild.RosterActivity ??= [];
|
||||||
|
guild.RosterActivity.push({
|
||||||
|
dateTime: new Date(),
|
||||||
|
entryType: 6,
|
||||||
|
details: getSuffixedName(account)
|
||||||
|
});
|
||||||
|
await guild.save();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
...(await getGuildClient(guild, account._id.toString())),
|
||||||
|
InventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// POST request: Clan representative accepting invite(s).
|
||||||
|
export const confirmGuildInvitationPostController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const guild = (await Guild.findById(req.query.clanId as string, "Ranks RosterActivity"))!;
|
||||||
|
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Recruiter))) {
|
||||||
|
res.status(400).json("Invalid permission");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const payload = getJSONfromString<{ userId: string }>(String(req.body));
|
||||||
|
const filter: { accountId?: string; status: number } = { status: 1 };
|
||||||
|
if (payload.userId != "all") {
|
||||||
|
filter.accountId = payload.userId;
|
||||||
|
}
|
||||||
|
const guildMembers = await GuildMember.find(filter);
|
||||||
|
const newMembers: string[] = [];
|
||||||
|
for (const guildMember of guildMembers) {
|
||||||
|
guildMember.status = 0;
|
||||||
|
guildMember.RequestMsg = undefined;
|
||||||
|
guildMember.RequestExpiry = undefined;
|
||||||
|
await guildMember.save();
|
||||||
|
|
||||||
|
// Remove other pending applications for this account
|
||||||
|
await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
|
||||||
|
|
||||||
|
// Update inventory of new member
|
||||||
|
const inventory = await getInventory(guildMember.accountId.toString(), "GuildId LevelKeys Recipes");
|
||||||
|
inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
|
||||||
|
giveClanKey(inventory);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
// Add join to clan log
|
||||||
|
const account = (await Account.findOne({ _id: guildMember.accountId }))!;
|
||||||
|
guild.RosterActivity ??= [];
|
||||||
|
guild.RosterActivity.push({
|
||||||
|
dateTime: new Date(),
|
||||||
|
entryType: 6,
|
||||||
|
details: getSuffixedName(account)
|
||||||
|
});
|
||||||
|
|
||||||
|
newMembers.push(account._id.toString());
|
||||||
|
}
|
||||||
|
await guild.save();
|
||||||
|
res.json({
|
||||||
|
NewMembers: newMembers
|
||||||
|
});
|
||||||
|
};
|
67
src/controllers/api/contributeGuildClassController.ts
Normal file
67
src/controllers/api/contributeGuildClassController.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Guild } from "@/src/models/guildModel";
|
||||||
|
import { checkClanAscensionHasRequiredContributors } from "@/src/services/guildService";
|
||||||
|
import { addFusionPoints, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
|
||||||
|
export const contributeGuildClassController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const payload = getJSONfromString<IContributeGuildClassRequest>(String(req.body));
|
||||||
|
const guild = (await Guild.findById(payload.GuildId))!;
|
||||||
|
|
||||||
|
// First contributor initiates ceremony and locks the pending class.
|
||||||
|
if (!guild.CeremonyContributors) {
|
||||||
|
guild.CeremonyContributors = [];
|
||||||
|
guild.CeremonyClass = guildXpToClass(guild.XP);
|
||||||
|
guild.CeremonyEndo = 0;
|
||||||
|
for (let i = guild.Class; i != guild.CeremonyClass; ++i) {
|
||||||
|
guild.CeremonyEndo += (i + 1) * 1000;
|
||||||
|
}
|
||||||
|
guild.ClassChanges ??= [];
|
||||||
|
guild.ClassChanges.push({
|
||||||
|
dateTime: new Date(),
|
||||||
|
entryType: 13,
|
||||||
|
details: guild.CeremonyClass
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
guild.CeremonyContributors.push(new Types.ObjectId(accountId));
|
||||||
|
|
||||||
|
await checkClanAscensionHasRequiredContributors(guild);
|
||||||
|
|
||||||
|
await guild.save();
|
||||||
|
|
||||||
|
// Either way, endo is given to the contributor.
|
||||||
|
const inventory = await getInventory(accountId, "FusionPoints");
|
||||||
|
addFusionPoints(inventory, guild.CeremonyEndo!);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
NumContributors: guild.CeremonyContributors.length,
|
||||||
|
FusionPointReward: guild.CeremonyEndo,
|
||||||
|
Class: guild.Class,
|
||||||
|
CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IContributeGuildClassRequest {
|
||||||
|
GuildId: string;
|
||||||
|
RequiredContributors: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const guildXpToClass = (xp: number): number => {
|
||||||
|
const cummXp = [
|
||||||
|
0, 11000, 34000, 69000, 114000, 168000, 231000, 302000, 381000, 68000, 563000, 665000, 774000, 891000
|
||||||
|
];
|
||||||
|
let highest = 0;
|
||||||
|
for (let i = 0; i != cummXp.length; ++i) {
|
||||||
|
if (xp < cummXp[i]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
highest = i;
|
||||||
|
}
|
||||||
|
return highest;
|
||||||
|
};
|
168
src/controllers/api/contributeToDojoComponentController.ts
Normal file
168
src/controllers/api/contributeToDojoComponentController.ts
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
|
||||||
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
import {
|
||||||
|
addGuildMemberMiscItemContribution,
|
||||||
|
getDojoClient,
|
||||||
|
getGuildForRequestEx,
|
||||||
|
hasAccessToDojo,
|
||||||
|
processDojoBuildMaterialsGathered,
|
||||||
|
scaleRequiredCount,
|
||||||
|
setDojoRoomLogFunded
|
||||||
|
} from "@/src/services/guildService";
|
||||||
|
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IDojoContributable, IGuildMemberDatabase } from "@/src/types/guildTypes";
|
||||||
|
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
interface IContributeToDojoComponentRequest {
|
||||||
|
ComponentId: string;
|
||||||
|
DecoId?: string;
|
||||||
|
DecoType?: string;
|
||||||
|
IngredientContributions: IMiscItem[];
|
||||||
|
RegularCredits: number;
|
||||||
|
VaultIngredientContributions: IMiscItem[];
|
||||||
|
VaultCredits: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const contributeToDojoComponentController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
// Any clan member should have permission to contribute although notably permission is denied if they have not crafted the dojo key and were simply invited in.
|
||||||
|
if (!hasAccessToDojo(inventory)) {
|
||||||
|
res.json({ DojoRequestStatus: -1 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
const guildMember = (await GuildMember.findOne(
|
||||||
|
{ accountId, guildId: guild._id },
|
||||||
|
"RegularCreditsContributed MiscItemsContributed"
|
||||||
|
))!;
|
||||||
|
const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
|
||||||
|
const component = guild.DojoComponents.id(request.ComponentId)!;
|
||||||
|
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
if (!component.CompletionTime) {
|
||||||
|
// Room is in "Collecting Materials" state
|
||||||
|
if (request.DecoId) {
|
||||||
|
throw new Error("attempt to contribute to a deco in an unfinished room?!");
|
||||||
|
}
|
||||||
|
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
|
||||||
|
processContribution(guild, guildMember, request, inventory, inventoryChanges, meta, component);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
if (component.CompletionTime) {
|
||||||
|
setDojoRoomLogFunded(guild, component);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Room is past "Collecting Materials"
|
||||||
|
if (request.DecoId) {
|
||||||
|
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
|
||||||
|
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
|
||||||
|
processContribution(guild, guildMember, request, inventory, inventoryChanges, meta, deco);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
||||||
|
res.json({
|
||||||
|
...(await getDojoClient(guild, 0, component._id)),
|
||||||
|
InventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const processContribution = (
|
||||||
|
guild: TGuildDatabaseDocument,
|
||||||
|
guildMember: IGuildMemberDatabase,
|
||||||
|
request: IContributeToDojoComponentRequest,
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
inventoryChanges: IInventoryChanges,
|
||||||
|
meta: IDojoBuild,
|
||||||
|
component: IDojoContributable
|
||||||
|
): void => {
|
||||||
|
component.RegularCredits ??= 0;
|
||||||
|
if (request.RegularCredits) {
|
||||||
|
component.RegularCredits += request.RegularCredits;
|
||||||
|
inventoryChanges.RegularCredits = -request.RegularCredits;
|
||||||
|
updateCurrency(inventory, request.RegularCredits, false);
|
||||||
|
|
||||||
|
guildMember.RegularCreditsContributed ??= 0;
|
||||||
|
guildMember.RegularCreditsContributed += request.RegularCredits;
|
||||||
|
}
|
||||||
|
if (request.VaultCredits) {
|
||||||
|
component.RegularCredits += request.VaultCredits;
|
||||||
|
guild.VaultRegularCredits! -= request.VaultCredits;
|
||||||
|
}
|
||||||
|
if (component.RegularCredits > scaleRequiredCount(guild.Tier, meta.price)) {
|
||||||
|
guild.VaultRegularCredits ??= 0;
|
||||||
|
guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(guild.Tier, meta.price);
|
||||||
|
component.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
|
||||||
|
}
|
||||||
|
|
||||||
|
component.MiscItems ??= [];
|
||||||
|
if (request.VaultIngredientContributions.length) {
|
||||||
|
for (const ingredientContribution of request.VaultIngredientContributions) {
|
||||||
|
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredientContribution.ItemType);
|
||||||
|
if (componentMiscItem) {
|
||||||
|
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
|
||||||
|
if (
|
||||||
|
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
|
||||||
|
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
|
||||||
|
) {
|
||||||
|
ingredientContribution.ItemCount =
|
||||||
|
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
||||||
|
}
|
||||||
|
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
|
||||||
|
} else {
|
||||||
|
component.MiscItems.push(ingredientContribution);
|
||||||
|
}
|
||||||
|
const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == ingredientContribution.ItemType)!;
|
||||||
|
vaultMiscItem.ItemCount -= ingredientContribution.ItemCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request.IngredientContributions.length) {
|
||||||
|
const miscItemChanges: IMiscItem[] = [];
|
||||||
|
for (const ingredientContribution of request.IngredientContributions) {
|
||||||
|
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredientContribution.ItemType);
|
||||||
|
if (componentMiscItem) {
|
||||||
|
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
|
||||||
|
if (
|
||||||
|
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
|
||||||
|
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
|
||||||
|
) {
|
||||||
|
ingredientContribution.ItemCount =
|
||||||
|
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
||||||
|
}
|
||||||
|
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
|
||||||
|
} else {
|
||||||
|
component.MiscItems.push(ingredientContribution);
|
||||||
|
}
|
||||||
|
miscItemChanges.push({
|
||||||
|
ItemType: ingredientContribution.ItemType,
|
||||||
|
ItemCount: ingredientContribution.ItemCount * -1
|
||||||
|
});
|
||||||
|
|
||||||
|
addGuildMemberMiscItemContribution(guildMember, ingredientContribution);
|
||||||
|
}
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
inventoryChanges.MiscItems = miscItemChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.RegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
|
||||||
|
let fullyFunded = true;
|
||||||
|
for (const ingredient of meta.ingredients) {
|
||||||
|
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
|
||||||
|
if (
|
||||||
|
!componentMiscItem ||
|
||||||
|
componentMiscItem.ItemCount < scaleRequiredCount(guild.Tier, ingredient.ItemCount)
|
||||||
|
) {
|
||||||
|
fullyFunded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fullyFunded) {
|
||||||
|
component.CompletionTime = new Date(Date.now() + meta.time * 1000);
|
||||||
|
processDojoBuildMaterialsGathered(guild, meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
113
src/controllers/api/contributeToVaultController.ts
Normal file
113
src/controllers/api/contributeToVaultController.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import {
|
||||||
|
Alliance,
|
||||||
|
Guild,
|
||||||
|
GuildMember,
|
||||||
|
TGuildDatabaseDocument,
|
||||||
|
TGuildMemberDatabaseDocument
|
||||||
|
} from "@/src/models/guildModel";
|
||||||
|
import {
|
||||||
|
addGuildMemberMiscItemContribution,
|
||||||
|
addGuildMemberShipDecoContribution,
|
||||||
|
addVaultFusionTreasures,
|
||||||
|
addVaultMiscItems,
|
||||||
|
addVaultShipDecos,
|
||||||
|
getGuildForRequestEx
|
||||||
|
} from "@/src/services/guildService";
|
||||||
|
import {
|
||||||
|
addFusionTreasures,
|
||||||
|
addMiscItems,
|
||||||
|
addShipDecorations,
|
||||||
|
getInventory,
|
||||||
|
updateCurrency
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const contributeToVaultController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId RegularCredits MiscItems ShipDecorations FusionTreasures");
|
||||||
|
const request = JSON.parse(String(req.body)) as IContributeToVaultRequest;
|
||||||
|
|
||||||
|
if (request.Alliance) {
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
const alliance = (await Alliance.findById(guild.AllianceId!))!;
|
||||||
|
alliance.VaultRegularCredits ??= 0;
|
||||||
|
alliance.VaultRegularCredits += request.RegularCredits;
|
||||||
|
if (request.FromVault) {
|
||||||
|
guild.VaultRegularCredits! -= request.RegularCredits;
|
||||||
|
await Promise.all([guild.save(), alliance.save()]);
|
||||||
|
} else {
|
||||||
|
updateCurrency(inventory, request.RegularCredits, false);
|
||||||
|
await Promise.all([inventory.save(), alliance.save()]);
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let guild: TGuildDatabaseDocument;
|
||||||
|
let guildMember: TGuildMemberDatabaseDocument | undefined;
|
||||||
|
if (request.GuildVault) {
|
||||||
|
guild = (await Guild.findById(request.GuildVault))!;
|
||||||
|
} else {
|
||||||
|
guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
guildMember = (await GuildMember.findOne(
|
||||||
|
{ accountId, guildId: guild._id },
|
||||||
|
"RegularCreditsContributed MiscItemsContributed ShipDecorationsContributed"
|
||||||
|
))!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.RegularCredits) {
|
||||||
|
updateCurrency(inventory, request.RegularCredits, false);
|
||||||
|
|
||||||
|
guild.VaultRegularCredits ??= 0;
|
||||||
|
guild.VaultRegularCredits += request.RegularCredits;
|
||||||
|
|
||||||
|
if (guildMember) {
|
||||||
|
guildMember.RegularCreditsContributed ??= 0;
|
||||||
|
guildMember.RegularCreditsContributed += request.RegularCredits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request.MiscItems.length) {
|
||||||
|
addVaultMiscItems(guild, request.MiscItems);
|
||||||
|
for (const item of request.MiscItems) {
|
||||||
|
if (guildMember) {
|
||||||
|
addGuildMemberMiscItemContribution(guildMember, item);
|
||||||
|
}
|
||||||
|
addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request.ShipDecorations.length) {
|
||||||
|
addVaultShipDecos(guild, request.ShipDecorations);
|
||||||
|
for (const item of request.ShipDecorations) {
|
||||||
|
if (guildMember) {
|
||||||
|
addGuildMemberShipDecoContribution(guildMember, item);
|
||||||
|
}
|
||||||
|
addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request.FusionTreasures.length) {
|
||||||
|
addVaultFusionTreasures(guild, request.FusionTreasures);
|
||||||
|
for (const item of request.FusionTreasures) {
|
||||||
|
addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises: Promise<unknown>[] = [guild.save(), inventory.save()];
|
||||||
|
if (guildMember) {
|
||||||
|
promises.push(guildMember.save());
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IContributeToVaultRequest {
|
||||||
|
RegularCredits: number;
|
||||||
|
MiscItems: IMiscItem[];
|
||||||
|
ShipDecorations: ITypeCount[];
|
||||||
|
FusionTreasures: IFusionTreasure[];
|
||||||
|
Alliance?: boolean;
|
||||||
|
FromVault?: boolean;
|
||||||
|
GuildVault?: string;
|
||||||
|
}
|
50
src/controllers/api/createAllianceController.ts
Normal file
50
src/controllers/api/createAllianceController.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getAllianceClient } from "@/src/services/guildService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const createAllianceController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
|
const guild = (await Guild.findById(inventory.GuildId!, "Name Tier AllianceId"))!;
|
||||||
|
if (guild.AllianceId) {
|
||||||
|
res.status(400).send("Guild is already in an alliance").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const guildMember = (await GuildMember.findOne({ guildId: guild._id, accountId }, "rank"))!;
|
||||||
|
if (guildMember.rank > 1) {
|
||||||
|
res.status(400).send("Invalid permission").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = getJSONfromString<ICreateAllianceRequest>(String(req.body));
|
||||||
|
const alliance = new Alliance({ Name: data.allianceName });
|
||||||
|
try {
|
||||||
|
await alliance.save();
|
||||||
|
} catch (e) {
|
||||||
|
res.status(400).send("Alliance name already in use").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
guild.AllianceId = alliance._id;
|
||||||
|
await Promise.all([
|
||||||
|
guild.save(),
|
||||||
|
AllianceMember.insertOne({
|
||||||
|
allianceId: alliance._id,
|
||||||
|
guildId: guild._id,
|
||||||
|
Pending: false,
|
||||||
|
Permissions:
|
||||||
|
GuildPermission.Ruler |
|
||||||
|
GuildPermission.Promoter |
|
||||||
|
GuildPermission.Recruiter |
|
||||||
|
GuildPermission.Treasurer |
|
||||||
|
GuildPermission.ChatModerator
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
res.json(await getAllianceClient(alliance, guild));
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ICreateAllianceRequest {
|
||||||
|
allianceName: string;
|
||||||
|
}
|
@ -1,38 +1,44 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { Guild } from "@/src/models/guildModel";
|
import { createUniqueClanName, getGuildClient, giveClanKey } from "@/src/services/guildService";
|
||||||
import { ICreateGuildRequest } from "@/src/types/guildTypes";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
export const createGuildController: RequestHandler = async (req, res) => {
|
||||||
const createGuildController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const payload = getJSONfromString(String(req.body)) as ICreateGuildRequest;
|
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
|
||||||
|
|
||||||
|
// Remove pending applications for this account
|
||||||
|
await GuildMember.deleteMany({ accountId, status: 1 });
|
||||||
|
|
||||||
// Create guild on database
|
// Create guild on database
|
||||||
const guild = new Guild({
|
const guild = new Guild({
|
||||||
Name: payload.guildName
|
Name: await createUniqueClanName(payload.guildName)
|
||||||
});
|
});
|
||||||
await guild.save();
|
await guild.save();
|
||||||
|
|
||||||
// Update inventory
|
// Create guild member on database
|
||||||
const inventory = await Inventory.findOne({ accountOwnerId: accountId });
|
await GuildMember.insertOne({
|
||||||
if (inventory) {
|
accountId: accountId,
|
||||||
// Set GuildId
|
guildId: guild._id,
|
||||||
inventory.GuildId = guild._id;
|
status: 0,
|
||||||
|
rank: 0
|
||||||
// Give clan key (TODO: This should only be a blueprint)
|
|
||||||
inventory.LevelKeys ??= [];
|
|
||||||
inventory.LevelKeys.push({
|
|
||||||
ItemType: "/Lotus/Types/Keys/DojoKey",
|
|
||||||
ItemCount: 1
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const inventory = await getInventory(accountId, "GuildId LevelKeys Recipes");
|
||||||
|
inventory.GuildId = guild._id;
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
giveClanKey(inventory, inventoryChanges);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
}
|
|
||||||
|
|
||||||
res.json(guild);
|
res.json({
|
||||||
|
...(await getGuildClient(guild, accountId)),
|
||||||
|
InventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export { createGuildController };
|
interface ICreateGuildRequest {
|
||||||
|
guildName: string;
|
||||||
|
}
|
||||||
|
27
src/controllers/api/creditsController.ts
Normal file
27
src/controllers/api/creditsController.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
|
export const creditsController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
|
||||||
|
const inventory = await getInventory(accountId, "RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits");
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
RegularCredits: inventory.RegularCredits,
|
||||||
|
TradesRemaining: inventory.TradesRemaining,
|
||||||
|
PremiumCreditsFree: inventory.PremiumCreditsFree,
|
||||||
|
PremiumCredits: inventory.PremiumCredits
|
||||||
|
};
|
||||||
|
|
||||||
|
if (config.infiniteCredits) {
|
||||||
|
response.RegularCredits = 999999999;
|
||||||
|
}
|
||||||
|
if (config.infinitePlatinum) {
|
||||||
|
response.PremiumCreditsFree = 0;
|
||||||
|
response.PremiumCredits = 999999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(response);
|
||||||
|
};
|
28
src/controllers/api/crewMembersController.ts
Normal file
28
src/controllers/api/crewMembersController.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { ICrewMemberClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
|
||||||
|
export const crewMembersController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "CrewMembers");
|
||||||
|
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
|
||||||
|
const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
|
||||||
|
dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
|
||||||
|
dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
|
||||||
|
dbCrewMember.WeaponConfigIdx = data.crewMember.WeaponConfigIdx;
|
||||||
|
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
|
||||||
|
dbCrewMember.Configs = data.crewMember.Configs;
|
||||||
|
dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
crewMemberId: data.crewMember.ItemId.$oid,
|
||||||
|
NemesisFingerprint: data.crewMember.NemesisFingerprint
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ICrewMembersRequest {
|
||||||
|
crewMember: ICrewMemberClient;
|
||||||
|
}
|
84
src/controllers/api/crewShipIdentifySalvageController.ts
Normal file
84
src/controllers/api/crewShipIdentifySalvageController.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import {
|
||||||
|
addCrewShipSalvagedWeaponSkin,
|
||||||
|
addCrewShipRawSalvage,
|
||||||
|
getInventory,
|
||||||
|
addEquipment
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ICrewShipComponentFingerprint, IInnateDamageFingerprint } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { ExportCustoms, ExportRailjackWeapons, ExportUpgrades } from "warframe-public-export-plus";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { getRandomInt } from "@/src/services/rngService";
|
||||||
|
import { IFingerprintStat } from "@/src/helpers/rivenHelper";
|
||||||
|
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
|
|
||||||
|
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(
|
||||||
|
accountId,
|
||||||
|
"CrewShipSalvagedWeaponSkins CrewShipSalvagedWeapons CrewShipRawSalvage"
|
||||||
|
);
|
||||||
|
const payload = getJSONfromString<ICrewShipIdentifySalvageRequest>(String(req.body));
|
||||||
|
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
if (payload.ItemType in ExportCustoms) {
|
||||||
|
const meta = ExportCustoms[payload.ItemType];
|
||||||
|
let upgradeFingerprint: ICrewShipComponentFingerprint = { compat: payload.ItemType, buffs: [] };
|
||||||
|
if (meta.subroutines) {
|
||||||
|
upgradeFingerprint = {
|
||||||
|
SubroutineIndex: getRandomInt(0, meta.subroutines.length - 1),
|
||||||
|
...upgradeFingerprint
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (const upgrade of meta.randomisedUpgrades!) {
|
||||||
|
upgradeFingerprint.buffs.push({ Tag: upgrade.tag, Value: Math.trunc(Math.random() * 0x40000000) });
|
||||||
|
}
|
||||||
|
addCrewShipSalvagedWeaponSkin(
|
||||||
|
inventory,
|
||||||
|
payload.ItemType,
|
||||||
|
JSON.stringify(upgradeFingerprint),
|
||||||
|
inventoryChanges
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const meta = ExportRailjackWeapons[payload.ItemType];
|
||||||
|
let defaultOverwrites: Partial<IEquipmentDatabase> | undefined;
|
||||||
|
if (meta.defaultUpgrades?.[0]) {
|
||||||
|
const upgradeType = meta.defaultUpgrades[0].ItemType;
|
||||||
|
const upgradeMeta = ExportUpgrades[upgradeType];
|
||||||
|
const buffs: IFingerprintStat[] = [];
|
||||||
|
for (const buff of upgradeMeta.upgradeEntries!) {
|
||||||
|
buffs.push({
|
||||||
|
Tag: buff.tag,
|
||||||
|
Value: Math.trunc(Math.random() * 0x40000000)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
defaultOverwrites = {
|
||||||
|
UpgradeType: upgradeType,
|
||||||
|
UpgradeFingerprint: JSON.stringify({
|
||||||
|
compat: payload.ItemType,
|
||||||
|
buffs
|
||||||
|
} satisfies IInnateDamageFingerprint)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
addEquipment(inventory, "CrewShipSalvagedWeapons", payload.ItemType, defaultOverwrites, inventoryChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
inventoryChanges.CrewShipRawSalvage = [
|
||||||
|
{
|
||||||
|
ItemType: payload.ItemType,
|
||||||
|
ItemCount: -1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
addCrewShipRawSalvage(inventory, inventoryChanges.CrewShipRawSalvage);
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ICrewShipIdentifySalvageRequest {
|
||||||
|
ItemType: string;
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Guild } from "@/src/models/guildModel";
|
||||||
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => {
|
||||||
|
const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body));
|
||||||
|
const guild = (await Guild.findById(data.g, "DojoComponents"))!;
|
||||||
|
const component = guild.DojoComponents.id(data.c)!;
|
||||||
|
if (req.query.act == "f") {
|
||||||
|
res.json({
|
||||||
|
results: component.Leaderboard ?? []
|
||||||
|
});
|
||||||
|
} else if (req.query.act == "p") {
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
component.Leaderboard ??= [];
|
||||||
|
const entry = component.Leaderboard.find(x => x.n == account.DisplayName);
|
||||||
|
if (entry) {
|
||||||
|
entry.s = data.s!;
|
||||||
|
} else {
|
||||||
|
component.Leaderboard.push({
|
||||||
|
s: data.s!,
|
||||||
|
n: account.DisplayName,
|
||||||
|
r: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
component.Leaderboard.sort((a, b) => a.s - b.s); // In this case, the score is the time in milliseconds, so smaller is better.
|
||||||
|
if (component.Leaderboard.length > 10) {
|
||||||
|
component.Leaderboard.shift();
|
||||||
|
}
|
||||||
|
let r = 0;
|
||||||
|
for (const entry of component.Leaderboard) {
|
||||||
|
entry.r = ++r;
|
||||||
|
}
|
||||||
|
await guild.save();
|
||||||
|
res.status(200).end();
|
||||||
|
} else {
|
||||||
|
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||||
|
throw new Error(`unknown customObstacleCourseLeaderboard act: ${String(req.query.act)}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ICustomObstacleCourseLeaderboardRequest {
|
||||||
|
g: string;
|
||||||
|
c: string;
|
||||||
|
s?: number; // act=p
|
||||||
|
}
|
21
src/controllers/api/customizeGuildRanksController.ts
Normal file
21
src/controllers/api/customizeGuildRanksController.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { getGuildForRequest, hasGuildPermission } from "@/src/services/guildService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission, IGuildRank } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const customizeGuildRanksController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const guild = await getGuildForRequest(req);
|
||||||
|
const payload = JSON.parse(String(req.body)) as ICustomizeGuildRanksRequest;
|
||||||
|
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Ruler))) {
|
||||||
|
res.status(400).json("Invalid permission");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
guild.Ranks = payload.GuildRanks;
|
||||||
|
await guild.save();
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ICustomizeGuildRanksRequest {
|
||||||
|
GuildRanks: IGuildRank[];
|
||||||
|
}
|
17
src/controllers/api/declineAllianceInviteController.ts
Normal file
17
src/controllers/api/declineAllianceInviteController.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { AllianceMember, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const declineAllianceInviteController: RequestHandler = async (req, res) => {
|
||||||
|
// Check requester is a warlord in their guild
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const guildMember = (await GuildMember.findOne({ accountId, status: 0 }))!;
|
||||||
|
if (guildMember.rank > 1) {
|
||||||
|
res.status(400).json({ Error: 104 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await AllianceMember.deleteOne({ allianceId: req.query.allianceId, guildId: guildMember.guildId });
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
14
src/controllers/api/declineGuildInviteController.ts
Normal file
14
src/controllers/api/declineGuildInviteController.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const declineGuildInviteController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountForRequest(req);
|
||||||
|
|
||||||
|
await GuildMember.deleteOne({
|
||||||
|
accountId: accountId,
|
||||||
|
guildId: req.query.clanId as string
|
||||||
|
});
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
33
src/controllers/api/destroyDojoDecoController.ts
Normal file
33
src/controllers/api/destroyDojoDecoController.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
getDojoClient,
|
||||||
|
getGuildForRequestEx,
|
||||||
|
hasAccessToDojo,
|
||||||
|
hasGuildPermission,
|
||||||
|
removeDojoDeco
|
||||||
|
} from "@/src/services/guildService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const destroyDojoDecoController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId LevelKeys");
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
|
||||||
|
res.json({ DojoRequestStatus: -1 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest;
|
||||||
|
|
||||||
|
removeDojoDeco(guild, request.ComponentId, request.DecoId);
|
||||||
|
|
||||||
|
await guild.save();
|
||||||
|
res.json(await getDojoClient(guild, 0, request.ComponentId));
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IDestroyDojoDecoRequest {
|
||||||
|
DecoType: string;
|
||||||
|
ComponentId: string;
|
||||||
|
DecoId: string;
|
||||||
|
}
|
67
src/controllers/api/divvyAllianceVaultController.ts
Normal file
67
src/controllers/api/divvyAllianceVaultController.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { parallelForeach } from "@/src/utils/async-utils";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const divvyAllianceVaultController: RequestHandler = async (req, res) => {
|
||||||
|
// Afaict, there's no way to put anything other than credits in the alliance vault (anymore?), so just no-op if this is not a request to divvy credits.
|
||||||
|
if (req.query.credits == "1") {
|
||||||
|
// Check requester is a warlord in their guild
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
|
||||||
|
if (guildMember.rank > 1) {
|
||||||
|
res.status(400).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check guild has treasurer permissions in the alliance
|
||||||
|
const allianceMember = (await AllianceMember.findOne({
|
||||||
|
allianceId: req.query.allianceId,
|
||||||
|
guildId: guildMember.guildId
|
||||||
|
}))!;
|
||||||
|
if (!(allianceMember.Permissions & GuildPermission.Treasurer)) {
|
||||||
|
res.status(400).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allianceMembers = await AllianceMember.find({ allianceId: req.query.allianceId });
|
||||||
|
const memberCounts: Record<string, number> = {};
|
||||||
|
let totalMembers = 0;
|
||||||
|
await parallelForeach(allianceMembers, async allianceMember => {
|
||||||
|
const memberCount = await GuildMember.countDocuments({
|
||||||
|
guildId: allianceMember.guildId
|
||||||
|
});
|
||||||
|
memberCounts[allianceMember.guildId.toString()] = memberCount;
|
||||||
|
totalMembers += memberCount;
|
||||||
|
});
|
||||||
|
logger.debug(`alliance has ${totalMembers} members between all its clans`);
|
||||||
|
|
||||||
|
const alliance = (await Alliance.findById(allianceMember.allianceId, "VaultRegularCredits"))!;
|
||||||
|
if (alliance.VaultRegularCredits) {
|
||||||
|
let creditsHandedOutInTotal = 0;
|
||||||
|
await parallelForeach(allianceMembers, async allianceMember => {
|
||||||
|
const memberCount = memberCounts[allianceMember.guildId.toString()];
|
||||||
|
const cutPercentage = memberCount / totalMembers;
|
||||||
|
const creditsToHandOut = Math.trunc(alliance.VaultRegularCredits! * cutPercentage);
|
||||||
|
logger.debug(
|
||||||
|
`${allianceMember.guildId.toString()} has ${memberCount} member(s) = ${Math.trunc(cutPercentage * 100)}% of alliance; giving ${creditsToHandOut} credit(s)`
|
||||||
|
);
|
||||||
|
if (creditsToHandOut != 0) {
|
||||||
|
await Guild.updateOne(
|
||||||
|
{ _id: allianceMember.guildId },
|
||||||
|
{ $inc: { VaultRegularCredits: creditsToHandOut } }
|
||||||
|
);
|
||||||
|
creditsHandedOutInTotal += creditsToHandOut;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alliance.VaultRegularCredits -= creditsHandedOutInTotal;
|
||||||
|
logger.debug(
|
||||||
|
`handed out ${creditsHandedOutInTotal} credits; alliance vault now has ${alliance.VaultRegularCredits} credit(s)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await alliance.save();
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
};
|
76
src/controllers/api/dojoComponentRushController.ts
Normal file
76
src/controllers/api/dojoComponentRushController.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
|
||||||
|
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, scaleRequiredCount } from "@/src/services/guildService";
|
||||||
|
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IDojoContributable } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
interface IDojoComponentRushRequest {
|
||||||
|
DecoType?: string;
|
||||||
|
DecoId?: string;
|
||||||
|
ComponentId: string;
|
||||||
|
Amount: number;
|
||||||
|
VaultAmount: number;
|
||||||
|
AllianceVaultAmount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dojoComponentRushController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
if (!hasAccessToDojo(inventory)) {
|
||||||
|
res.json({ DojoRequestStatus: -1 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
const request = JSON.parse(String(req.body)) as IDojoComponentRushRequest;
|
||||||
|
const component = guild.DojoComponents.id(request.ComponentId)!;
|
||||||
|
|
||||||
|
let platinumDonated = request.Amount;
|
||||||
|
const inventoryChanges = updateCurrency(inventory, request.Amount, true);
|
||||||
|
if (request.VaultAmount) {
|
||||||
|
platinumDonated += request.VaultAmount;
|
||||||
|
guild.VaultPremiumCredits! -= request.VaultAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.DecoId) {
|
||||||
|
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
|
||||||
|
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
|
||||||
|
processContribution(guild, deco, meta, platinumDonated);
|
||||||
|
} else {
|
||||||
|
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
|
||||||
|
processContribution(guild, component, meta, platinumDonated);
|
||||||
|
|
||||||
|
const entry = guild.RoomChanges?.find(x => x.componentId.equals(component._id));
|
||||||
|
if (entry) {
|
||||||
|
entry.dateTime = component.CompletionTime!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }, "PremiumCreditsContributed"))!;
|
||||||
|
guildMember.PremiumCreditsContributed ??= 0;
|
||||||
|
guildMember.PremiumCreditsContributed += request.Amount;
|
||||||
|
|
||||||
|
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
...(await getDojoClient(guild, 0, component._id)),
|
||||||
|
InventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const processContribution = (
|
||||||
|
guild: TGuildDatabaseDocument,
|
||||||
|
component: IDojoContributable,
|
||||||
|
meta: IDojoBuild,
|
||||||
|
platinumDonated: number
|
||||||
|
): void => {
|
||||||
|
const fullPlatinumCost = scaleRequiredCount(guild.Tier, meta.skipTimePrice);
|
||||||
|
const fullDurationSeconds = meta.time;
|
||||||
|
const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
|
||||||
|
component.CompletionTime = new Date(
|
||||||
|
component.CompletionTime!.getTime() - secondsPerPlatinum * platinumDonated * 1000
|
||||||
|
);
|
||||||
|
component.RushPlatinum ??= 0;
|
||||||
|
component.RushPlatinum += platinumDonated;
|
||||||
|
};
|
@ -1,5 +1,11 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
// Arbiter Dojo endpoints, not really used by us as we don't provide a ContentURL.
|
||||||
|
|
||||||
export const dojoController: RequestHandler = (_req, res) => {
|
export const dojoController: RequestHandler = (_req, res) => {
|
||||||
res.json("-1"); // Tell client to use authorised request.
|
res.json("-1"); // Tell client to use authorised request.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setDojoURLController: RequestHandler = (_req, res) => {
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
@ -1,7 +1,143 @@
|
|||||||
|
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { fromStoreItem } from "@/src/services/itemDataService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getRandomInt, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
||||||
|
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||||
|
import { IDroneClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportDrones, ExportResources, ExportSystems } from "warframe-public-export-plus";
|
||||||
|
|
||||||
const dronesController: RequestHandler = (_req, res) => {
|
export const dronesController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
if ("GetActive" in req.query) {
|
||||||
|
const inventory = await getInventory(accountId, "Drones");
|
||||||
|
const activeDrones: IActiveDrone[] = [];
|
||||||
|
for (const drone of inventory.Drones) {
|
||||||
|
if (drone.DeployTime) {
|
||||||
|
activeDrones.push({
|
||||||
|
DeployTime: toMongoDate(drone.DeployTime),
|
||||||
|
System: drone.System!,
|
||||||
|
ItemId: toOid(drone._id),
|
||||||
|
ItemType: drone.ItemType,
|
||||||
|
CurrentHP: drone.CurrentHP,
|
||||||
|
DamageTime: toMongoDate(drone.DamageTime!),
|
||||||
|
PendingDamage: drone.PendingDamage!,
|
||||||
|
Resources: [
|
||||||
|
{
|
||||||
|
ItemType: drone.ResourceType!,
|
||||||
|
BinTotal: drone.ResourceCount!,
|
||||||
|
StartTime: toMongoDate(drone.DeployTime)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.json({
|
||||||
|
ActiveDrones: activeDrones
|
||||||
|
});
|
||||||
|
} else if ("droneId" in req.query && "systemIndex" in req.query) {
|
||||||
|
const inventory = await getInventory(accountId, "Drones");
|
||||||
|
const drone = inventory.Drones.id(req.query.droneId as string)!;
|
||||||
|
const droneMeta = ExportDrones[drone.ItemType];
|
||||||
|
drone.DeployTime = config.instantResourceExtractorDrones ? new Date(0) : new Date();
|
||||||
|
if (drone.RepairStart) {
|
||||||
|
const repairMinutes = (Date.now() - drone.RepairStart.getTime()) / 60_000;
|
||||||
|
const hpPerMinute = droneMeta.repairRate / 60;
|
||||||
|
drone.CurrentHP = Math.min(drone.CurrentHP + Math.round(repairMinutes * hpPerMinute), droneMeta.durability);
|
||||||
|
drone.RepairStart = undefined;
|
||||||
|
}
|
||||||
|
drone.System = parseInt(req.query.systemIndex as string);
|
||||||
|
const system = ExportSystems[drone.System - 1];
|
||||||
|
drone.DamageTime = config.instantResourceExtractorDrones
|
||||||
|
? new Date()
|
||||||
|
: new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000));
|
||||||
|
drone.PendingDamage =
|
||||||
|
!config.noResourceExtractorDronesDamage && Math.random() < system.damageChance
|
||||||
|
? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue)
|
||||||
|
: 0;
|
||||||
|
const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!;
|
||||||
|
//logger.debug(`drone rolled`, resource);
|
||||||
|
drone.ResourceType = fromStoreItem(resource.StoreItem);
|
||||||
|
const resourceMeta = ExportResources[drone.ResourceType];
|
||||||
|
if (resourceMeta.pickupQuantity) {
|
||||||
|
const pickupsToCollect = droneMeta.binCapacity * droneMeta.capacityMultipliers[resource.Rarity];
|
||||||
|
drone.ResourceCount = 0;
|
||||||
|
for (let i = 0; i != pickupsToCollect; ++i) {
|
||||||
|
drone.ResourceCount += getRandomInt(
|
||||||
|
resourceMeta.pickupQuantity.minValue,
|
||||||
|
resourceMeta.pickupQuantity.maxValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
drone.ResourceCount = 1;
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
res.json({});
|
res.json({});
|
||||||
|
} else if ("collectDroneId" in req.query) {
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const drone = inventory.Drones.id(req.query.collectDroneId as string)!;
|
||||||
|
|
||||||
|
if (new Date() >= drone.DamageTime!) {
|
||||||
|
drone.CurrentHP -= drone.PendingDamage!;
|
||||||
|
drone.RepairStart = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
if (drone.CurrentHP <= 0) {
|
||||||
|
inventory.RegularCredits += 100;
|
||||||
|
inventoryChanges.RegularCredits = 100;
|
||||||
|
inventory.Drones.pull({ _id: req.query.collectDroneId as string });
|
||||||
|
inventoryChanges.RemovedIdItems = [
|
||||||
|
{
|
||||||
|
ItemId: { $oid: req.query.collectDroneId }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
const completionTime = drone.DeployTime!.getTime() + ExportDrones[drone.ItemType].fillRate * 3600_000;
|
||||||
|
if (Date.now() >= completionTime) {
|
||||||
|
const miscItemChanges = [
|
||||||
|
{
|
||||||
|
ItemType: drone.ResourceType!,
|
||||||
|
ItemCount: drone.ResourceCount!
|
||||||
|
}
|
||||||
|
];
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
inventoryChanges.MiscItems = miscItemChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
drone.DeployTime = undefined;
|
||||||
|
drone.System = undefined;
|
||||||
|
drone.DamageTime = undefined;
|
||||||
|
drone.PendingDamage = undefined;
|
||||||
|
drone.ResourceType = undefined;
|
||||||
|
drone.ResourceCount = undefined;
|
||||||
|
|
||||||
|
inventoryChanges.Drones = [drone.toJSON<IDroneClient>()];
|
||||||
|
}
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(`drones.php query not handled`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { dronesController };
|
interface IActiveDrone {
|
||||||
|
DeployTime: IMongoDate;
|
||||||
|
System: number;
|
||||||
|
ItemId: IOid;
|
||||||
|
ItemType: string;
|
||||||
|
CurrentHP: number;
|
||||||
|
DamageTime: IMongoDate;
|
||||||
|
PendingDamage: number;
|
||||||
|
Resources: {
|
||||||
|
ItemType: string;
|
||||||
|
BinTotal: number;
|
||||||
|
StartTime: IMongoDate;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
60
src/controllers/api/endlessXpController.ts
Normal file
60
src/controllers/api/endlessXpController.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
|
||||||
|
export const endlessXpController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const payload = getJSONfromString<IEndlessXpRequest>(String(req.body));
|
||||||
|
|
||||||
|
inventory.EndlessXP ??= [];
|
||||||
|
const entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
||||||
|
if (entry) {
|
||||||
|
entry.Choices = payload.Choices;
|
||||||
|
} else {
|
||||||
|
inventory.EndlessXP.push({
|
||||||
|
Category: payload.Category,
|
||||||
|
Choices: payload.Choices
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
NewProgress: {
|
||||||
|
Category: payload.Category,
|
||||||
|
Earn: 0,
|
||||||
|
Claim: 0,
|
||||||
|
BonusAvailable: {
|
||||||
|
$date: {
|
||||||
|
$numberLong: "9999999999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Expiry: {
|
||||||
|
$date: {
|
||||||
|
$numberLong: "9999999999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Choices: payload.Choices,
|
||||||
|
PendingRewards: [
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 190,
|
||||||
|
Rewards: [
|
||||||
|
{
|
||||||
|
StoreItem: "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerHealthAuraMod",
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IEndlessXpRequest {
|
||||||
|
Mode: string; // "r"
|
||||||
|
Category: TEndlessXpCategory;
|
||||||
|
Choices: string[];
|
||||||
|
}
|
71
src/controllers/api/entratiLabConquestModeController.ts
Normal file
71
src/controllers/api/entratiLabConquestModeController.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const entratiLabConquestModeController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(
|
||||||
|
accountId,
|
||||||
|
"EntratiVaultCountResetDate EntratiVaultCountLastPeriod EntratiLabConquestUnlocked EchoesHexConquestUnlocked EchoesHexConquestActiveFrameVariants EchoesHexConquestActiveStickers EntratiLabConquestActiveFrameVariants EntratiLabConquestCacheScoreMission EchoesHexConquestCacheScoreMission"
|
||||||
|
);
|
||||||
|
const body = getJSONfromString<IEntratiLabConquestModeRequest>(String(req.body));
|
||||||
|
if (!inventory.EntratiVaultCountResetDate || Date.now() >= inventory.EntratiVaultCountResetDate.getTime()) {
|
||||||
|
const EPOCH = 1734307200 * 1000; // Mondays, amirite?
|
||||||
|
const day = Math.trunc((Date.now() - EPOCH) / 86400000);
|
||||||
|
const week = Math.trunc(day / 7);
|
||||||
|
const weekStart = EPOCH + week * 604800000;
|
||||||
|
const weekEnd = weekStart + 604800000;
|
||||||
|
inventory.EntratiVaultCountLastPeriod = 0;
|
||||||
|
inventory.EntratiVaultCountResetDate = new Date(weekEnd);
|
||||||
|
if (inventory.EntratiLabConquestUnlocked) {
|
||||||
|
inventory.EntratiLabConquestUnlocked = 0;
|
||||||
|
inventory.EntratiLabConquestCacheScoreMission = 0;
|
||||||
|
inventory.EntratiLabConquestActiveFrameVariants = [];
|
||||||
|
}
|
||||||
|
if (inventory.EchoesHexConquestUnlocked) {
|
||||||
|
inventory.EchoesHexConquestUnlocked = 0;
|
||||||
|
inventory.EchoesHexConquestCacheScoreMission = 0;
|
||||||
|
inventory.EchoesHexConquestActiveFrameVariants = [];
|
||||||
|
inventory.EchoesHexConquestActiveStickers = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (body.BuyMode) {
|
||||||
|
inventory.EntratiVaultCountLastPeriod! += 2;
|
||||||
|
if (body.IsEchoesDeepArchemedea) {
|
||||||
|
inventory.EchoesHexConquestUnlocked = 1;
|
||||||
|
} else {
|
||||||
|
inventory.EntratiLabConquestUnlocked = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (body.IsEchoesDeepArchemedea) {
|
||||||
|
if (inventory.EchoesHexConquestUnlocked) {
|
||||||
|
inventory.EchoesHexConquestActiveFrameVariants = body.EchoesHexConquestActiveFrameVariants!;
|
||||||
|
inventory.EchoesHexConquestActiveStickers = body.EchoesHexConquestActiveStickers!;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (inventory.EntratiLabConquestUnlocked) {
|
||||||
|
inventory.EntratiLabConquestActiveFrameVariants = body.EntratiLabConquestActiveFrameVariants!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
EntratiVaultCountResetDate: toMongoDate(inventory.EntratiVaultCountResetDate),
|
||||||
|
EntratiVaultCountLastPeriod: inventory.EntratiVaultCountLastPeriod,
|
||||||
|
EntratiLabConquestUnlocked: inventory.EntratiLabConquestUnlocked,
|
||||||
|
EntratiLabConquestCacheScoreMission: inventory.EntratiLabConquestCacheScoreMission,
|
||||||
|
EchoesHexConquestUnlocked: inventory.EchoesHexConquestUnlocked,
|
||||||
|
EchoesHexConquestCacheScoreMission: inventory.EchoesHexConquestCacheScoreMission
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IEntratiLabConquestModeRequest {
|
||||||
|
BuyMode?: number;
|
||||||
|
IsEchoesDeepArchemedea?: number;
|
||||||
|
EntratiLabConquestUnlocked?: number;
|
||||||
|
EntratiLabConquestActiveFrameVariants?: string[];
|
||||||
|
EchoesHexConquestUnlocked?: number;
|
||||||
|
EchoesHexConquestActiveFrameVariants?: string[];
|
||||||
|
EchoesHexConquestActiveStickers?: string[];
|
||||||
|
}
|
@ -1,20 +1,23 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { WeaponTypeInternal } from "@/src/services/itemDataService";
|
import { getRecipe, WeaponTypeInternal } from "@/src/services/itemDataService";
|
||||||
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
export const evolveWeaponController: RequestHandler = async (req, res) => {
|
export const evolveWeaponController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const payload = getJSONfromString(String(req.body)) as IEvolveWeaponRequest;
|
const payload = getJSONfromString<IEvolveWeaponRequest>(String(req.body));
|
||||||
console.assert(payload.Action == "EWA_INSTALL");
|
|
||||||
|
|
||||||
// TODO: We should remove the Genesis item & its resources, but currently we don't know these "recipes".
|
const recipe = getRecipe(payload.Recipe)!;
|
||||||
|
if (payload.Action == "EWA_INSTALL") {
|
||||||
|
addMiscItems(
|
||||||
|
inventory,
|
||||||
|
recipe.ingredients.map(x => ({ ItemType: x.ItemType, ItemCount: x.ItemCount * -1 }))
|
||||||
|
);
|
||||||
|
|
||||||
const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
|
const item = inventory[payload.Category].id(req.query.ItemId as string)!;
|
||||||
item.Features ??= 0;
|
item.Features ??= 0;
|
||||||
item.Features |= EquipmentFeatures.INCARNON_GENESIS;
|
item.Features |= EquipmentFeatures.INCARNON_GENESIS;
|
||||||
|
|
||||||
@ -28,13 +31,26 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
|
|||||||
ItemType: payload.EvoType
|
ItemType: payload.EvoType
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if (payload.Action == "EWA_UNINSTALL") {
|
||||||
|
addMiscItems(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: recipe.resultType,
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const item = inventory[payload.Category].id(req.query.ItemId as string)!;
|
||||||
|
item.Features! &= ~EquipmentFeatures.INCARNON_GENESIS;
|
||||||
|
} else {
|
||||||
|
throw new Error(`unexpected evolve weapon action: ${payload.Action}`);
|
||||||
|
}
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.end();
|
res.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IEvolveWeaponRequest {
|
interface IEvolveWeaponRequest {
|
||||||
Action: "EWA_INSTALL";
|
Action: string;
|
||||||
Category: WeaponTypeInternal;
|
Category: WeaponTypeInternal;
|
||||||
Recipe: string; // e.g. "/Lotus/Types/Items/MiscItems/IncarnonAdapters/UnlockerBlueprints/DespairIncarnonBlueprint"
|
Recipe: string; // e.g. "/Lotus/Types/Items/MiscItems/IncarnonAdapters/UnlockerBlueprints/DespairIncarnonBlueprint"
|
||||||
UninstallRecipe: "";
|
UninstallRecipe: "";
|
||||||
|
@ -1,31 +1,28 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getSession } from "@/src/managers/sessionManager";
|
import { getSession } from "@/src/managers/sessionManager";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { IFindSessionRequest } from "@/src/types/session";
|
||||||
|
|
||||||
//TODO: cleanup
|
export const findSessionsController: RequestHandler = (_req, res) => {
|
||||||
const findSessionsController: RequestHandler = (_req, res) => {
|
const req = JSON.parse(String(_req.body)) as IFindSessionRequest;
|
||||||
const reqBody = JSON.parse(String(_req.body));
|
logger.debug("FindSession Request ", req);
|
||||||
logger.debug("FindSession Request ", { reqBody });
|
|
||||||
const req = JSON.parse(String(_req.body));
|
|
||||||
if (req.id != undefined) {
|
if (req.id != undefined) {
|
||||||
logger.debug("Found ID");
|
logger.debug("Found ID");
|
||||||
const session = getSession(req.id as string);
|
const session = getSession(req.id);
|
||||||
|
|
||||||
if (session) res.json({ queryId: req.queryId, Sessions: session });
|
if (session.length) res.json({ queryId: req.queryId, Sessions: session });
|
||||||
else res.json({});
|
else res.json({});
|
||||||
} else if (req.originalSessionId != undefined) {
|
} else if (req.originalSessionId != undefined) {
|
||||||
logger.debug("Found OriginalSessionID");
|
logger.debug("Found OriginalSessionID");
|
||||||
|
|
||||||
const session = getSession(req.originalSessionId as string);
|
const session = getSession(req.originalSessionId);
|
||||||
if (session) res.json({ queryId: req.queryId, Sessions: session });
|
if (session.length) res.json({ queryId: req.queryId, Sessions: session });
|
||||||
else res.json({});
|
else res.json({});
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Found SessionRequest");
|
logger.debug("Found SessionRequest");
|
||||||
|
|
||||||
const session = getSession(String(_req.body));
|
const session = getSession(req);
|
||||||
if (session) res.json({ queryId: req.queryId, Sessions: session });
|
if (session.length) res.json({ queryId: req.queryId, Sessions: session });
|
||||||
else res.json({});
|
else res.json({});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { findSessionsController };
|
|
||||||
|
47
src/controllers/api/fishmongerController.ts
Normal file
47
src/controllers/api/fishmongerController.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { addMiscItems, addStanding, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportResources } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const fishmongerController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const body = getJSONfromString<IFishmongerRequest>(String(req.body));
|
||||||
|
const miscItemChanges: IMiscItem[] = [];
|
||||||
|
let syndicateTag: string | undefined;
|
||||||
|
let gainedStanding = 0;
|
||||||
|
for (const fish of body.Fish) {
|
||||||
|
const fishData = ExportResources[fish.ItemType];
|
||||||
|
if (req.query.dissect == "1") {
|
||||||
|
for (const part of fishData.dissectionParts!) {
|
||||||
|
const partItem = miscItemChanges.find(x => x.ItemType == part.ItemType);
|
||||||
|
if (partItem) {
|
||||||
|
partItem.ItemCount += part.ItemCount * fish.ItemCount;
|
||||||
|
} else {
|
||||||
|
miscItemChanges.push({ ItemType: part.ItemType, ItemCount: part.ItemCount * fish.ItemCount });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
syndicateTag = fishData.syndicateTag!;
|
||||||
|
gainedStanding += fishData.standingBonus! * fish.ItemCount;
|
||||||
|
}
|
||||||
|
miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
|
||||||
|
}
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
let affiliationMod;
|
||||||
|
if (gainedStanding && syndicateTag) affiliationMod = addStanding(inventory, syndicateTag, gainedStanding);
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: {
|
||||||
|
MiscItems: miscItemChanges
|
||||||
|
},
|
||||||
|
SyndicateTag: syndicateTag,
|
||||||
|
StandingChange: affiliationMod?.Standing || 0
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IFishmongerRequest {
|
||||||
|
Fish: IMiscItem[];
|
||||||
|
}
|
@ -1,19 +1,40 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getInventory, addMiscItems, addEquipment } from "@/src/services/inventoryService";
|
import { getInventory, addMiscItems, addEquipment, occupySlot } from "@/src/services/inventoryService";
|
||||||
import { IMiscItem, TFocusPolarity } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IMiscItem, TFocusPolarity, TEquipmentKey, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { ExportFocusUpgrades } from "warframe-public-export-plus";
|
import { ExportFocusUpgrades } from "warframe-public-export-plus";
|
||||||
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
|
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
export const focusController: RequestHandler = async (req, res) => {
|
export const focusController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
switch (req.query.op) {
|
switch (req.query.op) {
|
||||||
default:
|
default:
|
||||||
logger.error("Unhandled focus op type: " + req.query.op);
|
logger.error("Unhandled focus op type: " + String(req.query.op));
|
||||||
logger.debug(req.body.toString());
|
logger.debug(String(req.body));
|
||||||
res.end();
|
res.end();
|
||||||
break;
|
break;
|
||||||
|
case FocusOperation.InstallLens: {
|
||||||
|
const request = JSON.parse(String(req.body)) as ILensInstallRequest;
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const item = inventory[request.Category].id(request.WeaponId);
|
||||||
|
if (item) {
|
||||||
|
item.FocusLens = request.LensType;
|
||||||
|
addMiscItems(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: request.LensType,
|
||||||
|
ItemCount: -1
|
||||||
|
} satisfies IMiscItem
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
weaponId: request.WeaponId,
|
||||||
|
lensType: request.LensType
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case FocusOperation.UnlockWay: {
|
case FocusOperation.UnlockWay: {
|
||||||
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
|
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
|
||||||
const focusPolarity = focusTypeToPolarity(focusType);
|
const focusPolarity = focusTypeToPolarity(focusType);
|
||||||
@ -33,9 +54,16 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
case FocusOperation.ActivateWay: {
|
case FocusOperation.ActivateWay: {
|
||||||
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
|
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
inventory.FocusAbility = focusType;
|
await Inventory.updateOne(
|
||||||
await inventory.save();
|
{
|
||||||
|
accountOwnerId: accountId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
FocusAbility: focusType
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -48,7 +76,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
|
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
|
||||||
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
|
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
|
||||||
}
|
}
|
||||||
inventory.FocusXP[focusPolarity] -= cost;
|
inventory.FocusXP![focusPolarity] -= cost;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
FocusTypes: request.FocusTypes,
|
FocusTypes: request.FocusTypes,
|
||||||
@ -66,7 +94,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
|
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
|
||||||
focusUpgradeDb.Level = focusUpgrade.Level;
|
focusUpgradeDb.Level = focusUpgrade.Level;
|
||||||
}
|
}
|
||||||
inventory.FocusXP[focusPolarity] -= cost;
|
inventory.FocusXP![focusPolarity] -= cost;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
FocusInfos: request.FocusInfos,
|
FocusInfos: request.FocusInfos,
|
||||||
@ -76,20 +104,24 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
case FocusOperation.SentTrainingAmplifier: {
|
case FocusOperation.SentTrainingAmplifier: {
|
||||||
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
|
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
|
||||||
const parts: string[] = [
|
const inventory = await getInventory(accountId);
|
||||||
|
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, {
|
||||||
|
ModularParts: [
|
||||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
|
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
|
||||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
|
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
|
||||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
|
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
|
||||||
];
|
]
|
||||||
const result = await addEquipment("OperatorAmps", request.StartingWeaponType, accountId, parts);
|
});
|
||||||
res.json(result);
|
occupySlot(inventory, InventorySlot.AMPS, false);
|
||||||
|
await inventory.save();
|
||||||
|
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FocusOperation.UnbindUpgrade: {
|
case FocusOperation.UnbindUpgrade: {
|
||||||
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
|
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
|
||||||
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
|
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
inventory.FocusXP[focusPolarity] -= 750_000 * request.FocusTypes.length;
|
inventory.FocusXP![focusPolarity] -= 750_000 * request.FocusTypes.length;
|
||||||
addMiscItems(inventory, [
|
addMiscItems(inventory, [
|
||||||
{
|
{
|
||||||
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem",
|
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem",
|
||||||
@ -144,6 +176,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum FocusOperation {
|
enum FocusOperation {
|
||||||
|
InstallLens = "1",
|
||||||
UnlockWay = "2",
|
UnlockWay = "2",
|
||||||
UnlockUpgrade = "3",
|
UnlockUpgrade = "3",
|
||||||
LevelUpUpgrade = "4",
|
LevelUpUpgrade = "4",
|
||||||
@ -186,6 +219,12 @@ interface ISentTrainingAmplifierRequest {
|
|||||||
StartingWeaponType: string;
|
StartingWeaponType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ILensInstallRequest {
|
||||||
|
LensType: string;
|
||||||
|
Category: TEquipmentKey;
|
||||||
|
WeaponId: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Works for ways & upgrades
|
// Works for ways & upgrades
|
||||||
const focusTypeToPolarity = (type: string): TFocusPolarity => {
|
const focusTypeToPolarity = (type: string): TFocusPolarity => {
|
||||||
return ("AP_" + type.substr(1).split("/")[3].toUpperCase()) as TFocusPolarity;
|
return ("AP_" + type.substr(1).split("/")[3].toUpperCase()) as TFocusPolarity;
|
||||||
|
52
src/controllers/api/fusionTreasuresController.ts
Normal file
52
src/controllers/api/fusionTreasuresController.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportResources } from "warframe-public-export-plus";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { addFusionTreasures, addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { IFusionTreasure, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
|
||||||
|
interface IFusionTreasureRequest {
|
||||||
|
oldTreasureName: string;
|
||||||
|
newTreasureName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseFusionTreasure = (name: string, count: number): IFusionTreasure => {
|
||||||
|
const arr = name.split("_");
|
||||||
|
return {
|
||||||
|
ItemType: arr[0],
|
||||||
|
Sockets: parseInt(arr[1], 16),
|
||||||
|
ItemCount: count
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fusionTreasuresController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const request = JSON.parse(String(req.body)) as IFusionTreasureRequest;
|
||||||
|
|
||||||
|
// Swap treasures
|
||||||
|
const oldTreasure = parseFusionTreasure(request.oldTreasureName, -1);
|
||||||
|
const newTreasure = parseFusionTreasure(request.newTreasureName, 1);
|
||||||
|
const fusionTreasureChanges = [oldTreasure, newTreasure];
|
||||||
|
addFusionTreasures(inventory, fusionTreasureChanges);
|
||||||
|
|
||||||
|
// Remove consumed stars
|
||||||
|
const miscItemChanges: IMiscItem[] = [];
|
||||||
|
const filledSockets = newTreasure.Sockets & ~oldTreasure.Sockets;
|
||||||
|
for (let i = 0; filledSockets >> i; ++i) {
|
||||||
|
if ((filledSockets >> i) & 1) {
|
||||||
|
//console.log("Socket", i, "has been filled with", ExportResources[oldTreasure.ItemType].sockets![i]);
|
||||||
|
miscItemChanges.push({
|
||||||
|
ItemType: ExportResources[oldTreasure.ItemType].sockets![i],
|
||||||
|
ItemCount: -1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
// The response itself is the inventory changes for this endpoint.
|
||||||
|
res.json({
|
||||||
|
MiscItems: miscItemChanges,
|
||||||
|
FusionTreasures: fusionTreasureChanges
|
||||||
|
});
|
||||||
|
};
|
84
src/controllers/api/gardeningController.ts
Normal file
84
src/controllers/api/gardeningController.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { addMiscItem, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { toStoreItem } from "@/src/services/itemDataService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||||
|
import { IMongoDate } from "@/src/types/commonTypes";
|
||||||
|
import { IMissionReward } from "@/src/types/missionTypes";
|
||||||
|
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { IGardeningClient } from "@/src/types/shipTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { dict_en, ExportResources } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const gardeningController: RequestHandler = async (req, res) => {
|
||||||
|
const data = getJSONfromString<IGardeningRequest>(String(req.body));
|
||||||
|
if (data.Mode != "HarvestAll") {
|
||||||
|
throw new Error(`unexpected gardening mode: ${data.Mode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const [inventory, personalRooms] = await Promise.all([
|
||||||
|
getInventory(accountId, "MiscItems"),
|
||||||
|
getPersonalRooms(accountId, "Apartment")
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Harvest plants
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
const rewards: Record<string, IMissionReward[][]> = {};
|
||||||
|
for (const planter of personalRooms.Apartment.Gardening.Planters) {
|
||||||
|
rewards[planter.Name] = [];
|
||||||
|
for (const plant of planter.Plants) {
|
||||||
|
const itemType =
|
||||||
|
"/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItem" +
|
||||||
|
plant.PlantType.substring(plant.PlantType.length - 1);
|
||||||
|
const itemCount = Math.random() < 0.775 ? 2 : 4;
|
||||||
|
|
||||||
|
addMiscItem(inventory, itemType, itemCount, inventoryChanges);
|
||||||
|
|
||||||
|
rewards[planter.Name].push([
|
||||||
|
{
|
||||||
|
StoreItem: toStoreItem(itemType),
|
||||||
|
TypeName: itemType,
|
||||||
|
ItemCount: itemCount,
|
||||||
|
DailyCooldown: false,
|
||||||
|
Rarity: itemCount == 2 ? 0.7743589743589744 : 0.22564102564102564,
|
||||||
|
TweetText: `${itemCount}x ${dict_en[ExportResources[itemType].name]} (Resource)`,
|
||||||
|
ProductCategory: "MiscItems"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh garden
|
||||||
|
personalRooms.Apartment.Gardening = createGarden();
|
||||||
|
|
||||||
|
await Promise.all([inventory.save(), personalRooms.save()]);
|
||||||
|
|
||||||
|
const planter = personalRooms.Apartment.Gardening.Planters[personalRooms.Apartment.Gardening.Planters.length - 1];
|
||||||
|
const plant = planter.Plants[planter.Plants.length - 1];
|
||||||
|
res.json({
|
||||||
|
GardenTagName: planter.Name,
|
||||||
|
PlantType: plant.PlantType,
|
||||||
|
PlotIndex: plant.PlotIndex,
|
||||||
|
EndTime: toMongoDate(plant.EndTime),
|
||||||
|
InventoryChanges: inventoryChanges,
|
||||||
|
Gardening: personalRooms.toJSON<IPersonalRoomsClient>().Apartment.Gardening,
|
||||||
|
Rewards: rewards
|
||||||
|
} satisfies IGardeningResponse);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IGardeningRequest {
|
||||||
|
Mode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGardeningResponse {
|
||||||
|
GardenTagName: string;
|
||||||
|
PlantType: string;
|
||||||
|
PlotIndex: number;
|
||||||
|
EndTime: IMongoDate;
|
||||||
|
InventoryChanges: IInventoryChanges;
|
||||||
|
Gardening: IGardeningClient;
|
||||||
|
Rewards: Record<string, IMissionReward[][]>;
|
||||||
|
}
|
@ -7,10 +7,9 @@ import { IGenericUpdate } from "@/src/types/genericUpdate";
|
|||||||
// This endpoint used to be /api/genericUpdate.php, but sometime around the Jade Shadows update, it was changed to /api/updateNodeIntros.php.
|
// This endpoint used to be /api/genericUpdate.php, but sometime around the Jade Shadows update, it was changed to /api/updateNodeIntros.php.
|
||||||
// SpaceNinjaServer supports both endpoints right now.
|
// SpaceNinjaServer supports both endpoints right now.
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
const genericUpdateController: RequestHandler = async (request, response) => {
|
const genericUpdateController: RequestHandler = async (request, response) => {
|
||||||
const accountId = await getAccountIdForRequest(request);
|
const accountId = await getAccountIdForRequest(request);
|
||||||
const update = getJSONfromString(String(request.body)) as IGenericUpdate;
|
const update = getJSONfromString<IGenericUpdate>(String(request.body));
|
||||||
response.json(await updateGeneric(update, accountId));
|
response.json(await updateGeneric(update, accountId));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,26 @@
|
|||||||
|
import { Alliance, Guild } from "@/src/models/guildModel";
|
||||||
|
import { getAllianceClient } from "@/src/services/guildService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
const getAllianceController: RequestHandler = (_req, res) => {
|
export const getAllianceController: RequestHandler = async (req, res) => {
|
||||||
res.sendStatus(200);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
|
if (inventory.GuildId) {
|
||||||
|
const guild = (await Guild.findById(inventory.GuildId, "Name Tier AllianceId"))!;
|
||||||
|
if (guild.AllianceId) {
|
||||||
|
const alliance = (await Alliance.findById(guild.AllianceId))!;
|
||||||
|
res.json(await getAllianceClient(alliance, guild));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getAllianceController };
|
// POST request since U27
|
||||||
|
/*interface IGetAllianceRequest {
|
||||||
|
memberCount: number;
|
||||||
|
clanLeaderName: string;
|
||||||
|
clanLeaderId: string;
|
||||||
|
}*/
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
import { RequestHandler } from "express";
|
|
||||||
import { config } from "@/src/services/configService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
export const getCreditsController: RequestHandler = async (req, res) => {
|
|
||||||
let accountId;
|
|
||||||
try {
|
|
||||||
accountId = await getAccountIdForRequest(req);
|
|
||||||
} catch (e) {
|
|
||||||
res.status(400).send("Log-in expired");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.infiniteResources) {
|
|
||||||
res.json({
|
|
||||||
RegularCredits: 999999999,
|
|
||||||
TradesRemaining: 999999999,
|
|
||||||
PremiumCreditsFree: 999999999,
|
|
||||||
PremiumCredits: 999999999
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
res.json({
|
|
||||||
RegularCredits: inventory.RegularCredits,
|
|
||||||
TradesRemaining: inventory.TradesRemaining,
|
|
||||||
PremiumCreditsFree: inventory.PremiumCreditsFree,
|
|
||||||
PremiumCredits: inventory.PremiumCredits
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,6 +1,7 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
|
|
||||||
const getFriendsController = (_request: Request, response: Response) => {
|
// POST with {} instead of GET as of 38.5.0
|
||||||
|
const getFriendsController = (_request: Request, response: Response): void => {
|
||||||
response.writeHead(200, {
|
response.writeHead(200, {
|
||||||
//Connection: "keep-alive",
|
//Connection: "keep-alive",
|
||||||
//"Content-Encoding": "gzip",
|
//"Content-Encoding": "gzip",
|
||||||
|
19
src/controllers/api/getGuildContributionsController.ts
Normal file
19
src/controllers/api/getGuildContributionsController.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IGuildMemberClient } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const getGuildContributionsController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const guildId = (await getInventory(accountId, "GuildId")).GuildId;
|
||||||
|
const guildMember = (await GuildMember.findOne({ guildId, accountId: req.query.buddyId }))!;
|
||||||
|
res.json({
|
||||||
|
_id: { $oid: req.query.buddyId as string },
|
||||||
|
RegularCreditsContributed: guildMember.RegularCreditsContributed,
|
||||||
|
PremiumCreditsContributed: guildMember.PremiumCreditsContributed,
|
||||||
|
MiscItemsContributed: guildMember.MiscItemsContributed,
|
||||||
|
ConsumablesContributed: [], // ???
|
||||||
|
ShipDecorationsContributed: guildMember.ShipDecorationsContributed
|
||||||
|
} satisfies Partial<IGuildMemberClient>);
|
||||||
|
};
|
@ -1,74 +1,32 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
import { Guild } from "@/src/models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
export const getGuildController: RequestHandler = async (req, res) => {
|
||||||
const getGuildController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await Inventory.findOne({ accountOwnerId: accountId });
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
if (!inventory) {
|
|
||||||
res.status(400).json({ error: "inventory was undefined" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (inventory.GuildId) {
|
if (inventory.GuildId) {
|
||||||
const guild = await Guild.findOne({ _id: inventory.GuildId });
|
const guild = await Guild.findById(inventory.GuildId);
|
||||||
if (guild) {
|
if (guild) {
|
||||||
res.json({
|
// Handle guilds created before we added discriminators
|
||||||
_id: toOid(guild._id),
|
if (guild.Name.indexOf("#") == -1) {
|
||||||
Name: guild.Name,
|
guild.Name = await createUniqueClanName(guild.Name);
|
||||||
Members: [
|
await guild.save();
|
||||||
{
|
|
||||||
_id: { $oid: req.query.accountId },
|
|
||||||
Rank: 0,
|
|
||||||
Status: 0
|
|
||||||
}
|
}
|
||||||
],
|
|
||||||
Ranks: [
|
if (guild.CeremonyResetDate && Date.now() >= guild.CeremonyResetDate.getTime()) {
|
||||||
{
|
logger.debug(`ascension ceremony is over`);
|
||||||
Name: "/Lotus/Language/Game/Rank_Creator",
|
guild.CeremonyEndo = undefined;
|
||||||
Permissions: 16351
|
guild.CeremonyContributors = undefined;
|
||||||
},
|
guild.CeremonyResetDate = undefined;
|
||||||
{
|
await guild.save();
|
||||||
Name: "/Lotus/Language/Game/Rank_Warlord",
|
|
||||||
Permissions: 14303
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_General",
|
|
||||||
Permissions: 4318
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_Officer",
|
|
||||||
Permissions: 4314
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_Leader",
|
|
||||||
Permissions: 4106
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_Sage",
|
|
||||||
Permissions: 4304
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_Soldier",
|
|
||||||
Permissions: 4098
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_Initiate",
|
|
||||||
Permissions: 4096
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_Utility",
|
|
||||||
Permissions: 4096
|
|
||||||
}
|
}
|
||||||
],
|
res.json(await getGuildClient(guild, accountId));
|
||||||
Tier: 1
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.json({});
|
res.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getGuildController };
|
|
||||||
|
@ -1,60 +1,33 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Guild } from "@/src/models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { IDojoClient, IDojoComponentClient } from "@/src/types/guildTypes";
|
import { getDojoClient } from "@/src/services/guildService";
|
||||||
import { toOid, toMongoDate } from "@/src/helpers/inventoryHelpers";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
export const getGuildDojoController: RequestHandler = async (req, res) => {
|
export const getGuildDojoController: RequestHandler = async (req, res) => {
|
||||||
const guildId = req.query.guildId as string;
|
const guildId = req.query.guildId as string;
|
||||||
|
|
||||||
const guild = await Guild.findOne({ _id: guildId });
|
const guild = await Guild.findById(guildId);
|
||||||
if (!guild) {
|
if (!guild) {
|
||||||
res.status(404).end();
|
res.status(404).end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate dojo info if not present
|
// Populate dojo info if not present
|
||||||
if (!guild.DojoComponents || guild.DojoComponents.length == 0) {
|
if (guild.DojoComponents.length == 0) {
|
||||||
guild.DojoComponents = [
|
guild.DojoComponents.push({
|
||||||
{
|
|
||||||
_id: new Types.ObjectId(),
|
_id: new Types.ObjectId(),
|
||||||
pf: "/Lotus/Levels/ClanDojo/DojoHall.level",
|
pf: "/Lotus/Levels/ClanDojo/DojoHall.level",
|
||||||
ppf: "",
|
ppf: "",
|
||||||
CompletionTime: new Date(Date.now())
|
CompletionTime: new Date(Date.now()),
|
||||||
}
|
DecoCapacity: 600
|
||||||
];
|
});
|
||||||
await guild.save();
|
await guild.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
const dojo: IDojoClient = {
|
const payload: IGetGuildDojoRequest = req.body ? (JSON.parse(String(req.body)) as IGetGuildDojoRequest) : {};
|
||||||
_id: { $oid: guildId },
|
res.json(await getDojoClient(guild, 0, payload.ComponentId));
|
||||||
Name: guild.Name,
|
|
||||||
Tier: 1,
|
|
||||||
FixedContributions: true,
|
|
||||||
DojoRevision: 1,
|
|
||||||
RevisionTime: Math.round(Date.now() / 1000),
|
|
||||||
Energy: 5,
|
|
||||||
Capacity: 100,
|
|
||||||
DojoRequestStatus: 0,
|
|
||||||
DojoComponents: []
|
|
||||||
};
|
|
||||||
guild.DojoComponents.forEach(dojoComponent => {
|
|
||||||
const clientComponent: IDojoComponentClient = {
|
|
||||||
id: toOid(dojoComponent._id),
|
|
||||||
pf: dojoComponent.pf,
|
|
||||||
ppf: dojoComponent.ppf,
|
|
||||||
DecoCapacity: 600
|
|
||||||
};
|
|
||||||
if (dojoComponent.pi) {
|
|
||||||
clientComponent.pi = toOid(dojoComponent.pi);
|
|
||||||
clientComponent.op = dojoComponent.op!;
|
|
||||||
clientComponent.pp = dojoComponent.pp!;
|
|
||||||
}
|
|
||||||
if (dojoComponent.CompletionTime) {
|
|
||||||
clientComponent.CompletionTime = toMongoDate(dojoComponent.CompletionTime);
|
|
||||||
}
|
|
||||||
dojo.DojoComponents.push(clientComponent);
|
|
||||||
});
|
|
||||||
res.json(dojo);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface IGetGuildDojoRequest {
|
||||||
|
ComponentId?: string;
|
||||||
|
}
|
||||||
|
@ -1,11 +1,60 @@
|
|||||||
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { Guild } from "@/src/models/guildModel";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IMongoDate } from "@/src/types/commonTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const getGuildLogController: RequestHandler = (_req, res) => {
|
export const getGuildLogController: RequestHandler = async (req, res) => {
|
||||||
res.json({
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
|
if (inventory.GuildId) {
|
||||||
|
const guild = await Guild.findById(inventory.GuildId);
|
||||||
|
if (guild) {
|
||||||
|
const log: Record<string, IGuildLogEntryClient[]> = {
|
||||||
RoomChanges: [],
|
RoomChanges: [],
|
||||||
TechChanges: [],
|
TechChanges: [],
|
||||||
RosterActivity: [],
|
RosterActivity: [],
|
||||||
StandingsUpdates: [],
|
StandingsUpdates: [],
|
||||||
ClassChanges: []
|
ClassChanges: []
|
||||||
|
};
|
||||||
|
guild.RoomChanges?.forEach(entry => {
|
||||||
|
log.RoomChanges.push({
|
||||||
|
dateTime: toMongoDate(entry.dateTime ?? new Date()),
|
||||||
|
entryType: entry.entryType,
|
||||||
|
details: entry.details
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
guild.TechChanges?.forEach(entry => {
|
||||||
|
log.TechChanges.push({
|
||||||
|
dateTime: toMongoDate(entry.dateTime ?? new Date()),
|
||||||
|
entryType: entry.entryType,
|
||||||
|
details: entry.details
|
||||||
|
});
|
||||||
|
});
|
||||||
|
guild.RosterActivity?.forEach(entry => {
|
||||||
|
log.RosterActivity.push({
|
||||||
|
dateTime: toMongoDate(entry.dateTime),
|
||||||
|
entryType: entry.entryType,
|
||||||
|
details: entry.details
|
||||||
|
});
|
||||||
|
});
|
||||||
|
guild.ClassChanges?.forEach(entry => {
|
||||||
|
log.ClassChanges.push({
|
||||||
|
dateTime: toMongoDate(entry.dateTime),
|
||||||
|
entryType: entry.entryType,
|
||||||
|
details: entry.details
|
||||||
|
});
|
||||||
|
});
|
||||||
|
res.json(log);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.sendStatus(200);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface IGuildLogEntryClient {
|
||||||
|
dateTime: IMongoDate;
|
||||||
|
entryType: number;
|
||||||
|
details: number | string;
|
||||||
|
}
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { Account, Ignore } from "@/src/models/loginModel";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IFriendInfo } from "@/src/types/guildTypes";
|
||||||
|
import { parallelForeach } from "@/src/utils/async-utils";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
const getIgnoredUsersController: RequestHandler = (_req, res) => {
|
export const getIgnoredUsersController: RequestHandler = async (req, res) => {
|
||||||
res.writeHead(200, {
|
const accountId = await getAccountIdForRequest(req);
|
||||||
"Content-Type": "text/html",
|
const ignores = await Ignore.find({ ignorer: accountId });
|
||||||
"Content-Length": "3"
|
const ignoredUsers: IFriendInfo[] = [];
|
||||||
|
await parallelForeach(ignores, async ignore => {
|
||||||
|
const ignoreeAccount = (await Account.findById(ignore.ignoree, "DisplayName"))!;
|
||||||
|
ignoredUsers.push({
|
||||||
|
_id: toOid(ignore.ignoree),
|
||||||
|
DisplayName: ignoreeAccount.DisplayName + ""
|
||||||
});
|
});
|
||||||
res.end(
|
});
|
||||||
Buffer.from([
|
res.json({ IgnoredUsers: ignoredUsers });
|
||||||
0x7b, 0x22, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x38, 0x33, 0x30, 0x34, 0x30, 0x37, 0x37, 0x32, 0x32,
|
|
||||||
0x34, 0x30, 0x32, 0x32, 0x32, 0x36, 0x31, 0x35, 0x30, 0x31, 0x7d
|
|
||||||
])
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getIgnoredUsersController };
|
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
|
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
import { generateRewardSeed } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
const getNewRewardSeedController: RequestHandler = (_req, res) => {
|
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
||||||
res.json({ rewardSeed: generateRewardSeed() });
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
|
||||||
|
const rewardSeed = generateRewardSeed();
|
||||||
|
await Inventory.updateOne(
|
||||||
|
{
|
||||||
|
accountOwnerId: accountId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RewardSeed: rewardSeed
|
||||||
|
}
|
||||||
|
);
|
||||||
|
res.json({ rewardSeed: rewardSeed });
|
||||||
};
|
};
|
||||||
|
|
||||||
function generateRewardSeed(): number {
|
|
||||||
const min = -Number.MAX_SAFE_INTEGER;
|
|
||||||
const max = Number.MAX_SAFE_INTEGER;
|
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
export { getNewRewardSeedController };
|
|
||||||
|
@ -2,33 +2,44 @@ import { RequestHandler } from "express";
|
|||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||||
import { getShip } from "@/src/services/shipService";
|
import { getShip } from "@/src/services/shipService";
|
||||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
|
||||||
import { logger } from "@/src/utils/logger";
|
|
||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { IGetShipResponse } from "@/src/types/shipTypes";
|
import { IGetShipResponse } from "@/src/types/shipTypes";
|
||||||
|
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||||
|
import { getLoadout } from "@/src/services/loadoutService";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
export const getShipController: RequestHandler = async (req, res) => {
|
export const getShipController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const personalRooms = await getPersonalRooms(accountId);
|
const personalRoomsDb = await getPersonalRooms(accountId);
|
||||||
|
|
||||||
|
// Setup gardening if it's missing. Maybe should be done as part of some quest completion in the future.
|
||||||
|
if (personalRoomsDb.Apartment.Gardening.Planters.length == 0) {
|
||||||
|
personalRoomsDb.Apartment.Gardening = createGarden();
|
||||||
|
await personalRoomsDb.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
||||||
const loadout = await getLoadout(accountId);
|
const loadout = await getLoadout(accountId);
|
||||||
const ship = await getShip(personalRooms.activeShipId, "ShipInteriorColors ShipAttachments SkinFlavourItem");
|
const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem");
|
||||||
|
|
||||||
const getShipResponse: IGetShipResponse = {
|
const getShipResponse: IGetShipResponse = {
|
||||||
ShipOwnerId: accountId,
|
ShipOwnerId: accountId,
|
||||||
LoadOutInventory: { LoadOutPresets: loadout.toJSON() },
|
LoadOutInventory: { LoadOutPresets: loadout.toJSON() },
|
||||||
Ship: {
|
Ship: {
|
||||||
...personalRooms.toJSON().Ship,
|
...personalRooms.Ship,
|
||||||
ShipId: toOid(personalRooms.activeShipId),
|
ShipId: toOid(personalRoomsDb.activeShipId),
|
||||||
ShipInterior: {
|
ShipInterior: {
|
||||||
Colors: ship.ShipInteriorColors,
|
Colors: personalRooms.ShipInteriorColors,
|
||||||
ShipAttachments: ship.ShipAttachments,
|
ShipAttachments: ship.ShipAttachments,
|
||||||
SkinFlavourItem: ship.SkinFlavourItem
|
SkinFlavourItem: ship.SkinFlavourItem
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Apartment: personalRooms.Apartment
|
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
|
||||||
|
? toOid(personalRooms.Ship.FavouriteLoadoutId)
|
||||||
|
: undefined
|
||||||
|
},
|
||||||
|
Apartment: personalRooms.Apartment,
|
||||||
|
TailorShop: personalRooms.TailorShop
|
||||||
};
|
};
|
||||||
|
|
||||||
if (config.unlockAllShipFeatures) {
|
if (config.unlockAllShipFeatures) {
|
||||||
@ -37,14 +48,3 @@ export const getShipController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
res.json(getShipResponse);
|
res.json(getShipResponse);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getLoadout = async (accountId: string) => {
|
|
||||||
const loadout = await Loadout.findOne({ loadoutOwnerId: accountId });
|
|
||||||
|
|
||||||
if (!loadout) {
|
|
||||||
logger.error(`loadout not found for account ${accountId}`);
|
|
||||||
throw new Error("loadout not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return loadout;
|
|
||||||
};
|
|
||||||
|
@ -1,23 +1,14 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
|
import { getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
|
||||||
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
|
|
||||||
import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json";
|
|
||||||
|
|
||||||
export const getVendorInfoController: RequestHandler = (req, res) => {
|
export const getVendorInfoController: RequestHandler = (req, res) => {
|
||||||
switch (req.query.vendor as string) {
|
if (typeof req.query.vendor == "string") {
|
||||||
case "/Lotus/Types/Game/VendorManifests/Zariman/ArchimedeanVendorManifest":
|
const manifest = getVendorManifestByTypeName(req.query.vendor);
|
||||||
res.json(ArchimedeanVendorManifest);
|
if (!manifest) {
|
||||||
break;
|
|
||||||
|
|
||||||
case "/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest":
|
|
||||||
res.json(MaskSalesmanManifest);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "/Lotus/Types/Game/VendorManifests/Zariman/ZarimanCommisionsManifestArchimedean":
|
|
||||||
res.json(ZarimanCommisionsManifestArchimedean);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown vendor: ${req.query.vendor}`);
|
throw new Error(`Unknown vendor: ${req.query.vendor}`);
|
||||||
}
|
}
|
||||||
|
res.json(manifest);
|
||||||
|
} else {
|
||||||
|
res.status(400).end();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
38
src/controllers/api/getVoidProjectionRewardsController.ts
Normal file
38
src/controllers/api/getVoidProjectionRewardsController.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { crackRelic } from "@/src/helpers/relicHelper";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IVoidTearParticipantInfo } from "@/src/types/requestTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const getVoidProjectionRewardsController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const data = getJSONfromString<IVoidProjectionRewardRequest>(String(req.body));
|
||||||
|
|
||||||
|
if (data.ParticipantInfo.QualifiesForReward && !data.ParticipantInfo.HaveRewardResponse) {
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
await crackRelic(inventory, data.ParticipantInfo);
|
||||||
|
await inventory.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
const response: IVoidProjectionRewardResponse = {
|
||||||
|
CurrentWave: data.CurrentWave,
|
||||||
|
ParticipantInfo: data.ParticipantInfo,
|
||||||
|
DifficultyTier: data.DifficultyTier
|
||||||
|
};
|
||||||
|
res.json(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IVoidProjectionRewardRequest {
|
||||||
|
CurrentWave: number;
|
||||||
|
ParticipantInfo: IVoidTearParticipantInfo;
|
||||||
|
VoidTier: string;
|
||||||
|
DifficultyTier: number;
|
||||||
|
VoidProjectionRemovalHash: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IVoidProjectionRewardResponse {
|
||||||
|
CurrentWave: number;
|
||||||
|
ParticipantInfo: IVoidTearParticipantInfo;
|
||||||
|
DifficultyTier: number;
|
||||||
|
}
|
92
src/controllers/api/giftingController.ts
Normal file
92
src/controllers/api/giftingController.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Account } from "@/src/models/loginModel";
|
||||||
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
|
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
|
import { IPurchaseParams } from "@/src/types/purchaseTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportFlavour } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const giftingController: RequestHandler = async (req, res) => {
|
||||||
|
const data = getJSONfromString<IGiftingRequest>(String(req.body));
|
||||||
|
if (data.PurchaseParams.Source != 0 || !data.PurchaseParams.UsePremium) {
|
||||||
|
throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = await Account.findOne(
|
||||||
|
data.RecipientId ? { _id: data.RecipientId.$oid } : { DisplayName: data.Recipient }
|
||||||
|
);
|
||||||
|
if (!account) {
|
||||||
|
res.status(400).send("9").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const inventory = await getInventory(account._id.toString(), "Suits Settings");
|
||||||
|
|
||||||
|
// Cannot gift items to players that have not completed the tutorial.
|
||||||
|
if (inventory.Suits.length == 0) {
|
||||||
|
res.status(400).send("14").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot gift to players who have gifting disabled.
|
||||||
|
// TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
|
||||||
|
if (inventory.Settings?.GiftMode == "GIFT_MODE_NONE") {
|
||||||
|
res.status(400).send("17").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Cannot gift items with mastery requirement to players who are too low level. (Code 2)
|
||||||
|
// TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7)
|
||||||
|
// TODO: Cannot gift necramechs to players that have not completed heart of deimos. (Code 20)
|
||||||
|
|
||||||
|
const senderAccount = await getAccountForRequest(req);
|
||||||
|
const senderInventory = await getInventory(
|
||||||
|
senderAccount._id.toString(),
|
||||||
|
"PremiumCredits PremiumCreditsFree ActiveAvatarImageType GiftsRemaining"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (senderInventory.GiftsRemaining == 0) {
|
||||||
|
res.status(400).send("10").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
senderInventory.GiftsRemaining -= 1;
|
||||||
|
|
||||||
|
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true);
|
||||||
|
await senderInventory.save();
|
||||||
|
|
||||||
|
const senderName = getSuffixedName(senderAccount);
|
||||||
|
await createMessage(account._id, [
|
||||||
|
{
|
||||||
|
sndr: senderName,
|
||||||
|
msg: data.Message || "/Lotus/Language/Menu/GiftReceivedBody_NoCustomMessage",
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "GIFTER_NAME",
|
||||||
|
Tag: senderName
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "GIFT_QUANTITY",
|
||||||
|
Tag: data.PurchaseParams.Quantity
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sub: "/Lotus/Language/Menu/GiftReceivedSubject",
|
||||||
|
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||||
|
gifts: [
|
||||||
|
{
|
||||||
|
GiftType: data.PurchaseParams.StoreItem
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IGiftingRequest {
|
||||||
|
PurchaseParams: IPurchaseParams;
|
||||||
|
Message?: string;
|
||||||
|
Recipient?: string;
|
||||||
|
RecipientId?: IOid;
|
||||||
|
buildLabel: string;
|
||||||
|
}
|
@ -1,52 +1,40 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||||
import { WeaponTypeInternal } from "@/src/services/itemDataService";
|
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { ArtifactPolarity, EquipmentFeatures, IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
|
import { ExportRecipes } from "warframe-public-export-plus";
|
||||||
const modularWeaponCategory: (WeaponTypeInternal | "Hoverboards")[] = [
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
"LongGuns",
|
|
||||||
"Pistols",
|
|
||||||
"Melee",
|
|
||||||
"OperatorAmps",
|
|
||||||
"Hoverboards" // Not sure about hoverboards just coppied from modual crafting
|
|
||||||
];
|
|
||||||
|
|
||||||
interface IGildWeaponRequest {
|
interface IGildWeaponRequest {
|
||||||
ItemName: string;
|
ItemName: string;
|
||||||
Recipe: string; // /Lotus/Weapons/SolarisUnited/LotusGildKitgunBlueprint
|
Recipe: string; // e.g. /Lotus/Weapons/SolarisUnited/LotusGildKitgunBlueprint
|
||||||
PolarizeSlot?: number;
|
PolarizeSlot?: number;
|
||||||
PolarizeValue?: ArtifactPolarity;
|
PolarizeValue?: ArtifactPolarity;
|
||||||
ItemId: string;
|
ItemId: string;
|
||||||
Category: WeaponTypeInternal | "Hoverboards";
|
Category: TEquipmentKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In export there no recipes for gild action, so reputation and ressources only consumed visually
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
export const gildWeaponController: RequestHandler = async (req, res) => {
|
export const gildWeaponController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const data: IGildWeaponRequest = getJSONfromString(String(req.body));
|
const data = getJSONfromString<IGildWeaponRequest>(String(req.body));
|
||||||
data.ItemId = String(req.query.ItemId);
|
data.ItemId = String(req.query.ItemId);
|
||||||
if (!modularWeaponCategory.includes(req.query.Category as WeaponTypeInternal | "Hoverboards")) {
|
data.Category = req.query.Category as TEquipmentKey;
|
||||||
throw new Error(`Unknown modular weapon Category: ${req.query.Category}`);
|
|
||||||
}
|
|
||||||
data.Category = req.query.Category as WeaponTypeInternal | "Hoverboards";
|
|
||||||
|
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
if (!inventory[data.Category]) {
|
|
||||||
throw new Error(`Category ${req.query.Category} not found in inventory`);
|
|
||||||
}
|
|
||||||
const weaponIndex = inventory[data.Category].findIndex(x => String(x._id) === data.ItemId);
|
const weaponIndex = inventory[data.Category].findIndex(x => String(x._id) === data.ItemId);
|
||||||
if (weaponIndex === -1) {
|
if (weaponIndex === -1) {
|
||||||
throw new Error(`Weapon with ${data.ItemId} not found in category ${req.query.Category}`);
|
throw new Error(`Weapon with ${data.ItemId} not found in category ${String(req.query.Category)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const weapon = inventory[data.Category][weaponIndex];
|
const weapon = inventory[data.Category][weaponIndex];
|
||||||
weapon.Features = EquipmentFeatures.GILDED; // maybe 9 idk if DOUBLE_CAPACITY is also given
|
weapon.Features ??= 0;
|
||||||
|
weapon.Features |= EquipmentFeatures.GILDED;
|
||||||
|
if (data.Recipe != "webui") {
|
||||||
weapon.ItemName = data.ItemName;
|
weapon.ItemName = data.ItemName;
|
||||||
weapon.XP = 0;
|
weapon.XP = 0;
|
||||||
|
}
|
||||||
if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) {
|
if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) {
|
||||||
weapon.Polarity = [
|
weapon.Polarity = [
|
||||||
{
|
{
|
||||||
@ -56,11 +44,32 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
inventory[data.Category][weaponIndex] = weapon;
|
inventory[data.Category][weaponIndex] = weapon;
|
||||||
await inventory.save();
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
inventoryChanges[data.Category] = [weapon.toJSON<IEquipmentClient>()];
|
||||||
|
|
||||||
res.json({
|
const affiliationMods = [];
|
||||||
InventoryChanges: {
|
|
||||||
[data.Category]: [weapon]
|
if (data.Recipe != "webui") {
|
||||||
|
const recipe = ExportRecipes[data.Recipe];
|
||||||
|
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
|
||||||
|
ItemType: ingredient.ItemType,
|
||||||
|
ItemCount: ingredient.ItemCount * -1
|
||||||
|
}));
|
||||||
|
addMiscItems(inventory, inventoryChanges.MiscItems);
|
||||||
|
|
||||||
|
if (recipe.syndicateStandingChange) {
|
||||||
|
const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
|
||||||
|
affiliation.Standing += recipe.syndicateStandingChange.value;
|
||||||
|
affiliationMods.push({
|
||||||
|
Tag: recipe.syndicateStandingChange.tag,
|
||||||
|
Standing: recipe.syndicateStandingChange.value
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: inventoryChanges,
|
||||||
|
AffiliationMods: affiliationMods
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
17
src/controllers/api/giveKeyChainTriggeredItemsController.ts
Normal file
17
src/controllers/api/giveKeyChainTriggeredItemsController.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { parseString } from "@/src/helpers/general";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { giveKeyChainItem } from "@/src/services/questService";
|
||||||
|
import { IKeyChainRequest } from "@/src/types/requestTypes";
|
||||||
|
|
||||||
|
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = parseString(req.query.accountId);
|
||||||
|
const keyChainInfo = getJSONfromString<IKeyChainRequest>((req.body as string).toString());
|
||||||
|
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const inventoryChanges = await giveKeyChainItem(inventory, keyChainInfo);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
res.send(inventoryChanges);
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { giveKeyChainMessage } from "@/src/services/questService";
|
||||||
|
import { IKeyChainRequest } from "@/src/types/requestTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const keyChainInfo = JSON.parse((req.body as Buffer).toString()) as IKeyChainRequest;
|
||||||
|
|
||||||
|
const inventory = await getInventory(accountId, "QuestKeys");
|
||||||
|
await giveKeyChainMessage(inventory, accountId, keyChainInfo);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
res.send(1);
|
||||||
|
};
|
45
src/controllers/api/giveQuestKeyRewardController.ts
Normal file
45
src/controllers/api/giveQuestKeyRewardController.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { addItem, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const giveQuestKeyRewardController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const rewardRequest = getJSONfromString<IQuestKeyRewardRequest>((req.body as Buffer).toString());
|
||||||
|
|
||||||
|
if (Array.isArray(rewardRequest.reward)) {
|
||||||
|
throw new Error("Multiple rewards not expected");
|
||||||
|
}
|
||||||
|
|
||||||
|
const reward = rewardRequest.reward;
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const inventoryChanges = await addItem(inventory, reward.ItemType, reward.Amount);
|
||||||
|
await inventory.save();
|
||||||
|
res.json(inventoryChanges);
|
||||||
|
//TODO: consider whishlist changes
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IQuestKeyRewardRequest {
|
||||||
|
reward: IQuestKeyReward;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IQuestKeyReward {
|
||||||
|
RewardType: string;
|
||||||
|
CouponType: string;
|
||||||
|
Icon: string;
|
||||||
|
ItemType: string;
|
||||||
|
StoreItemType: string;
|
||||||
|
ProductCategory: string;
|
||||||
|
Amount: number;
|
||||||
|
ScalingMultiplier: number;
|
||||||
|
Durability: string;
|
||||||
|
DisplayName: string;
|
||||||
|
Duration: number;
|
||||||
|
CouponSku: number;
|
||||||
|
Syndicate: string;
|
||||||
|
//Milestones: any[];
|
||||||
|
ChooseSetIndex: number;
|
||||||
|
NewSystemReward: boolean;
|
||||||
|
_id: IOid;
|
||||||
|
}
|
20
src/controllers/api/giveShipDecoAndLoreFragmentController.ts
Normal file
20
src/controllers/api/giveShipDecoAndLoreFragmentController.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { ILoreFragmentScan, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "LoreFragmentScans ShipDecorations");
|
||||||
|
const data = getJSONfromString<IGiveShipDecoAndLoreFragmentRequest>(String(req.body));
|
||||||
|
addLoreFragmentScans(inventory, data.LoreFragmentScans);
|
||||||
|
addShipDecorations(inventory, data.ShipDecorations);
|
||||||
|
await inventory.save();
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IGiveShipDecoAndLoreFragmentRequest {
|
||||||
|
LoreFragmentScans: ILoreFragmentScan[];
|
||||||
|
ShipDecorations: ITypeCount[];
|
||||||
|
}
|
16
src/controllers/api/giveStartingGearController.ts
Normal file
16
src/controllers/api/giveStartingGearController.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { addStartingGear, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { TPartialStartingGear } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const giveStartingGearController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const startingGear = getJSONfromString<TPartialStartingGear>(String(req.body));
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
|
||||||
|
const inventoryChanges = await addStartingGear(inventory, startingGear);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
res.send(inventoryChanges);
|
||||||
|
};
|
@ -1,5 +1,457 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
import {
|
||||||
|
addGuildMemberMiscItemContribution,
|
||||||
|
getGuildForRequestEx,
|
||||||
|
getGuildVault,
|
||||||
|
hasAccessToDojo,
|
||||||
|
hasGuildPermission,
|
||||||
|
processFundedGuildTechProject,
|
||||||
|
processGuildTechProjectContributionsUpdate,
|
||||||
|
removePigmentsFromGuildMembers,
|
||||||
|
scaleRequiredCount,
|
||||||
|
setGuildTechLogState
|
||||||
|
} from "@/src/services/guildService";
|
||||||
|
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import {
|
||||||
|
addCrewShipWeaponSkin,
|
||||||
|
addEquipment,
|
||||||
|
addItem,
|
||||||
|
addMiscItems,
|
||||||
|
addRecipes,
|
||||||
|
combineInventoryChanges,
|
||||||
|
getInventory,
|
||||||
|
occupySlot,
|
||||||
|
updateCurrency
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
|
import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes";
|
||||||
|
import { GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
|
||||||
export const guildTechController: RequestHandler = (_req, res) => {
|
export const guildTechController: RequestHandler = async (req, res) => {
|
||||||
res.status(500).end(); // This is what I got for a fresh clan.
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const data = JSON.parse(String(req.body)) as TGuildTechRequest;
|
||||||
|
if (data.Action == "Sync") {
|
||||||
|
let needSave = false;
|
||||||
|
const techProjects: ITechProjectClient[] = [];
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
if (guild.TechProjects) {
|
||||||
|
for (const project of guild.TechProjects) {
|
||||||
|
const techProject: ITechProjectClient = {
|
||||||
|
ItemType: project.ItemType,
|
||||||
|
ReqCredits: project.ReqCredits,
|
||||||
|
ReqItems: project.ReqItems,
|
||||||
|
State: project.State
|
||||||
|
};
|
||||||
|
if (project.CompletionDate) {
|
||||||
|
techProject.CompletionDate = toMongoDate(project.CompletionDate);
|
||||||
|
if (Date.now() >= project.CompletionDate.getTime()) {
|
||||||
|
needSave ||= setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
techProjects.push(techProject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (needSave) {
|
||||||
|
await guild.save();
|
||||||
|
}
|
||||||
|
res.json({ TechProjects: techProjects });
|
||||||
|
} else if (data.Action == "Start") {
|
||||||
|
if (data.Mode == "Guild") {
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||||
|
res.status(400).send("-1").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const recipe = ExportDojoRecipes.research[data.RecipeType];
|
||||||
|
guild.TechProjects ??= [];
|
||||||
|
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
|
||||||
|
const techProject =
|
||||||
|
guild.TechProjects[
|
||||||
|
guild.TechProjects.push({
|
||||||
|
ItemType: data.RecipeType,
|
||||||
|
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
|
||||||
|
ReqItems: recipe.ingredients.map(x => ({
|
||||||
|
ItemType: x.ItemType,
|
||||||
|
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
|
||||||
|
})),
|
||||||
|
State: 0
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
setGuildTechLogState(guild, techProject.ItemType, 5);
|
||||||
|
if (config.noDojoResearchCosts) {
|
||||||
|
processFundedGuildTechProject(guild, techProject, recipe);
|
||||||
|
} else {
|
||||||
|
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
|
||||||
|
guild.ActiveDojoColorResearch = data.RecipeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await guild.save();
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
|
const recipe = ExportDojoRecipes.research[data.RecipeType];
|
||||||
|
if (data.TechProductCategory) {
|
||||||
|
if (
|
||||||
|
data.TechProductCategory != "CrewShipWeapons" &&
|
||||||
|
data.TechProductCategory != "CrewShipWeaponSkins"
|
||||||
|
) {
|
||||||
|
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
|
||||||
|
}
|
||||||
|
if (!inventory[getSalvageCategory(data.TechProductCategory)].id(data.CategoryItemId)) {
|
||||||
|
throw new Error(
|
||||||
|
`no item with id ${data.CategoryItemId} in ${getSalvageCategory(data.TechProductCategory)} array`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const techProject =
|
||||||
|
inventory.PersonalTechProjects[
|
||||||
|
inventory.PersonalTechProjects.push({
|
||||||
|
State: 0,
|
||||||
|
ReqCredits: recipe.price,
|
||||||
|
ItemType: data.RecipeType,
|
||||||
|
ProductCategory: data.TechProductCategory,
|
||||||
|
CategoryItemId: data.CategoryItemId,
|
||||||
|
ReqItems: recipe.ingredients
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
isPersonal: true,
|
||||||
|
action: "Start",
|
||||||
|
personalTech: techProject.toJSON()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (data.Action == "Contribute") {
|
||||||
|
if ((req.query.guildId as string) == "000000000000000000000000") {
|
||||||
|
const techProject = inventory.PersonalTechProjects.id(data.ResearchId)!;
|
||||||
|
|
||||||
|
techProject.ReqCredits -= data.RegularCredits;
|
||||||
|
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
|
||||||
|
|
||||||
|
const miscItemChanges = [];
|
||||||
|
for (const miscItem of data.MiscItems) {
|
||||||
|
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||||
|
if (reqItem) {
|
||||||
|
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||||
|
miscItem.ItemCount = reqItem.ItemCount;
|
||||||
|
}
|
||||||
|
reqItem.ItemCount -= miscItem.ItemCount;
|
||||||
|
miscItemChanges.push({
|
||||||
|
ItemType: miscItem.ItemType,
|
||||||
|
ItemCount: miscItem.ItemCount * -1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
inventoryChanges.MiscItems = miscItemChanges;
|
||||||
|
|
||||||
|
techProject.HasContributions = true;
|
||||||
|
|
||||||
|
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
|
||||||
|
techProject.State = 1;
|
||||||
|
const recipe = ExportDojoRecipes.research[techProject.ItemType];
|
||||||
|
techProject.CompletionDate = new Date(Date.now() + recipe.time * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: inventoryChanges,
|
||||||
|
PersonalResearch: { $oid: data.ResearchId },
|
||||||
|
PersonalResearchDate: techProject.CompletionDate ? toMongoDate(techProject.CompletionDate) : undefined
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!hasAccessToDojo(inventory)) {
|
||||||
|
res.status(400).send("-1").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
const guildMember = (await GuildMember.findOne(
|
||||||
|
{ accountId, guildId: guild._id },
|
||||||
|
"RegularCreditsContributed MiscItemsContributed"
|
||||||
|
))!;
|
||||||
|
|
||||||
|
const techProject = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
|
||||||
|
|
||||||
|
if (data.VaultCredits) {
|
||||||
|
if (data.VaultCredits > techProject.ReqCredits) {
|
||||||
|
data.VaultCredits = techProject.ReqCredits;
|
||||||
|
}
|
||||||
|
techProject.ReqCredits -= data.VaultCredits;
|
||||||
|
guild.VaultRegularCredits! -= data.VaultCredits;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.RegularCredits > techProject.ReqCredits) {
|
||||||
|
data.RegularCredits = techProject.ReqCredits;
|
||||||
|
}
|
||||||
|
techProject.ReqCredits -= data.RegularCredits;
|
||||||
|
|
||||||
|
guildMember.RegularCreditsContributed ??= 0;
|
||||||
|
guildMember.RegularCreditsContributed += data.RegularCredits;
|
||||||
|
|
||||||
|
if (data.VaultMiscItems.length) {
|
||||||
|
for (const miscItem of data.VaultMiscItems) {
|
||||||
|
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||||
|
if (reqItem) {
|
||||||
|
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||||
|
miscItem.ItemCount = reqItem.ItemCount;
|
||||||
|
}
|
||||||
|
reqItem.ItemCount -= miscItem.ItemCount;
|
||||||
|
|
||||||
|
const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == miscItem.ItemType)!;
|
||||||
|
vaultMiscItem.ItemCount -= miscItem.ItemCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const miscItemChanges = [];
|
||||||
|
for (const miscItem of data.MiscItems) {
|
||||||
|
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||||
|
if (reqItem) {
|
||||||
|
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||||
|
miscItem.ItemCount = reqItem.ItemCount;
|
||||||
|
}
|
||||||
|
reqItem.ItemCount -= miscItem.ItemCount;
|
||||||
|
miscItemChanges.push({
|
||||||
|
ItemType: miscItem.ItemType,
|
||||||
|
ItemCount: miscItem.ItemCount * -1
|
||||||
|
});
|
||||||
|
|
||||||
|
addGuildMemberMiscItemContribution(guildMember, miscItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
|
||||||
|
inventoryChanges.MiscItems = miscItemChanges;
|
||||||
|
|
||||||
|
// Check if research is fully funded now.
|
||||||
|
await processGuildTechProjectContributionsUpdate(guild, techProject);
|
||||||
|
|
||||||
|
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: inventoryChanges,
|
||||||
|
Vault: getGuildVault(guild)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (data.Action.split(",")[0] == "Buy") {
|
||||||
|
const purchase = data as IGuildTechBuyRequest;
|
||||||
|
if (purchase.Mode == "Guild") {
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
if (
|
||||||
|
!hasAccessToDojo(inventory) ||
|
||||||
|
!(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))
|
||||||
|
) {
|
||||||
|
res.status(400).send("-1").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const quantity = parseInt(data.Action.split(",")[1]);
|
||||||
|
const recipeChanges = [
|
||||||
|
{
|
||||||
|
ItemType: purchase.RecipeType,
|
||||||
|
ItemCount: quantity
|
||||||
|
}
|
||||||
|
];
|
||||||
|
addRecipes(inventory, recipeChanges);
|
||||||
|
const currencyChanges = updateCurrency(
|
||||||
|
inventory,
|
||||||
|
ExportDojoRecipes.research[purchase.RecipeType].replicatePrice,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
await inventory.save();
|
||||||
|
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
||||||
|
res.json({
|
||||||
|
inventoryChanges: {
|
||||||
|
...currencyChanges,
|
||||||
|
Recipes: recipeChanges
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const inventoryChanges = claimSalvagedComponent(inventory, purchase.CategoryItemId!);
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
inventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (data.Action == "Fabricate") {
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
||||||
|
res.status(400).send("-1").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const recipe = ExportDojoRecipes.fabrications[data.RecipeType];
|
||||||
|
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, recipe.price, false);
|
||||||
|
inventoryChanges.MiscItems = recipe.ingredients.map(x => ({
|
||||||
|
ItemType: x.ItemType,
|
||||||
|
ItemCount: x.ItemCount * -1
|
||||||
|
}));
|
||||||
|
addMiscItems(inventory, inventoryChanges.MiscItems);
|
||||||
|
combineInventoryChanges(inventoryChanges, await addItem(inventory, recipe.resultType));
|
||||||
|
await inventory.save();
|
||||||
|
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
||||||
|
res.json({ inventoryChanges: inventoryChanges });
|
||||||
|
} else if (data.Action == "Pause") {
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||||
|
res.status(400).send("-1").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const project = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
|
||||||
|
project.State = -2;
|
||||||
|
guild.ActiveDojoColorResearch = "";
|
||||||
|
await guild.save();
|
||||||
|
await removePigmentsFromGuildMembers(guild._id);
|
||||||
|
res.end();
|
||||||
|
} else if (data.Action == "Unpause") {
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||||
|
res.status(400).send("-1").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const project = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
|
||||||
|
project.State = 0;
|
||||||
|
guild.ActiveDojoColorResearch = data.RecipeType;
|
||||||
|
await guild.save();
|
||||||
|
res.end();
|
||||||
|
} else if (data.Action == "Cancel" && data.CategoryItemId) {
|
||||||
|
const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x =>
|
||||||
|
x.CategoryItemId?.equals(data.CategoryItemId)
|
||||||
|
);
|
||||||
|
const personalTechProject = inventory.PersonalTechProjects[personalTechProjectIndex];
|
||||||
|
inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
|
||||||
|
|
||||||
|
const meta = ExportDojoRecipes.research[personalTechProject.ItemType];
|
||||||
|
const contributedCredits = meta.price - personalTechProject.ReqCredits;
|
||||||
|
const inventoryChanges = updateCurrency(inventory, contributedCredits * -1, false);
|
||||||
|
inventoryChanges.MiscItems = [];
|
||||||
|
for (const ingredient of meta.ingredients) {
|
||||||
|
const reqItem = personalTechProject.ReqItems.find(x => x.ItemType == ingredient.ItemType);
|
||||||
|
if (reqItem) {
|
||||||
|
const contributedItems = ingredient.ItemCount - reqItem.ItemCount;
|
||||||
|
inventoryChanges.MiscItems.push({
|
||||||
|
ItemType: ingredient.ItemType,
|
||||||
|
ItemCount: contributedItems
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addMiscItems(inventory, inventoryChanges.MiscItems);
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
action: "Cancel",
|
||||||
|
isPersonal: true,
|
||||||
|
inventoryChanges: inventoryChanges,
|
||||||
|
personalTech: {
|
||||||
|
ItemId: toOid(personalTechProject._id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (data.Action == "Rush" && data.CategoryItemId) {
|
||||||
|
const inventoryChanges: IInventoryChanges = {
|
||||||
|
...updateCurrency(inventory, 20, true),
|
||||||
|
...claimSalvagedComponent(inventory, data.CategoryItemId)
|
||||||
|
};
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
inventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
} else if (data.Action == "InstantFinish") {
|
||||||
|
if (data.TechProductCategory != "CrewShipWeapons" && data.TechProductCategory != "CrewShipWeaponSkins") {
|
||||||
|
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
|
||||||
|
}
|
||||||
|
const inventoryChanges = finishComponentRepair(inventory, data.TechProductCategory, data.CategoryItemId!);
|
||||||
|
inventoryChanges.MiscItems = [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Items/MiscItems/InstantSalvageRepairItem",
|
||||||
|
ItemCount: -1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
addMiscItems(inventory, inventoryChanges.MiscItems);
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
inventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||||
|
throw new Error(`unhandled guildTech request`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type TGuildTechRequest =
|
||||||
|
| { Action: "Sync" | "SomethingElseThatWeMightNotKnowAbout" }
|
||||||
|
| IGuildTechBasicRequest
|
||||||
|
| IGuildTechContributeRequest;
|
||||||
|
|
||||||
|
interface IGuildTechBasicRequest {
|
||||||
|
Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush" | "InstantFinish";
|
||||||
|
Mode: "Guild" | "Personal";
|
||||||
|
RecipeType: string;
|
||||||
|
TechProductCategory?: string;
|
||||||
|
CategoryItemId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGuildTechBuyRequest extends Omit<IGuildTechBasicRequest, "Action"> {
|
||||||
|
Action: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGuildTechContributeRequest {
|
||||||
|
Action: "Contribute";
|
||||||
|
ResearchId: string;
|
||||||
|
RecipeType: string;
|
||||||
|
RegularCredits: number;
|
||||||
|
MiscItems: IMiscItem[];
|
||||||
|
VaultCredits: number;
|
||||||
|
VaultMiscItems: IMiscItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSalvageCategory = (
|
||||||
|
category: "CrewShipWeapons" | "CrewShipWeaponSkins"
|
||||||
|
): "CrewShipSalvagedWeapons" | "CrewShipSalvagedWeaponSkins" => {
|
||||||
|
return category == "CrewShipWeapons" ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins";
|
||||||
|
};
|
||||||
|
|
||||||
|
const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: string): IInventoryChanges => {
|
||||||
|
// delete personal tech project
|
||||||
|
const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x => x.CategoryItemId?.equals(itemId));
|
||||||
|
const personalTechProject = inventory.PersonalTechProjects[personalTechProjectIndex];
|
||||||
|
inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
|
||||||
|
|
||||||
|
const category = personalTechProject.ProductCategory! as "CrewShipWeapons" | "CrewShipWeaponSkins";
|
||||||
|
return finishComponentRepair(inventory, category, itemId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const finishComponentRepair = (
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
category: "CrewShipWeapons" | "CrewShipWeaponSkins",
|
||||||
|
itemId: string
|
||||||
|
): IInventoryChanges => {
|
||||||
|
const salvageCategory = getSalvageCategory(category);
|
||||||
|
|
||||||
|
// find salved part & delete it
|
||||||
|
const salvageIndex = inventory[salvageCategory].findIndex(x => x._id.equals(itemId));
|
||||||
|
const salvageItem = inventory[salvageCategory][salvageIndex];
|
||||||
|
inventory[salvageCategory].splice(salvageIndex, 1);
|
||||||
|
|
||||||
|
// add final item
|
||||||
|
const inventoryChanges = {
|
||||||
|
...(category == "CrewShipWeaponSkins"
|
||||||
|
? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
|
||||||
|
: addEquipment(inventory, category, salvageItem.ItemType, {
|
||||||
|
UpgradeFingerprint: salvageItem.UpgradeFingerprint
|
||||||
|
})),
|
||||||
|
...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
inventoryChanges.RemovedIdItems = [
|
||||||
|
{
|
||||||
|
ItemId: { $oid: itemId }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return inventoryChanges;
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,6 @@ import { createNewSession } from "@/src/managers/sessionManager";
|
|||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { ISession } from "@/src/types/session";
|
import { ISession } from "@/src/types/session";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
const hostSessionController: RequestHandler = async (req, res) => {
|
const hostSessionController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const hostSessionRequest = JSON.parse(req.body as string) as ISession;
|
const hostSessionRequest = JSON.parse(req.body as string) as ISession;
|
||||||
|
45
src/controllers/api/hubBlessingController.ts
Normal file
45
src/controllers/api/hubBlessingController.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { addBooster, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getRandomInt } from "@/src/services/rngService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportBoosters } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const hubBlessingController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const data = getJSONfromString<IHubBlessingRequest>(String(req.body));
|
||||||
|
const boosterType = ExportBoosters[data.booster].typeName;
|
||||||
|
if (req.query.mode == "send") {
|
||||||
|
const inventory = await getInventory(accountId, "BlessingCooldown Boosters");
|
||||||
|
inventory.BlessingCooldown = new Date(Date.now() + 86400000);
|
||||||
|
addBooster(boosterType, 3 * 3600, inventory);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
let token = "";
|
||||||
|
for (let i = 0; i != 32; ++i) {
|
||||||
|
token += getRandomInt(0, 15).toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
BlessingCooldown: inventory.BlessingCooldown,
|
||||||
|
SendTime: Math.trunc(Date.now() / 1000).toString(),
|
||||||
|
Token: token
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const inventory = await getInventory(accountId, "Boosters");
|
||||||
|
addBooster(boosterType, 3 * 3600, inventory);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
BoosterType: data.booster,
|
||||||
|
Sender: data.senderId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IHubBlessingRequest {
|
||||||
|
booster: string;
|
||||||
|
senderId?: string; // mode=request
|
||||||
|
sendTime?: string; // mode=request
|
||||||
|
token?: string; // mode=request
|
||||||
|
}
|
@ -1,8 +1,134 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import inbox from "@/static/fixed_responses/inbox.json";
|
import { Inbox } from "@/src/models/inboxModel";
|
||||||
|
import {
|
||||||
|
createMessage,
|
||||||
|
createNewEventMessages,
|
||||||
|
deleteAllMessagesRead,
|
||||||
|
deleteMessageRead,
|
||||||
|
getAllMessagesSorted,
|
||||||
|
getMessage
|
||||||
|
} from "@/src/services/inboxService";
|
||||||
|
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
|
||||||
|
import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { ExportFlavour } from "warframe-public-export-plus";
|
||||||
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
|
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
|
||||||
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
|
|
||||||
const inboxController: RequestHandler = (_req, res) => {
|
export const inboxController: RequestHandler = async (req, res) => {
|
||||||
res.json(inbox);
|
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
|
||||||
|
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
const accountId = account._id.toString();
|
||||||
|
|
||||||
|
if (deleteId) {
|
||||||
|
if (deleteId === "DeleteAllRead") {
|
||||||
|
await deleteAllMessagesRead(accountId);
|
||||||
|
res.status(200).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await deleteMessageRead(parseOid(deleteId as string));
|
||||||
|
res.status(200).end();
|
||||||
|
} else if (messageId) {
|
||||||
|
const message = await getMessage(parseOid(messageId as string));
|
||||||
|
message.r = true;
|
||||||
|
await message.save();
|
||||||
|
|
||||||
|
const attachmentItems = message.attVisualOnly ? undefined : message.att;
|
||||||
|
const attachmentCountedItems = message.attVisualOnly ? undefined : message.countedAtt;
|
||||||
|
|
||||||
|
if (!attachmentItems && !attachmentCountedItems && !message.gifts) {
|
||||||
|
res.status(200).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const inventoryChanges = {};
|
||||||
|
if (attachmentItems) {
|
||||||
|
await addItems(
|
||||||
|
inventory,
|
||||||
|
attachmentItems.map(attItem => ({
|
||||||
|
ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem,
|
||||||
|
ItemCount: 1
|
||||||
|
})),
|
||||||
|
inventoryChanges
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (attachmentCountedItems) {
|
||||||
|
await addItems(inventory, attachmentCountedItems, inventoryChanges);
|
||||||
|
}
|
||||||
|
if (message.gifts) {
|
||||||
|
const sender = await getAccountFromSuffixedName(message.sndr);
|
||||||
|
const recipientName = getSuffixedName(account);
|
||||||
|
const giftQuantity = message.arg!.find(x => x.Key == "GIFT_QUANTITY")!.Tag as number;
|
||||||
|
for (const gift of message.gifts) {
|
||||||
|
combineInventoryChanges(
|
||||||
|
inventoryChanges,
|
||||||
|
(await handleStoreItemAcquisition(gift.GiftType, inventory, giftQuantity)).InventoryChanges
|
||||||
|
);
|
||||||
|
if (sender) {
|
||||||
|
await createMessage(sender._id, [
|
||||||
|
{
|
||||||
|
sndr: recipientName,
|
||||||
|
msg: "/Lotus/Language/Menu/GiftReceivedConfirmationBody",
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "RECIPIENT_NAME",
|
||||||
|
Tag: recipientName
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "GIFT_TYPE",
|
||||||
|
Tag: gift.GiftType
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "GIFT_QUANTITY",
|
||||||
|
Tag: giftQuantity
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sub: "/Lotus/Language/Menu/GiftReceivedConfirmationSubject",
|
||||||
|
icon: ExportFlavour[inventory.ActiveAvatarImageType].icon,
|
||||||
|
highPriority: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
res.json({ InventoryChanges: inventoryChanges });
|
||||||
|
} else if (latestClientMessageId) {
|
||||||
|
await createNewEventMessages(req);
|
||||||
|
const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 });
|
||||||
|
|
||||||
|
const latestClientMessage = messages.find(m => m._id.toString() === parseOid(latestClientMessageId as string));
|
||||||
|
|
||||||
|
if (!latestClientMessage) {
|
||||||
|
logger.debug(`this should only happen after DeleteAllRead `);
|
||||||
|
res.json({ Inbox: messages });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newMessages = messages.filter(m => m.date > latestClientMessage.date);
|
||||||
|
|
||||||
|
if (newMessages.length === 0) {
|
||||||
|
res.send("no-new");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ Inbox: newMessages });
|
||||||
|
} else {
|
||||||
|
//newly created event messages must be newer than account.LatestEventMessageDate
|
||||||
|
await createNewEventMessages(req);
|
||||||
|
const messages = await getAllMessagesSorted(accountId);
|
||||||
|
const inbox = messages.map(m => m.toJSON());
|
||||||
|
res.json({ Inbox: inbox });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { inboxController };
|
// 33.6.0 has query arguments like lastMessage={"$oid":"68112baebf192e786d1502bb"} instead of lastMessage=68112baebf192e786d1502bb
|
||||||
|
const parseOid = (oid: string): string => {
|
||||||
|
if (oid[0] == "{") {
|
||||||
|
return (JSON.parse(oid) as IOid).$oid;
|
||||||
|
}
|
||||||
|
return oid;
|
||||||
|
};
|
||||||
|
@ -1,18 +1,35 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getInventory, addMiscItems } from "@/src/services/inventoryService";
|
import { getInventory, addMiscItems, updateCurrency, addRecipes, freeUpSlot } from "@/src/services/inventoryService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
|
import {
|
||||||
|
IConsumedSuit,
|
||||||
|
IHelminthFoodRecord,
|
||||||
|
IInventoryClient,
|
||||||
|
IMiscItem,
|
||||||
|
InventorySlot
|
||||||
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { ExportMisc } from "warframe-public-export-plus";
|
||||||
|
import { getRecipe } from "@/src/services/itemDataService";
|
||||||
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { colorToShard } from "@/src/helpers/shardHelper";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
import {
|
||||||
|
addInfestedFoundryXP,
|
||||||
|
applyCheatsToInfestedFoundry,
|
||||||
|
handleSubsumeCompletion
|
||||||
|
} from "@/src/services/infestedFoundryService";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
export const infestedFoundryController: RequestHandler = async (req, res) => {
|
export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
switch (req.query.mode) {
|
switch (req.query.mode) {
|
||||||
case "s": {
|
case "s": {
|
||||||
// shard installation
|
// shard installation
|
||||||
const request = getJSONfromString(String(req.body)) as IShardInstallRequest;
|
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const suit = inventory.Suits.find(suit => suit._id.toString() == request.SuitId.$oid)!;
|
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||||
if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
|
if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
|
||||||
suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
|
suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
|
||||||
}
|
}
|
||||||
@ -36,9 +53,52 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "x": {
|
||||||
|
// shard removal
|
||||||
|
const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||||
|
|
||||||
|
const miscItemChanges: IMiscItem[] = [];
|
||||||
|
if (suit.ArchonCrystalUpgrades![request.Slot].Color) {
|
||||||
|
// refund shard
|
||||||
|
const shard = Object.entries(colorToShard).find(
|
||||||
|
([color]) => color == suit.ArchonCrystalUpgrades![request.Slot].Color
|
||||||
|
)![1];
|
||||||
|
miscItemChanges.push({
|
||||||
|
ItemType: shard,
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove from suit
|
||||||
|
suit.ArchonCrystalUpgrades![request.Slot] = {};
|
||||||
|
|
||||||
|
if (!config.infiniteHelminthMaterials) {
|
||||||
|
// remove bile
|
||||||
|
const bile = inventory.InfestedFoundry!.Resources!.find(
|
||||||
|
x => x.ItemType == "/Lotus/Types/Items/InfestedFoundry/HelminthBile"
|
||||||
|
)!;
|
||||||
|
bile.Count -= 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
|
||||||
|
applyCheatsToInfestedFoundry(infestedFoundry);
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: {
|
||||||
|
MiscItems: miscItemChanges,
|
||||||
|
InfestedFoundry: infestedFoundry
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "n": {
|
case "n": {
|
||||||
// name the beast
|
// name the beast
|
||||||
const request = getJSONfromString(String(req.body)) as IHelminthNameRequest;
|
const request = getJSONfromString<IHelminthNameRequest>(String(req.body));
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
inventory.InfestedFoundry ??= {};
|
inventory.InfestedFoundry ??= {};
|
||||||
inventory.InfestedFoundry.Name = request.newName;
|
inventory.InfestedFoundry.Name = request.newName;
|
||||||
@ -53,13 +113,251 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "o": // offerings update
|
case "c": {
|
||||||
// {"OfferingsIndex":540,"SuitTypes":["/Lotus/Powersuits/PaxDuviricus/PaxDuviricusBaseSuit","/Lotus/Powersuits/Nezha/NezhaBaseSuit","/Lotus/Powersuits/Devourer/DevourerBaseSuit"],"Extra":false}
|
// consume items
|
||||||
res.status(404).end();
|
|
||||||
|
if (config.infiniteHelminthMaterials) {
|
||||||
|
res.status(400).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = getJSONfromString<IHelminthFeedRequest>(String(req.body));
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
inventory.InfestedFoundry ??= {};
|
||||||
|
inventory.InfestedFoundry.Resources ??= [];
|
||||||
|
|
||||||
|
const miscItemChanges: IMiscItem[] = [];
|
||||||
|
let totalPercentagePointsGained = 0;
|
||||||
|
|
||||||
|
const currentUnixSeconds = Math.trunc(Date.now() / 1000);
|
||||||
|
|
||||||
|
for (const contribution of request.ResourceContributions) {
|
||||||
|
const snack = ExportMisc.helminthSnacks[contribution.ItemType];
|
||||||
|
|
||||||
|
// tally items for removal
|
||||||
|
const change = miscItemChanges.find(x => x.ItemType == contribution.ItemType);
|
||||||
|
if (change) {
|
||||||
|
change.ItemCount -= snack.count;
|
||||||
|
} else {
|
||||||
|
miscItemChanges.push({ ItemType: contribution.ItemType, ItemCount: snack.count * -1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snack.type == "/Lotus/Types/Items/InfestedFoundry/HelminthAppetiteCooldownReducer") {
|
||||||
|
// sentinent apetite
|
||||||
|
let mostDislikedSnackRecord: IHelminthFoodRecord = { ItemType: "", Date: 0 };
|
||||||
|
for (const resource of inventory.InfestedFoundry.Resources) {
|
||||||
|
if (resource.RecentlyConvertedResources) {
|
||||||
|
for (const record of resource.RecentlyConvertedResources) {
|
||||||
|
if (record.Date > mostDislikedSnackRecord.Date) {
|
||||||
|
mostDislikedSnackRecord = record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug("helminth eats sentient resource; most disliked snack:", {
|
||||||
|
type: mostDislikedSnackRecord.ItemType,
|
||||||
|
date: mostDislikedSnackRecord.Date
|
||||||
|
});
|
||||||
|
mostDislikedSnackRecord.Date = currentUnixSeconds + 24 * 60 * 60; // Possibly unfaithful
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let resource = inventory.InfestedFoundry.Resources.find(x => x.ItemType == snack.type);
|
||||||
|
if (!resource) {
|
||||||
|
resource =
|
||||||
|
inventory.InfestedFoundry.Resources[
|
||||||
|
inventory.InfestedFoundry.Resources.push({ ItemType: snack.type, Count: 0 }) - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
resource.RecentlyConvertedResources ??= [];
|
||||||
|
let record = resource.RecentlyConvertedResources.find(x => x.ItemType == contribution.ItemType);
|
||||||
|
if (!record) {
|
||||||
|
record =
|
||||||
|
resource.RecentlyConvertedResources[
|
||||||
|
resource.RecentlyConvertedResources.push({ ItemType: contribution.ItemType, Date: 0 }) - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const hoursRemaining = (record.Date - currentUnixSeconds) / 3600;
|
||||||
|
const apetiteFactor = apetiteModel(hoursRemaining) / 30;
|
||||||
|
logger.debug(`helminth eating ${contribution.ItemType} (+${(snack.gain * 100).toFixed(0)}%)`, {
|
||||||
|
hoursRemaining,
|
||||||
|
apetiteFactor
|
||||||
|
});
|
||||||
|
if (hoursRemaining >= 18) {
|
||||||
|
record.Date = currentUnixSeconds + 72 * 60 * 60; // Possibly unfaithful
|
||||||
|
} else {
|
||||||
|
record.Date = currentUnixSeconds + 24 * 60 * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPercentagePointsGained += snack.gain * 100 * apetiteFactor; // 30% would be gain=0.3, so percentage points is equal to gain * 100.
|
||||||
|
resource.Count += Math.trunc(snack.gain * 1000 * apetiteFactor); // 30% would be gain=0.3 or Count=300, so Count=gain*1000.
|
||||||
|
if (resource.Count > 1000) resource.Count = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry, 666 * totalPercentagePointsGained);
|
||||||
|
addRecipes(inventory, recipeChanges);
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: {
|
||||||
|
Recipes: recipeChanges,
|
||||||
|
InfestedFoundry: {
|
||||||
|
XP: inventory.InfestedFoundry.XP,
|
||||||
|
Resources: inventory.InfestedFoundry.Resources,
|
||||||
|
Slots: inventory.InfestedFoundry.Slots
|
||||||
|
},
|
||||||
|
MiscItems: miscItemChanges
|
||||||
|
}
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "o": {
|
||||||
|
// offerings update
|
||||||
|
const request = getJSONfromString<IHelminthOfferingsUpdate>(String(req.body));
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
inventory.InfestedFoundry ??= {};
|
||||||
|
inventory.InfestedFoundry.InvigorationIndex = request.OfferingsIndex;
|
||||||
|
inventory.InfestedFoundry.InvigorationSuitOfferings = request.SuitTypes;
|
||||||
|
if (request.Extra) {
|
||||||
|
inventory.InfestedFoundry.InvigorationsApplied = 0;
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
|
||||||
|
applyCheatsToInfestedFoundry(infestedFoundry);
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: {
|
||||||
|
InfestedFoundry: infestedFoundry
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "a": {
|
||||||
|
// subsume warframe
|
||||||
|
const request = getJSONfromString<IHelminthSubsumeRequest>(String(req.body));
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const recipe = getRecipe(request.Recipe)!;
|
||||||
|
if (!config.infiniteHelminthMaterials) {
|
||||||
|
for (const ingredient of recipe.secretIngredients!) {
|
||||||
|
const resource = inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == ingredient.ItemType);
|
||||||
|
if (resource) {
|
||||||
|
resource.Count -= ingredient.ItemCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||||
|
inventory.Suits.pull(suit);
|
||||||
|
const consumedSuit: IConsumedSuit = { s: suit.ItemType };
|
||||||
|
if (suit.Configs[0] && suit.Configs[0].pricol) {
|
||||||
|
consumedSuit.c = suit.Configs[0].pricol;
|
||||||
|
}
|
||||||
|
if ((inventory.InfestedFoundry!.XP ?? 0) < 73125_00) {
|
||||||
|
inventory.InfestedFoundry!.Slots!--;
|
||||||
|
}
|
||||||
|
inventory.InfestedFoundry!.ConsumedSuits ??= [];
|
||||||
|
inventory.InfestedFoundry!.ConsumedSuits.push(consumedSuit);
|
||||||
|
inventory.InfestedFoundry!.LastConsumedSuit = suit;
|
||||||
|
inventory.InfestedFoundry!.AbilityOverrideUnlockCooldown = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
||||||
|
const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry!, 1600_00);
|
||||||
|
addRecipes(inventory, recipeChanges);
|
||||||
|
freeUpSlot(inventory, InventorySlot.SUITS);
|
||||||
|
await inventory.save();
|
||||||
|
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
|
||||||
|
applyCheatsToInfestedFoundry(infestedFoundry);
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: {
|
||||||
|
Recipes: recipeChanges,
|
||||||
|
RemovedIdItems: [
|
||||||
|
{
|
||||||
|
ItemId: request.SuitId
|
||||||
|
}
|
||||||
|
],
|
||||||
|
SuitBin: {
|
||||||
|
count: -1,
|
||||||
|
platinum: 0,
|
||||||
|
Slots: 1
|
||||||
|
},
|
||||||
|
InfestedFoundry: infestedFoundry
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "r": {
|
||||||
|
// rush subsume
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const currencyChanges = updateCurrency(inventory, 50, true);
|
||||||
|
const recipeChanges = handleSubsumeCompletion(inventory);
|
||||||
|
await inventory.save();
|
||||||
|
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
|
||||||
|
applyCheatsToInfestedFoundry(infestedFoundry);
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: {
|
||||||
|
...currencyChanges,
|
||||||
|
Recipes: recipeChanges,
|
||||||
|
InfestedFoundry: infestedFoundry
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "u": {
|
||||||
|
const request = getJSONfromString<IHelminthInvigorationRequest>(String(req.body));
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||||
|
const upgradesExpiry = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
||||||
|
suit.OffensiveUpgrade = request.OffensiveUpgradeType;
|
||||||
|
suit.DefensiveUpgrade = request.DefensiveUpgradeType;
|
||||||
|
suit.UpgradesExpiry = upgradesExpiry;
|
||||||
|
const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry!, 4800_00);
|
||||||
|
addRecipes(inventory, recipeChanges);
|
||||||
|
if (!config.infiniteHelminthMaterials) {
|
||||||
|
for (let i = 0; i != request.ResourceTypes.length; ++i) {
|
||||||
|
inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == request.ResourceTypes[i])!.Count -=
|
||||||
|
request.ResourceCosts[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inventory.InfestedFoundry!.InvigorationsApplied ??= 0;
|
||||||
|
inventory.InfestedFoundry!.InvigorationsApplied += 1;
|
||||||
|
await inventory.save();
|
||||||
|
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
|
||||||
|
applyCheatsToInfestedFoundry(infestedFoundry);
|
||||||
|
res.json({
|
||||||
|
SuitId: request.SuitId,
|
||||||
|
OffensiveUpgrade: request.OffensiveUpgradeType,
|
||||||
|
DefensiveUpgrade: request.DefensiveUpgradeType,
|
||||||
|
UpgradesExpiry: toMongoDate(upgradesExpiry),
|
||||||
|
InventoryChanges: {
|
||||||
|
Recipes: recipeChanges,
|
||||||
|
InfestedFoundry: infestedFoundry
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "custom_unlockall": {
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
inventory.InfestedFoundry ??= {};
|
||||||
|
inventory.InfestedFoundry.XP ??= 0;
|
||||||
|
if (151875_00 > inventory.InfestedFoundry.XP) {
|
||||||
|
const recipeChanges = addInfestedFoundryXP(
|
||||||
|
inventory.InfestedFoundry,
|
||||||
|
151875_00 - inventory.InfestedFoundry.XP
|
||||||
|
);
|
||||||
|
addRecipes(inventory, recipeChanges);
|
||||||
|
await inventory.save();
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`unhandled infestedFoundry mode: ${req.query.mode}`);
|
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||||
|
throw new Error(`unhandled infestedFoundry mode: ${String(req.query.mode)}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -70,21 +368,74 @@ interface IShardInstallRequest {
|
|||||||
Color: string;
|
Color: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IShardUninstallRequest {
|
||||||
|
SuitId: IOid;
|
||||||
|
Slot: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface IHelminthNameRequest {
|
interface IHelminthNameRequest {
|
||||||
newName: string;
|
newName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const colorToShard: Record<string, string> = {
|
interface IHelminthFeedRequest {
|
||||||
ACC_RED: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalAmar",
|
ResourceContributions: {
|
||||||
ACC_RED_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalAmarMythic",
|
ItemType: string;
|
||||||
ACC_YELLOW: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalNira",
|
Date: number; // unix timestamp
|
||||||
ACC_YELLOW_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalNiraMythic",
|
}[];
|
||||||
ACC_BLUE: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalBoreal",
|
}
|
||||||
ACC_BLUE_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalBorealMythic",
|
|
||||||
ACC_GREEN: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalGreen",
|
interface IHelminthSubsumeRequest {
|
||||||
ACC_GREEN_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalGreenMythic",
|
SuitId: IOid;
|
||||||
ACC_ORANGE: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalOrange",
|
Recipe: string;
|
||||||
ACC_ORANGE_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalOrangeMythic",
|
}
|
||||||
ACC_PURPLE: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalViolet",
|
|
||||||
ACC_PURPLE_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalVioletMythic"
|
interface IHelminthOfferingsUpdate {
|
||||||
|
OfferingsIndex: number;
|
||||||
|
SuitTypes: string[];
|
||||||
|
Extra: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IHelminthInvigorationRequest {
|
||||||
|
SuitId: IOid;
|
||||||
|
OffensiveUpgradeType: string;
|
||||||
|
DefensiveUpgradeType: string;
|
||||||
|
ResourceTypes: string[];
|
||||||
|
ResourceCosts: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// A fitted model for observed apetite values. Likely slightly inaccurate.
|
||||||
|
//
|
||||||
|
// Hours remaining, percentage points gained (out of 30 total)
|
||||||
|
// 0, 30
|
||||||
|
// 5, 25.8
|
||||||
|
// 10, 21.6
|
||||||
|
// 12, 20
|
||||||
|
// 16, 16.6
|
||||||
|
// 17, 15.8
|
||||||
|
// 18, 15
|
||||||
|
// 20, 15
|
||||||
|
// 24, 15
|
||||||
|
// 36, 15
|
||||||
|
// 40, 13.6
|
||||||
|
// 47, 11.3
|
||||||
|
// 48, 11
|
||||||
|
// 50, 10.3
|
||||||
|
// 60, 7
|
||||||
|
// 70, 3.6
|
||||||
|
// 71, 3.3
|
||||||
|
// 72, 3
|
||||||
|
const apetiteModel = (x: number): number => {
|
||||||
|
if (x <= 0) {
|
||||||
|
return 30;
|
||||||
|
}
|
||||||
|
if (x < 18) {
|
||||||
|
return -0.84 * x + 30;
|
||||||
|
}
|
||||||
|
if (x <= 36) {
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
if (x < 71.9) {
|
||||||
|
return -0.3327892 * x + 26.94135;
|
||||||
|
}
|
||||||
|
return 3;
|
||||||
};
|
};
|
||||||
|
@ -1,46 +1,156 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { toInventoryResponse } from "@/src/helpers/inventoryHelpers";
|
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import allDialogue from "@/static/fixed_responses/allDialogue.json";
|
import allDialogue from "@/static/fixed_responses/allDialogue.json";
|
||||||
import allMissions from "@/static/fixed_responses/allMissions.json";
|
|
||||||
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
|
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
|
||||||
import { IInventoryDatabase, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IPolarity, ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { ExportCustoms, ExportFlavour, ExportKeys, ExportResources } from "warframe-public-export-plus";
|
import {
|
||||||
|
ExportCustoms,
|
||||||
|
ExportFlavour,
|
||||||
|
ExportRegions,
|
||||||
|
ExportResources,
|
||||||
|
ExportVirtuals
|
||||||
|
} from "warframe-public-export-plus";
|
||||||
|
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
|
||||||
|
import {
|
||||||
|
addMiscItems,
|
||||||
|
allDailyAffiliationKeys,
|
||||||
|
cleanupInventory,
|
||||||
|
createLibraryDailyTask,
|
||||||
|
generateRewardSeed
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
export const inventoryController: RequestHandler = async (request, response) => {
|
||||||
const inventoryController: RequestHandler = async (request, response) => {
|
const account = await getAccountForRequest(request);
|
||||||
let accountId;
|
|
||||||
try {
|
|
||||||
accountId = await getAccountIdForRequest(request);
|
|
||||||
} catch (e) {
|
|
||||||
response.status(400).send("Log-in expired");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const inventory = await Inventory.findOne({ accountOwnerId: accountId })
|
const inventory = await Inventory.findOne({ accountOwnerId: account._id });
|
||||||
.populate<{ LoadOutPresets: ILoadoutDatabase }>("LoadOutPresets")
|
|
||||||
.populate<{ Ships: IShipInventory }>("Ships", "-ShipInteriorColors");
|
|
||||||
|
|
||||||
if (!inventory) {
|
if (!inventory) {
|
||||||
response.status(400).json({ error: "inventory was undefined" });
|
response.status(400).json({ error: "inventory was undefined" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: make a function that converts from database representation to client
|
// Handle daily reset
|
||||||
const inventoryJSON: IInventoryDatabase = inventory.toJSON();
|
if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) {
|
||||||
console.log(inventoryJSON.Ships);
|
for (const key of allDailyAffiliationKeys) {
|
||||||
|
inventory[key] = 16000 + inventory.PlayerLevel * 500;
|
||||||
|
}
|
||||||
|
inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
|
||||||
|
inventory.GiftsRemaining = Math.max(8, inventory.PlayerLevel);
|
||||||
|
inventory.TradesRemaining = inventory.PlayerLevel;
|
||||||
|
|
||||||
const inventoryResponse = toInventoryResponse(inventoryJSON);
|
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
|
||||||
|
|
||||||
if (config.infiniteResources) {
|
if (inventory.NextRefill) {
|
||||||
|
if (config.noArgonCrystalDecay) {
|
||||||
|
inventory.FoundToday = undefined;
|
||||||
|
} else {
|
||||||
|
const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
|
||||||
|
const today = Math.trunc(Date.now() / 86400000);
|
||||||
|
const daysPassed = today - lastLoginDay;
|
||||||
|
for (let i = 0; i != daysPassed; ++i) {
|
||||||
|
const numArgonCrystals =
|
||||||
|
inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
|
||||||
|
?.ItemCount ?? 0;
|
||||||
|
if (numArgonCrystals == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const numStableArgonCrystals = Math.min(
|
||||||
|
numArgonCrystals,
|
||||||
|
inventory.FoundToday?.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
|
||||||
|
?.ItemCount ?? 0
|
||||||
|
);
|
||||||
|
const numDecayingArgonCrystals = numArgonCrystals - numStableArgonCrystals;
|
||||||
|
const numDecayingArgonCrystalsToRemove = Math.ceil(numDecayingArgonCrystals / 2);
|
||||||
|
logger.debug(`ticking argon crystals for day ${i + 1} of ${daysPassed}`, {
|
||||||
|
numArgonCrystals,
|
||||||
|
numStableArgonCrystals,
|
||||||
|
numDecayingArgonCrystals,
|
||||||
|
numDecayingArgonCrystalsToRemove
|
||||||
|
});
|
||||||
|
// Remove half of owned decaying argon crystals
|
||||||
|
addMiscItems(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Items/MiscItems/ArgonCrystal",
|
||||||
|
ItemCount: numDecayingArgonCrystalsToRemove * -1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
// All stable argon crystals are now decaying
|
||||||
|
inventory.FoundToday = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupInventory(inventory);
|
||||||
|
|
||||||
|
inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
|
||||||
|
//await inventory.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
inventory.InfestedFoundry &&
|
||||||
|
inventory.InfestedFoundry.AbilityOverrideUnlockCooldown &&
|
||||||
|
new Date() >= inventory.InfestedFoundry.AbilityOverrideUnlockCooldown
|
||||||
|
) {
|
||||||
|
handleSubsumeCompletion(inventory);
|
||||||
|
//await inventory.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inventory.LastInventorySync) {
|
||||||
|
const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000);
|
||||||
|
const currentDuviriMood = Math.trunc(Date.now() / 7200000);
|
||||||
|
if (lastSyncDuviriMood != currentDuviriMood) {
|
||||||
|
logger.debug(`refreshing duviri seed`);
|
||||||
|
if (!inventory.DuviriInfo) {
|
||||||
|
inventory.DuviriInfo = {
|
||||||
|
Seed: generateRewardSeed(),
|
||||||
|
NumCompletions: 0
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
inventory.DuviriInfo.Seed = generateRewardSeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inventory.LastInventorySync = new Types.ObjectId();
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
response.json(
|
||||||
|
await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query, account.BuildLabel)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getInventoryResponse = async (
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
xpBasedLevelCapDisabled: boolean,
|
||||||
|
buildLabel: string | undefined
|
||||||
|
): Promise<IInventoryClient> => {
|
||||||
|
const inventoryWithLoadOutPresets = await inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>(
|
||||||
|
"LoadOutPresets"
|
||||||
|
);
|
||||||
|
const inventoryWithLoadOutPresetsAndShips = await inventoryWithLoadOutPresets.populate<{ Ships: IShipInventory }>(
|
||||||
|
"Ships"
|
||||||
|
);
|
||||||
|
const inventoryResponse = inventoryWithLoadOutPresetsAndShips.toJSON<IInventoryClient>();
|
||||||
|
|
||||||
|
if (config.infiniteCredits) {
|
||||||
inventoryResponse.RegularCredits = 999999999;
|
inventoryResponse.RegularCredits = 999999999;
|
||||||
inventoryResponse.TradesRemaining = 999999999;
|
}
|
||||||
inventoryResponse.PremiumCreditsFree = 999999999;
|
if (config.infinitePlatinum) {
|
||||||
|
inventoryResponse.PremiumCreditsFree = 0;
|
||||||
inventoryResponse.PremiumCredits = 999999999;
|
inventoryResponse.PremiumCredits = 999999999;
|
||||||
}
|
}
|
||||||
|
if (config.infiniteEndo) {
|
||||||
|
inventoryResponse.FusionPoints = 999999999;
|
||||||
|
}
|
||||||
|
if (config.infiniteRegalAya) {
|
||||||
|
inventoryResponse.PrimeTokens = 999999999;
|
||||||
|
}
|
||||||
|
|
||||||
if (config.skipAllDialogue) {
|
if (config.skipAllDialogue) {
|
||||||
inventoryResponse.TauntHistory = [
|
inventoryResponse.TauntHistory = [
|
||||||
@ -55,43 +165,22 @@ const inventoryController: RequestHandler = async (request, response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.unlockAllMissions) {
|
if (config.unlockAllMissions) {
|
||||||
inventoryResponse.Missions = allMissions;
|
inventoryResponse.Missions = [];
|
||||||
|
for (const tag of Object.keys(ExportRegions)) {
|
||||||
|
inventoryResponse.Missions.push({
|
||||||
|
Completes: 1,
|
||||||
|
Tier: 1,
|
||||||
|
Tag: tag
|
||||||
|
});
|
||||||
|
}
|
||||||
addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked");
|
addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.unlockAllQuests) {
|
|
||||||
for (const [k, v] of Object.entries(ExportKeys)) {
|
|
||||||
if ("chainStages" in v) {
|
|
||||||
if (!inventoryResponse.QuestKeys.find(quest => quest.ItemType == k)) {
|
|
||||||
inventoryResponse.QuestKeys.push({ ItemType: k });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (config.completeAllQuests) {
|
|
||||||
for (const quest of inventoryResponse.QuestKeys) {
|
|
||||||
quest.Completed = true;
|
|
||||||
quest.Progress = [
|
|
||||||
{
|
|
||||||
c: 0,
|
|
||||||
i: false,
|
|
||||||
m: false,
|
|
||||||
b: []
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
inventoryResponse.ArchwingEnabled = true;
|
|
||||||
|
|
||||||
// Skip "Watch The Maker"
|
|
||||||
addString(inventoryResponse.NodeIntrosCompleted, "/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.unlockAllShipDecorations) {
|
if (config.unlockAllShipDecorations) {
|
||||||
inventoryResponse.ShipDecorations = [];
|
inventoryResponse.ShipDecorations = [];
|
||||||
for (const [uniqueName, item] of Object.entries(ExportResources)) {
|
for (const [uniqueName, item] of Object.entries(ExportResources)) {
|
||||||
if (item.productCategory == "ShipDecorations") {
|
if (item.productCategory == "ShipDecorations") {
|
||||||
inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 1 });
|
inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 999_999 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,20 +193,32 @@ const inventoryController: RequestHandler = async (request, response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.unlockAllSkins) {
|
if (config.unlockAllSkins) {
|
||||||
inventoryResponse.WeaponSkins = [];
|
const missingWeaponSkins = new Set(Object.keys(ExportCustoms));
|
||||||
for (const uniqueName in ExportCustoms) {
|
inventoryResponse.WeaponSkins.forEach(x => missingWeaponSkins.delete(x.ItemType));
|
||||||
|
for (const uniqueName of missingWeaponSkins) {
|
||||||
inventoryResponse.WeaponSkins.push({
|
inventoryResponse.WeaponSkins.push({
|
||||||
ItemId: {
|
ItemId: {
|
||||||
$oid: "000000000000000000000000"
|
$oid: "ca70ca70ca70ca70" + catBreadHash(uniqueName).toString(16).padStart(8, "0")
|
||||||
},
|
},
|
||||||
ItemType: uniqueName
|
ItemType: uniqueName
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.unlockAllCapturaScenes) {
|
||||||
|
for (const uniqueName of Object.keys(ExportResources)) {
|
||||||
|
if (resourceInheritsFrom(uniqueName, "/Lotus/Types/Items/MiscItems/PhotoboothTile")) {
|
||||||
|
inventoryResponse.MiscItems.push({
|
||||||
|
ItemType: uniqueName,
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof config.spoofMasteryRank === "number" && config.spoofMasteryRank >= 0) {
|
if (typeof config.spoofMasteryRank === "number" && config.spoofMasteryRank >= 0) {
|
||||||
inventoryResponse.PlayerLevel = config.spoofMasteryRank;
|
inventoryResponse.PlayerLevel = config.spoofMasteryRank;
|
||||||
if (!("xpBasedLevelCapDisabled" in request.query)) {
|
if (!xpBasedLevelCapDisabled) {
|
||||||
// This client has not been patched to accept any mastery rank, need to fake the XP.
|
// This client has not been patched to accept any mastery rank, need to fake the XP.
|
||||||
inventoryResponse.XPInfo = [];
|
inventoryResponse.XPInfo = [];
|
||||||
let numFrames = getExpRequiredForMr(Math.min(config.spoofMasteryRank, 5030)) / 6000;
|
let numFrames = getExpRequiredForMr(Math.min(config.spoofMasteryRank, 5030)) / 6000;
|
||||||
@ -132,7 +233,8 @@ const inventoryController: RequestHandler = async (request, response) => {
|
|||||||
|
|
||||||
if (config.universalPolarityEverywhere) {
|
if (config.universalPolarityEverywhere) {
|
||||||
const Polarity: IPolarity[] = [];
|
const Polarity: IPolarity[] = [];
|
||||||
for (let i = 0; i != 10; ++i) {
|
// 12 is needed for necramechs. 15 is needed for plexus/crewshipharness.
|
||||||
|
for (let i = 0; i != 15; ++i) {
|
||||||
Polarity.push({
|
Polarity.push({
|
||||||
Slot: i,
|
Slot: i,
|
||||||
Value: ArtifactPolarity.Any
|
Value: ArtifactPolarity.Any
|
||||||
@ -147,10 +249,70 @@ const inventoryController: RequestHandler = async (request, response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix for #380
|
if (config.unlockDoubleCapacityPotatoesEverywhere) {
|
||||||
inventoryResponse.NextRefill = { $date: { $numberLong: "9999999999999" } };
|
for (const key of equipmentKeys) {
|
||||||
|
if (key in inventoryResponse) {
|
||||||
|
for (const equipment of inventoryResponse[key]) {
|
||||||
|
equipment.Features ??= 0;
|
||||||
|
equipment.Features |= EquipmentFeatures.DOUBLE_CAPACITY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response.json(inventoryResponse);
|
if (config.unlockExilusEverywhere) {
|
||||||
|
for (const key of equipmentKeys) {
|
||||||
|
if (key in inventoryResponse) {
|
||||||
|
for (const equipment of inventoryResponse[key]) {
|
||||||
|
equipment.Features ??= 0;
|
||||||
|
equipment.Features |= EquipmentFeatures.UTILITY_SLOT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.unlockArcanesEverywhere) {
|
||||||
|
for (const key of equipmentKeys) {
|
||||||
|
if (key in inventoryResponse) {
|
||||||
|
for (const equipment of inventoryResponse[key]) {
|
||||||
|
equipment.Features ??= 0;
|
||||||
|
equipment.Features |= EquipmentFeatures.ARCANE_SLOT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.noDailyStandingLimits) {
|
||||||
|
const spoofedDailyAffiliation = Math.max(999_999, 16000 + inventoryResponse.PlayerLevel * 500);
|
||||||
|
for (const key of allDailyAffiliationKeys) {
|
||||||
|
inventoryResponse[key] = spoofedDailyAffiliation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.noDailyFocusLimit) {
|
||||||
|
inventoryResponse.DailyFocus = Math.max(999_999, 250000 + inventoryResponse.PlayerLevel * 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inventoryResponse.InfestedFoundry) {
|
||||||
|
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
|
||||||
|
inventoryResponse.LastInventorySync = undefined;
|
||||||
|
|
||||||
|
// Set 2FA enabled so trading post can be used
|
||||||
|
inventoryResponse.HWIDProtectEnabled = true;
|
||||||
|
|
||||||
|
// Fix nemesis for older versions
|
||||||
|
if (
|
||||||
|
inventoryResponse.Nemesis &&
|
||||||
|
buildLabel &&
|
||||||
|
!isNemesisCompatibleWithVersion(inventoryResponse.Nemesis, buildLabel)
|
||||||
|
) {
|
||||||
|
inventoryResponse.Nemesis = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return inventoryResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addString = (arr: string[], str: string): void => {
|
const addString = (arr: string[], str: string): void => {
|
||||||
@ -166,4 +328,20 @@ const getExpRequiredForMr = (rank: number): number => {
|
|||||||
return 2_250_000 + 147_500 * (rank - 30);
|
return 2_250_000 + 147_500 * (rank - 30);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { inventoryController };
|
const resourceInheritsFrom = (resourceName: string, targetName: string): boolean => {
|
||||||
|
let parentName = resourceGetParent(resourceName);
|
||||||
|
for (; parentName != undefined; parentName = resourceGetParent(parentName)) {
|
||||||
|
if (parentName == targetName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resourceGetParent = (resourceName: string): string | undefined => {
|
||||||
|
if (resourceName in ExportResources) {
|
||||||
|
return ExportResources[resourceName].parentName;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
return ExportVirtuals[resourceName]?.parentName;
|
||||||
|
};
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { updateCurrency } from "@/src/services/inventoryService";
|
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { updateSlots } from "@/src/services/inventoryService";
|
import { updateSlots } from "@/src/services/inventoryService";
|
||||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
loadout slots are additionally purchased slots only
|
loadout slots are additionally purchased slots only
|
||||||
@ -18,19 +19,22 @@ import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
|||||||
number of frames = extra - slots + 2
|
number of frames = extra - slots + 2
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
export const inventorySlotsController: RequestHandler = async (req, res) => {
|
export const inventorySlotsController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
//const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
|
const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
|
||||||
|
|
||||||
//console.log(body);
|
if (body.Bin != InventorySlot.SUITS && body.Bin != InventorySlot.PVE_LOADOUTS) {
|
||||||
|
logger.warn(`unexpected slot purchase of type ${body.Bin}, account may be overcharged`);
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: check which slot was purchased because pvpBonus is also possible
|
const inventory = await getInventory(accountId);
|
||||||
|
const currencyChanges = updateCurrency(inventory, 20, true);
|
||||||
const currencyChanges = await updateCurrency(20, true, accountId);
|
updateSlots(inventory, body.Bin, 1, 1);
|
||||||
await updateSlots(accountId, InventorySlot.PVE_LOADOUTS, 1, 1);
|
await inventory.save();
|
||||||
|
|
||||||
//console.log({ InventoryChanges: currencyChanges }, " added loadout changes:");
|
|
||||||
|
|
||||||
res.json({ InventoryChanges: currencyChanges });
|
res.json({ InventoryChanges: currencyChanges });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface IInventorySlotsRequest {
|
||||||
|
Bin: InventorySlot;
|
||||||
|
}
|
||||||
|
@ -2,12 +2,13 @@ import { RequestHandler } from "express";
|
|||||||
import { getSessionByID } from "@/src/managers/sessionManager";
|
import { getSessionByID } from "@/src/managers/sessionManager";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
|
|
||||||
const joinSessionController: RequestHandler = (_req, res) => {
|
export const joinSessionController: RequestHandler = (req, res) => {
|
||||||
const reqBody = JSON.parse(String(_req.body));
|
const reqBody = JSON.parse(String(req.body)) as IJoinSessionRequest;
|
||||||
logger.debug(`JoinSession Request`, { reqBody });
|
logger.debug(`JoinSession Request`, { reqBody });
|
||||||
const req = JSON.parse(String(_req.body));
|
const session = getSessionByID(reqBody.sessionIds[0]);
|
||||||
const session = getSessionByID(req.sessionIds[0] as string);
|
|
||||||
res.json({ rewardSeed: session?.rewardSeed, sessionId: { $oid: session?.sessionId } });
|
res.json({ rewardSeed: session?.rewardSeed, sessionId: { $oid: session?.sessionId } });
|
||||||
};
|
};
|
||||||
|
|
||||||
export { joinSessionController };
|
interface IJoinSessionRequest {
|
||||||
|
sessionIds: string[];
|
||||||
|
}
|
||||||
|
@ -1,55 +1,57 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import buildConfig from "@/static/data/buildConfig.json";
|
import { buildConfig } from "@/src/services/buildConfigService";
|
||||||
|
|
||||||
import { toLoginRequest } from "@/src/helpers/loginHelpers";
|
|
||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
import { createAccount, isCorrectPassword } from "@/src/services/loginService";
|
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
|
||||||
import { ILoginResponse } from "@/src/types/loginTypes";
|
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
|
||||||
import { DTLS, groups, HUB, platformCDNs } from "@/static/fixed_responses/login_static";
|
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { version_compare } from "@/src/services/worldStateService";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
export const loginController: RequestHandler = async (request, response) => {
|
||||||
const loginController: RequestHandler = async (request, response) => {
|
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument
|
|
||||||
const body = JSON.parse(request.body); // parse octet stream of json data to json object
|
|
||||||
const loginRequest = toLoginRequest(body);
|
|
||||||
|
|
||||||
const account = await Account.findOne({ email: loginRequest.email }); //{ _id: 0, __v: 0 }
|
const account = await Account.findOne({ email: loginRequest.email });
|
||||||
const nonce = Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
|
const nonce = Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||||
|
|
||||||
if (!account && config.autoCreateAccount && loginRequest.ClientType != "webui") {
|
const buildLabel: string =
|
||||||
|
typeof request.query.buildLabel == "string"
|
||||||
|
? request.query.buildLabel.split(" ").join("+")
|
||||||
|
: buildConfig.buildLabel;
|
||||||
|
|
||||||
|
const myAddress = request.host.indexOf("warframe.com") == -1 ? request.host : config.myAddress;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!account &&
|
||||||
|
((config.autoCreateAccount && loginRequest.ClientType != "webui") ||
|
||||||
|
loginRequest.ClientType == "webui-register")
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
|
const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
|
||||||
|
let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja";
|
||||||
|
if (await isNameTaken(name)) {
|
||||||
|
let suffix = 0;
|
||||||
|
do {
|
||||||
|
++suffix;
|
||||||
|
name = nameFromEmail + suffix;
|
||||||
|
} while (await isNameTaken(name));
|
||||||
|
}
|
||||||
const newAccount = await createAccount({
|
const newAccount = await createAccount({
|
||||||
email: loginRequest.email,
|
email: loginRequest.email,
|
||||||
password: loginRequest.password,
|
password: loginRequest.password,
|
||||||
DisplayName: loginRequest.email.substring(0, loginRequest.email.indexOf("@")),
|
DisplayName: name,
|
||||||
CountryCode: loginRequest.lang.toUpperCase(),
|
CountryCode: loginRequest.lang.toUpperCase(),
|
||||||
ClientType: loginRequest.ClientType,
|
ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType,
|
||||||
CrossPlatformAllowed: true,
|
CrossPlatformAllowed: true,
|
||||||
ForceLogoutVersion: 0,
|
ForceLogoutVersion: 0,
|
||||||
ConsentNeeded: false,
|
ConsentNeeded: false,
|
||||||
TrackedSettings: [],
|
TrackedSettings: [],
|
||||||
Nonce: nonce
|
Nonce: nonce,
|
||||||
|
BuildLabel: buildLabel
|
||||||
});
|
});
|
||||||
logger.debug("created new account");
|
logger.debug("created new account");
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
response.json(createLoginResponse(myAddress, newAccount, buildLabel));
|
||||||
const { email, password, ...databaseAccount } = newAccount;
|
|
||||||
const newLoginResponse: ILoginResponse = {
|
|
||||||
...databaseAccount,
|
|
||||||
Groups: groups,
|
|
||||||
platformCDNs: platformCDNs,
|
|
||||||
NRS: [config.myAddress],
|
|
||||||
DTLS: DTLS,
|
|
||||||
IRC: config.myIrcAddresses ?? [config.myAddress],
|
|
||||||
HUB: HUB,
|
|
||||||
BuildLabel: buildConfig.buildLabel,
|
|
||||||
MatchmakingBuildId: buildConfig.matchmakingBuildId
|
|
||||||
};
|
|
||||||
|
|
||||||
response.json(newLoginResponse);
|
|
||||||
return;
|
return;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@ -58,34 +60,77 @@ const loginController: RequestHandler = async (request, response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//email not found or incorrect password
|
if (!account) {
|
||||||
if (!account || !isCorrectPassword(loginRequest.password, account.password)) {
|
response.status(400).json({ error: "unknown user" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginRequest.ClientType == "webui-register") {
|
||||||
|
response.status(400).json({ error: "account already exists" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCorrectPassword(loginRequest.password, account.password)) {
|
||||||
response.status(400).json({ error: "incorrect login data" });
|
response.status(400).json({ error: "incorrect login data" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (account.Nonce == 0 || loginRequest.ClientType != "webui") {
|
if (loginRequest.ClientType == "webui") {
|
||||||
|
if (!account.Nonce) {
|
||||||
|
account.ClientType = "webui";
|
||||||
account.Nonce = nonce;
|
account.Nonce = nonce;
|
||||||
}
|
}
|
||||||
if (loginRequest.ClientType != "webui") {
|
} else {
|
||||||
|
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
|
||||||
|
response.status(400).json({ error: "nonce still set" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
account.ClientType = loginRequest.ClientType;
|
||||||
|
account.Nonce = nonce;
|
||||||
account.CountryCode = loginRequest.lang.toUpperCase();
|
account.CountryCode = loginRequest.lang.toUpperCase();
|
||||||
|
account.BuildLabel = buildLabel;
|
||||||
}
|
}
|
||||||
await account.save();
|
await account.save();
|
||||||
|
|
||||||
const { email, password, ...databaseAccount } = account.toJSON();
|
response.json(createLoginResponse(myAddress, account.toJSON(), buildLabel));
|
||||||
const newLoginResponse: ILoginResponse = {
|
|
||||||
...databaseAccount,
|
|
||||||
Groups: groups,
|
|
||||||
platformCDNs: platformCDNs,
|
|
||||||
NRS: [config.myAddress],
|
|
||||||
DTLS: DTLS,
|
|
||||||
IRC: config.myIrcAddresses ?? [config.myAddress],
|
|
||||||
HUB: HUB,
|
|
||||||
BuildLabel: buildConfig.buildLabel,
|
|
||||||
MatchmakingBuildId: buildConfig.matchmakingBuildId
|
|
||||||
};
|
|
||||||
|
|
||||||
response.json(newLoginResponse);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { loginController };
|
const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
|
||||||
|
const resp: ILoginResponse = {
|
||||||
|
id: account.id,
|
||||||
|
DisplayName: account.DisplayName,
|
||||||
|
CountryCode: account.CountryCode,
|
||||||
|
AmazonAuthToken: account.AmazonAuthToken,
|
||||||
|
AmazonRefreshToken: account.AmazonRefreshToken,
|
||||||
|
ConsentNeeded: account.ConsentNeeded,
|
||||||
|
TrackedSettings: account.TrackedSettings,
|
||||||
|
Nonce: account.Nonce,
|
||||||
|
IRC: config.myIrcAddresses ?? [myAddress],
|
||||||
|
NRS: config.NRS,
|
||||||
|
BuildLabel: buildLabel
|
||||||
|
};
|
||||||
|
if (version_compare(buildLabel, "2019.08.29.20.01") >= 0) {
|
||||||
|
// U25.7 and up
|
||||||
|
resp.ForceLogoutVersion = account.ForceLogoutVersion;
|
||||||
|
}
|
||||||
|
if (version_compare(buildLabel, "2019.10.31.22.42") >= 0) {
|
||||||
|
// U26 and up
|
||||||
|
resp.Groups = [];
|
||||||
|
}
|
||||||
|
if (version_compare(buildLabel, "2021.04.13.19.58") >= 0) {
|
||||||
|
resp.DTLS = 99;
|
||||||
|
}
|
||||||
|
if (version_compare(buildLabel, "2022.04.29.12.53") >= 0) {
|
||||||
|
resp.ClientType = account.ClientType;
|
||||||
|
}
|
||||||
|
if (version_compare(buildLabel, "2022.09.06.19.24") >= 0) {
|
||||||
|
resp.CrossPlatformAllowed = account.CrossPlatformAllowed;
|
||||||
|
resp.HUB = `https://${myAddress}/api/`;
|
||||||
|
resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
|
||||||
|
}
|
||||||
|
if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
|
||||||
|
resp.platformCDNs = [`https://${myAddress}/`];
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
};
|
||||||
|
@ -1,8 +1,55 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import loginRewards from "@/static/fixed_responses/loginRewards.json";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
|
import {
|
||||||
|
claimLoginReward,
|
||||||
|
getRandomLoginRewards,
|
||||||
|
ILoginRewardsReponse,
|
||||||
|
isLoginRewardAChoice,
|
||||||
|
setAccountGotLoginRewardToday
|
||||||
|
} from "@/src/services/loginRewardService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
const loginRewardsController: RequestHandler = (_req, res) => {
|
export const loginRewardsController: RequestHandler = async (req, res) => {
|
||||||
res.json(loginRewards);
|
const account = await getAccountForRequest(req);
|
||||||
|
const today = Math.trunc(Date.now() / 86400000) * 86400;
|
||||||
|
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
|
||||||
|
const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
|
||||||
|
|
||||||
|
if (today == account.LastLoginRewardDate) {
|
||||||
|
res.json({
|
||||||
|
DailyTributeInfo: {
|
||||||
|
IsMilestoneDay: isMilestoneDay,
|
||||||
|
IsChooseRewardSet: isLoginRewardAChoice(account),
|
||||||
|
LoginDays: account.LoginDays,
|
||||||
|
NextMilestoneReward: "",
|
||||||
|
NextMilestoneDay: nextMilestoneDay
|
||||||
|
}
|
||||||
|
} satisfies ILoginRewardsReponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventory = await getInventory(account._id.toString());
|
||||||
|
const randomRewards = getRandomLoginRewards(account, inventory);
|
||||||
|
const response: ILoginRewardsReponse = {
|
||||||
|
DailyTributeInfo: {
|
||||||
|
Rewards: randomRewards,
|
||||||
|
IsMilestoneDay: isMilestoneDay,
|
||||||
|
IsChooseRewardSet: randomRewards.length != 1,
|
||||||
|
LoginDays: account.LoginDays,
|
||||||
|
NextMilestoneReward: "",
|
||||||
|
NextMilestoneDay: nextMilestoneDay,
|
||||||
|
HasChosenReward: false
|
||||||
|
},
|
||||||
|
LastLoginRewardDate: today
|
||||||
|
};
|
||||||
|
if (!isMilestoneDay && randomRewards.length == 1) {
|
||||||
|
response.DailyTributeInfo.HasChosenReward = true;
|
||||||
|
response.DailyTributeInfo.ChosenReward = randomRewards[0];
|
||||||
|
response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
setAccountGotLoginRewardToday(account);
|
||||||
|
await account.save();
|
||||||
|
}
|
||||||
|
res.json(response);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { loginRewardsController };
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user