mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2025-10-24 13:50:14 +00:00
Compare commits
605 Commits
1.2.1
...
2464e4adda
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2464e4adda | ||
|
|
e88ef52d12 | ||
|
|
30b2f4eb36 | ||
|
|
4592fc42af | ||
|
|
70a27afad1 | ||
|
|
6cfb3bf532 | ||
| 9b2a65f8fd | |||
| 6a69388822 | |||
| 2924a27eb4 | |||
| ba20f5a401 | |||
|
|
eb88fa05cb | ||
|
|
0b73cef187 | ||
|
|
7af39b3d04 | ||
|
|
33206b1207 | ||
|
|
e325f7a40b | ||
|
|
82bee2051a | ||
|
|
4ece1bb89b | ||
|
|
31677e2638 | ||
|
|
d32827b692 | ||
|
|
13c009b448 | ||
|
|
a032497bed | ||
|
|
3b6b61d087 | ||
|
|
6d760f5bce | ||
|
|
2a622f901c | ||
|
|
03d28bf4e4 | ||
|
|
4b834579c5 | ||
|
|
07fe8ca367 | ||
|
|
2f3f8a3951 | ||
| 4f890a9c07 | |||
| 8517e0c7de | |||
| 5fb0cbbcf7 | |||
| 55e9f6531d | |||
|
|
7726357fbe | ||
|
|
564c275d51 | ||
|
|
3ce9ae5f77 | ||
|
|
7c5b9a8105 | ||
|
|
258ff35e20 | ||
|
|
ab480d88f1 | ||
|
|
89772d763b | ||
| bd0cc3c212 | |||
| c636c538eb | |||
| d3bef95a7f | |||
|
|
650f947451 | ||
|
|
f4b36b8f73 | ||
|
|
b12aecad63 | ||
|
|
5bf0c8f3ea | ||
|
|
2ddc956c9b | ||
|
|
4f0ae027a5 | ||
| 23ab908366 | |||
| be6a4c0a5d | |||
| 8eb1af20c8 | |||
| e73daa0865 | |||
| 743a39c125 | |||
| a9af8713bc | |||
| 4825267537 | |||
| a92cfaff25 | |||
| abcfa3445b | |||
| 2bf14200f7 | |||
| 876a9c82cd | |||
| fb5b0eeeb9 | |||
| 7aabc507e7 | |||
| 2914b95cff | |||
| dbd2ec2270 | |||
| 50e00a6772 | |||
| 7471bcbf38 | |||
| 100b4605ec | |||
| 741b898230 | |||
| 3f44f53f97 | |||
| d92b407349 | |||
| 9b3e856a05 | |||
| eb8e54c1f0 | |||
| 1ba0f5e14a | |||
| 12dde394c0 | |||
| b1eea6d4fe | |||
| f126b88781 | |||
| 2dbe2629c1 | |||
| 271eef83d3 | |||
| ca0d608a87 | |||
| 741bfb675b | |||
| c5dd745aa1 | |||
| 998b12617e | |||
| 129d1c2fe3 | |||
|
|
1bd4d2fbee | ||
| 63d4087488 | |||
| abda9dc158 | |||
|
|
7b7d8bce45 | ||
|
|
a9942eadab | ||
|
|
36638b1522 | ||
|
|
685cee2561 | ||
|
|
b683152fbf | ||
|
|
86576d48f6 | ||
|
|
4e1767ad58 | ||
|
|
4354cab7e3 | ||
| 4f6979f236 | |||
| 1404fa0bb7 | |||
| 7f65ec5b96 | |||
| 041908ddda | |||
| 57c9f139a2 | |||
| d3af99fcef | |||
| 94af318139 | |||
| 91f9a2085b | |||
| 00865e1c7b | |||
| 28bfd14362 | |||
| 6412a9a89e | |||
| f376c68115 | |||
| 3c6afa0322 | |||
| 384a2ece78 | |||
|
|
f4a7ab7373 | ||
| bc1153c97e | |||
| c6ffcd4804 | |||
| b3c844650b | |||
| 13bd299de4 | |||
| 1e3d183f9a | |||
| dfe596447b | |||
| 9297e82589 | |||
| 4319ee57a0 | |||
| 09e452a09d | |||
| 3c1e08372d | |||
| 05d6174351 | |||
| 9ab998688c | |||
| 7249b54127 | |||
| 4fa18a9642 | |||
|
|
8a294cb2be | ||
| 57e9834786 | |||
| 70c3650ee1 | |||
| e2c85aa03f | |||
|
|
ed285e5d24 | ||
| 0883ec4aae | |||
|
|
2eb64540d1 | ||
| bb4029a9bf | |||
| 25ce0f6d82 | |||
|
|
bab17eb23f | ||
|
|
aaaf03128a | ||
|
|
558d056bcf | ||
|
|
0accd1f345 | ||
|
|
bb12a60e04 | ||
|
|
8326ea6e26 | ||
| 81cc19f985 | |||
| 19fd4ecb83 | |||
|
|
243e4f6d50 | ||
|
|
6f59001be1 | ||
|
|
e5d9e7217e | ||
| 5c1bb0acc9 | |||
|
|
32daa68458 | ||
|
|
2d7aa3c536 | ||
|
|
eb8ec85746 | ||
|
|
a90ba9ea08 | ||
|
|
974b67d4b6 | ||
|
|
6ae4ab2cbf | ||
|
|
9fb41342b3 | ||
|
|
0ccc66208d | ||
|
|
ebd3b7b75a | ||
|
|
d41122157f | ||
|
|
917407f164 | ||
|
|
c393bf7af2 | ||
|
|
72b62cd5a4 | ||
|
|
e508a06eca | ||
|
|
f71d2581bd | ||
|
|
89a32ac9a4 | ||
|
|
c7e2e66a51 | ||
|
|
37b1d11948 | ||
|
|
c960b06227 | ||
|
|
2721f21427 | ||
|
|
d5e65fda3c | ||
|
|
5f29ea93d8 | ||
|
|
36cb32454d | ||
|
|
af8dd61967 | ||
|
|
26894c8a69 | ||
|
|
bf12ed4c47 | ||
|
|
59303ba30d | ||
|
|
b2a8b86e4c | ||
|
|
0c05fc4add | ||
|
|
c415db3fd3 | ||
|
|
183586afe4 | ||
|
|
e546d3948c | ||
|
|
a0e758f5b7 | ||
|
|
f58c6b72b3 | ||
|
|
438eba4aa8 | ||
|
|
805fd93b3c | ||
|
|
4bcf3af90f | ||
|
|
4eeb93ad51 | ||
|
|
e761c700dc | ||
|
|
14562f889e | ||
|
|
dd3066849b | ||
|
|
91dd5d5280 | ||
| 2658ed5900 | |||
|
|
1c3e1d83de | ||
|
|
c240cc005f | ||
| 33ea5af8b7 | |||
|
|
55f8dc94ce | ||
|
|
b99cab58f7 | ||
|
|
21e283bedb | ||
|
|
5517a358ea | ||
|
|
4eaf3b2f08 | ||
|
|
44fd66b511 | ||
|
|
001a0b8d4b | ||
|
|
20b05a72a0 | ||
|
|
78b87d0f61 | ||
|
|
527ca817d5 | ||
|
|
80b11b4364 | ||
|
|
6f636b77f2 | ||
|
|
8a871f7045 | ||
|
|
1779d69078 | ||
|
|
30de9f668c | ||
|
|
78b17aea72 | ||
|
|
aa028392f0 | ||
|
|
f0e21b5051 | ||
|
|
80d965944c | ||
|
|
2e34440d2e | ||
|
|
e9709805b7 | ||
|
|
fa7c88e214 | ||
|
|
588e941d3c | ||
|
|
f5600912cb | ||
|
|
fde4a5ff34 | ||
| 9b84d9dc4d | |||
| 48fb510b53 | |||
|
|
fd965fbf03 | ||
| 65462d01e3 | |||
| 3325397d17 | |||
| 22678fcfc2 | |||
| 0c8e209360 | |||
| 224ffe05e7 | |||
| 49f1cb0f00 | |||
|
|
a57953393d | ||
|
|
b428eb08e9 | ||
|
|
4a22449f5e | ||
| 7f9cdfc9ae | |||
| 8afe175bd1 | |||
| 0f687cc6b3 | |||
| 55b140f673 | |||
| 688f13e649 | |||
| ef7d0148c6 | |||
| 7c7d9f1be8 | |||
|
|
919c14be0d | ||
| 124ea33959 | |||
| 69266d1cda | |||
| 574f0cab09 | |||
| 610a683804 | |||
| a55a34e09a | |||
| e9bc2fe561 | |||
| cee09f6344 | |||
| 04c56ce426 | |||
| 2017b38e23 | |||
| c5776b9322 | |||
|
|
dd41d5b610 | ||
|
|
8981ad8c14 | ||
| df1ac82300 | |||
| ec67cc6527 | |||
| 2024fb4969 | |||
| ae279100d7 | |||
| e92a5a2f8b | |||
| ce197d7db3 | |||
| f9c2587557 | |||
| 2d7129111a | |||
| 4cd3a3dabd | |||
| f7e9cc2cea | |||
| 89eb0b140b | |||
| f5a34b9a3d | |||
| ffe5947925 | |||
| 0fbdb1dad2 | |||
| d781fae3ba | |||
| 3445c0bbc3 | |||
| 540c37a523 | |||
| 33a26cda7c | |||
| dc6de46a1f | |||
| c5e08b81da | |||
| 5e569d4324 | |||
| f2b1a84ef4 | |||
| d5fe1cc513 | |||
| 81c2a2a8b3 | |||
| f7c84c62ed | |||
| da8dde9818 | |||
|
|
29dbe83a0b | ||
| 5fdef50f0f | |||
|
|
b04c66dea7 | ||
|
|
f0f3eaf749 | ||
| 217168fe50 | |||
|
|
04a17ed862 | ||
| 74af1ad173 | |||
|
|
b0697f12a3 | ||
| 34bd7c102f | |||
|
|
9e30e55669 | ||
| 46b6d9fcc7 | |||
| 2bf3fd0975 | |||
| 442f85c7a6 | |||
| b87229aa65 | |||
| deca220d43 | |||
| 74e06f1084 | |||
| ddc7caf959 | |||
| 6baa0c5b07 | |||
| d4eaf83354 | |||
| 47d13ce39e | |||
| 2b95bc660c | |||
| 0e3fac4d34 | |||
|
|
89e4b2be22 | ||
|
|
28543641bb | ||
|
|
888f0e77f9 | ||
| 4516227a7b | |||
|
|
954cfabde5 | ||
| 52e3c3bcd7 | |||
| 966bd3edd2 | |||
| dab204ddaf | |||
| bad8ef1d10 | |||
| a12acbb68f | |||
| c78b3ca69f | |||
| 55431362a7 | |||
| 07a930fe1c | |||
| 4060bf25b0 | |||
| 9a79ab3927 | |||
| fc45775666 | |||
| 81d0964971 | |||
| 868dc8485e | |||
| 26f4767082 | |||
| d97444cca5 | |||
| ee978e8bc9 | |||
|
|
bdf283ae4f | ||
| f8129b91cb | |||
| afea9f436f | |||
| 7985fc475b | |||
| 959a708176 | |||
| 44fbb8e81f | |||
| e02ef55844 | |||
|
|
8bbf40ac95 | ||
|
|
7fe0e19bb0 | ||
|
|
82d5455da6 | ||
|
|
52389c2c69 | ||
|
|
ae75324153 | ||
|
|
a1a5815f1f | ||
|
|
772f80188e | ||
|
|
f28c643b48 | ||
|
|
5f82658c8d | ||
| ff75aa6693 | |||
| 50b2bdcb16 | |||
|
|
34dbb59fb1 | ||
|
|
a74c9be2ff | ||
| fcdea2e723 | |||
| 90191fd494 | |||
|
|
effbbd9a5e | ||
| bc7f4883a2 | |||
| e0808ffcbd | |||
| 9057f31bff | |||
| 0a9f637123 | |||
| 027f513a23 | |||
| a99f95d15f | |||
|
|
49158360ca | ||
| 0104bc9329 | |||
| 4fc5c092f8 | |||
| 8d1bc94b7e | |||
| 322e354f5b | |||
|
|
8a2073d081 | ||
|
|
e915c54ed0 | ||
|
|
e953b51229 | ||
|
|
b9013149f3 | ||
|
|
e6da454c73 | ||
|
|
598c7ce1d0 | ||
|
|
ea47f67b2b | ||
|
|
91ea8be72e | ||
|
|
b81a3761b6 | ||
|
|
45b8f8f581 | ||
|
|
cfc6a94c7a | ||
| c3d9883ddb | |||
|
|
ea12ec9607 | ||
|
|
2e173df2ca | ||
|
|
1fb48536c2 | ||
|
|
002bfffb62 | ||
|
|
140227406c | ||
| 26d0623d07 | |||
| f8a359dfe9 | |||
|
|
2f44243abb | ||
| e3561e9d15 | |||
| 80dabf4406 | |||
|
|
d3e5b9c485 | ||
| f7a6615379 | |||
|
|
618a8d0a9f | ||
| d25e7ca4fc | |||
| 3359ca0c3e | |||
| 0dd478b9f0 | |||
|
|
d03c4f109f | ||
|
|
792a317b48 | ||
| f74c40cf69 | |||
| 442d7853a5 | |||
|
|
f1aa2c19ef | ||
| fcd9b55ea7 | |||
|
|
b1375c69f5 | ||
|
|
51c3e01062 | ||
|
|
f1a2723274 | ||
|
|
5431d21d27 | ||
|
|
57c28d7539 | ||
| 6937ff86a4 | |||
| 454e0284af | |||
|
|
94b7864b02 | ||
|
|
166bfdfc4f | ||
|
|
156e9bf902 | ||
|
|
c8ff130b78 | ||
|
|
ec23e72215 | ||
|
|
a1274756ce | ||
|
|
05f4746af4 | ||
|
|
04112377ea | ||
|
|
bd3a91e530 | ||
|
|
66ecc45fce | ||
|
|
7aef973ef1 | ||
|
|
a12faac0e2 | ||
|
|
4dc48198ab | ||
|
|
3e855cbdac | ||
|
|
889fc985c4 | ||
|
|
c709d458f4 | ||
|
|
0e016646ef | ||
|
|
960f2dd10c | ||
|
|
2bad1252d3 | ||
|
|
31ac9d2e3b | ||
|
|
eeb3b1ee61 | ||
|
|
4bf35e5239 | ||
|
|
2f5c2a8764 | ||
|
|
34ca36062c | ||
|
|
a1062f220b | ||
|
|
c2f640fd97 | ||
|
|
143bb00ac0 | ||
|
|
b947ff65cf | ||
| 1474ff10ac | |||
| 974941f4fa | |||
|
|
2834891727 | ||
| ada8af0b82 | |||
| 86e0b1bc13 | |||
|
|
d5409ed3f1 | ||
| 811c9d4d5c | |||
|
|
a48fb3a8e8 | ||
| 27f396af7e | |||
| 8ebabac7c0 | |||
| 02c5df5c1b | |||
| dd6fbfb683 | |||
| 92307063fc | |||
| 53d8cb67ba | |||
| 6b257887b7 | |||
| ec7cba644c | |||
| 269315ca09 | |||
| 661061b4eb | |||
| 3a2b488f33 | |||
| 721b3f440f | |||
|
|
b04c377d7c | ||
|
|
794856a63c | ||
|
|
856a90abcf | ||
| 88d904e302 | |||
| 46552307cd | |||
| 26024de866 | |||
|
|
85dcdd4cc5 | ||
|
|
6e7129bf6f | ||
|
|
1ca8094628 | ||
|
|
543d0a7afd | ||
|
|
4c398895aa | ||
| e899928928 | |||
| c8b011913a | |||
| 15b63f3cbd | |||
| ce1a5a7664 | |||
| c6112d04da | |||
|
|
73f8179836 | ||
|
|
86f17b6525 | ||
| fed3eca378 | |||
| 840cba6a9e | |||
| fb9c4140b6 | |||
| 657061083e | |||
| 8a86c75747 | |||
| 046e7bb6f1 | |||
| 1e822f7a6c | |||
| c43a3d64fb | |||
|
|
7c5d7a70cc | ||
|
|
c1941654b6 | ||
|
|
d2d6171d04 | ||
| 58952be47e | |||
| 5771cd014a | |||
|
|
c9754902b9 | ||
|
|
c508016ca3 | ||
|
|
4863d29590 | ||
|
|
128aad89d3 | ||
| 858fbf40be | |||
|
|
1d7f8bd133 | ||
| a9ad399bc2 | |||
| 963205fad6 | |||
| b836952356 | |||
| 6fb652f642 | |||
| 2cde3e34f6 | |||
| 1371a6da77 | |||
| d2e89851d6 | |||
|
|
7cc0a0fc82 | ||
| c343092bd5 | |||
|
|
36d6231da4 | ||
|
|
7851866d13 | ||
|
|
c2ab5c9d02 | ||
| 166eb5125f | |||
| 26ca4d8671 | |||
| 28276d2229 | |||
| 2b25b17bd8 | |||
| f7c0596a4c | |||
| fe7ec44554 | |||
| 299fc1b461 | |||
| 2acb90f2d2 | |||
|
|
75d33aff3e | ||
|
|
8073c68bd5 | ||
|
|
15dd0a2fc3 | ||
|
|
71d1212877 | ||
|
|
57060e9b6f | ||
|
|
faf2a0ee7d | ||
|
|
fa7b6e6145 | ||
|
|
478bcd5338 | ||
|
|
c7d3870a60 | ||
|
|
8351596763 | ||
|
|
e3568ea506 | ||
|
|
a9a8d96321 | ||
|
|
b0aea27418 | ||
|
|
872425640d | ||
|
|
ea5b7104be | ||
|
|
d85d9d4b12 | ||
|
|
7e08bc60ce | ||
|
|
86e6937342 | ||
|
|
ec8abfa004 | ||
|
|
51a687c7db | ||
|
|
657306e0a1 | ||
|
|
eee8aab888 | ||
|
|
5c6d7d6055 | ||
|
|
4760d91ccd | ||
|
|
db98af9775 | ||
|
|
f91f9786d1 | ||
|
|
71d9cab72e | ||
|
|
717e5eb78f | ||
|
|
e7301f46ef | ||
|
|
85113a667f | ||
|
|
98ae236c08 | ||
|
|
6ff762ba57 | ||
|
|
73ef5fa5ff | ||
|
|
7d81035306 | ||
|
|
8a0d0e0e4c | ||
|
|
0f1d9cdf1c | ||
|
|
07a1927b9f | ||
|
|
e0858a42b2 | ||
|
|
dab536cb6a | ||
| f1d04cec01 | |||
| 5e8b6eec6e | |||
| 883a1c17e6 | |||
|
|
e9ffbe6148 | ||
|
|
d1baa0d9f9 | ||
|
|
71d4f331b5 | ||
|
|
665f28313a | ||
|
|
c5fa397724 | ||
|
|
95b385dee1 | ||
|
|
82b505a737 | ||
|
|
dae3b24093 | ||
|
|
e50a4c2edd | ||
|
|
5cbb8538c0 | ||
|
|
d2e776b672 | ||
|
|
b08fb52272 | ||
|
|
0075457f81 | ||
|
|
12baece1b2 | ||
|
|
eaeeae8d62 | ||
|
|
c77f99e849 | ||
|
|
d17890af68 | ||
|
|
6c1d8c3527 | ||
|
|
59ab81d3c6 | ||
|
|
9cee8f2c87 | ||
|
|
a483b0fb44 | ||
|
|
8ad3f3aabd | ||
|
|
b22ba781c8 | ||
|
|
cc74b01f72 | ||
|
|
4d1d77ceaf | ||
|
|
7135767cc4 | ||
|
|
0ecf76c5ec | ||
|
|
9087baae3c | ||
|
|
95b5da8932 | ||
|
|
f733aa60f0 | ||
|
|
d102fabc2f | ||
|
|
6d4afd0c6a | ||
|
|
8003518e18 | ||
|
|
859b24229a | ||
|
|
674d5112f3 | ||
|
|
de99522340 | ||
|
|
c7006b46ed | ||
|
|
45ed99ae35 | ||
|
|
f2ff4c7f4d | ||
| 2744ed64e3 | |||
|
|
90754665a0 | ||
|
|
4454faffc3 | ||
|
|
34f2aef248 | ||
|
|
121c65d7ea | ||
|
|
f53de8d521 | ||
|
|
8a68958ed4 | ||
|
|
f0ded0886c | ||
|
|
d7a8d2d453 | ||
|
|
5293573116 | ||
|
|
d505b09e98 | ||
|
|
8f90fdaac4 | ||
|
|
3a55a9b66f | ||
|
|
36e9370ff4 | ||
|
|
3f5a9c8811 | ||
|
|
30de5c1734 | ||
|
|
c7591c6ce2 | ||
|
|
7be79010fc | ||
|
|
803f1a246a | ||
|
|
262dca7dd6 | ||
|
|
26460c0167 | ||
| 83c378c9c2 | |||
| a1145aced4 | |||
| 1a405034af | |||
| d21f727e9d | |||
|
|
7b4fab8c6b | ||
|
|
2af33da4e8 | ||
|
|
f3b9ecd791 | ||
|
|
609d3cdb99 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
version.h
|
||||
@@ -1,23 +1,23 @@
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# 4 space indentation
|
||||
[*.cpp,*.hpp]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Tabs in makefile
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
# Don't enforce anything in contrib
|
||||
[/src/contrib/**]
|
||||
end_of_line = unset
|
||||
insert_final_newline = unset
|
||||
indent_style = unset
|
||||
indent_style = unset
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# 4 space indentation
|
||||
[*.cpp,*.hpp]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Tabs in makefile
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
# Don't enforce anything in vendored code
|
||||
[/vendor/**]
|
||||
end_of_line = unset
|
||||
insert_final_newline = unset
|
||||
indent_style = unset
|
||||
indent_style = unset
|
||||
|
||||
6
.gitattributes
vendored
6
.gitattributes
vendored
@@ -1 +1,5 @@
|
||||
src/contrib/* linguist-vendored
|
||||
vendor/* linguist-vendored
|
||||
|
||||
# Always checkout source with LF line endings
|
||||
src/*.c text eol=lf
|
||||
src/*.h text eol=lf
|
||||
|
||||
147
.github/workflows/check-builds.yaml
vendored
Normal file
147
.github/workflows/check-builds.yaml
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
name: Check Builds
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- src/**
|
||||
- vendor/**
|
||||
- .github/workflows/check-builds.yaml
|
||||
- CMakeLists.txt
|
||||
- Makefile
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths:
|
||||
- src/**
|
||||
- vendor/**
|
||||
- CMakeLists.txt
|
||||
- Makefile
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set environment
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- name: Install dependencies
|
||||
run: sudo apt install clang cmake snap -y && sudo snap install powershell --classic
|
||||
- name: Check compilation
|
||||
run: |
|
||||
$versions = "104", "728", "1013"
|
||||
|
||||
foreach ($version in $versions) {
|
||||
Write-Output "Cleaning old output"
|
||||
Invoke-Expression "make clean"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "make clean failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Write-Output "Building version $version"
|
||||
Invoke-Expression "make -j8 PROTOCOL_VERSION=$version"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "make failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Rename-Item -Path "bin/fusion" -newName "$version-fusion"
|
||||
Write-Output "Built version $version"
|
||||
}
|
||||
Copy-Item -Path "sql" -Destination "bin/sql" -Recurse
|
||||
Copy-Item -Path "config.ini" -Destination "bin"
|
||||
shell: pwsh
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: 'ubuntu22_04-bin-x64-${{ env.SHORT_SHA }}'
|
||||
path: bin
|
||||
|
||||
windows-build:
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- name: Set environment
|
||||
run: $s = $env:GITHUB_SHA.subString(0, 7); echo "SHORT_SHA=$s" >> $env:GITHUB_ENV
|
||||
shell: pwsh
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- name: Check compilation
|
||||
run: |
|
||||
$versions = "104", "728", "1013"
|
||||
$configurations = "Release"
|
||||
# "Debug" builds are disabled, since we don't really need them
|
||||
|
||||
$vsPath = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise"
|
||||
|
||||
Import-Module "$vsPath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
|
||||
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation
|
||||
|
||||
Invoke-Expression "vcpkg install sqlite3:x64-windows"
|
||||
Invoke-Expression "vcpkg integrate install"
|
||||
|
||||
foreach ($version in $versions) {
|
||||
if (Test-Path -LiteralPath "build") {
|
||||
Remove-Item "build" -Recurse
|
||||
Write-Output "Deleted existing build folder"
|
||||
}
|
||||
Invoke-Expression "cmake -B build -DPROTOCOL_VERSION=$version -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "cmake generation failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Write-Output "Generated build files for version $version"
|
||||
|
||||
foreach ($configuration in $configurations) {
|
||||
Write-Output "Building version $version $configuration"
|
||||
Invoke-Expression "msbuild build\OpenFusion.sln /maxcpucount:8 /p:BuildInParallel=true /p:CL_MPCount=8 /p:UseMultiToolTask=true /p:Configuration=$configuration"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "msbuild build failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Rename-Item -Path "bin/$configuration" -newName "$version-$configuration"
|
||||
Write-Output "Built version $version $configuration"
|
||||
Copy-Item -Path "sql" -Destination "bin/$version-$configuration/sql" -Recurse
|
||||
Copy-Item -Path "config.ini" -Destination "bin/$version-$configuration"
|
||||
}
|
||||
}
|
||||
shell: pwsh
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: 'windows-vs2019-bin-x64-${{ env.SHORT_SHA }}'
|
||||
path: bin
|
||||
|
||||
copy-artifacts:
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [windows-build, ubuntu-build]
|
||||
env:
|
||||
BOT_SSH_KEY: ${{ secrets.BOT_SSH_KEY }}
|
||||
ENDPOINT: ${{ secrets.ENDPOINT }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- run: |
|
||||
GITDESC=$(git describe --tags)
|
||||
mkdir $GITDESC
|
||||
echo "ARTDIR=$GITDESC" >> $GITHUB_ENV
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: ${{ env.ARTDIR }}
|
||||
- name: Upload artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt install zip -y
|
||||
cd $ARTDIR
|
||||
for build in *; do
|
||||
cd $build
|
||||
zip -r ../$build.zip *
|
||||
cd ..
|
||||
rm -r $build
|
||||
done
|
||||
cd ..
|
||||
umask 077
|
||||
printf %s "$BOT_SSH_KEY" > cdn_key
|
||||
scp -i cdn_key -o StrictHostKeyChecking=no -r $ARTDIR $ENDPOINT
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,6 +11,10 @@ build/
|
||||
.vs/
|
||||
.idea/
|
||||
*.db
|
||||
*-shm
|
||||
*-wal
|
||||
version.h
|
||||
infer-out
|
||||
gmon.out
|
||||
*.bak
|
||||
|
||||
|
||||
26
.vscode/launch.json
vendored
Normal file
26
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug (Linux)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/bin/fusion",
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Debug (Windows)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/bin/Debug/winfusion.exe",
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Release (Windows)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/bin/Release/winfusion.exe",
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,7 +3,8 @@ project(OpenFusion)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
|
||||
# OpenFusion supports multiple packet/struct versions
|
||||
# 104 is the default version to build which can be changed
|
||||
@@ -33,9 +34,9 @@ else()
|
||||
set(BIN_NAME fusion)
|
||||
endif()
|
||||
|
||||
include_directories(src)
|
||||
include_directories(src vendor)
|
||||
|
||||
file(GLOB_RECURSE SOURCES src/**.cpp src/**.hpp src/**.c src/**.h version.h)
|
||||
file(GLOB_RECURSE SOURCES src/**.[ch]pp vendor/**.[ch]pp vendor/**.[ch] version.h)
|
||||
|
||||
configure_file(version.h.in ${CMAKE_SOURCE_DIR}/version.h @ONLY)
|
||||
|
||||
@@ -43,11 +44,18 @@ add_executable(openfusion ${SOURCES})
|
||||
|
||||
set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME})
|
||||
|
||||
# find sqlite3 and use it
|
||||
find_package(SQLite3 REQUIRED)
|
||||
target_include_directories(openfusion PRIVATE ${SQLite3_INCLUDE_DIRS})
|
||||
target_link_libraries(openfusion PRIVATE ${SQLite3_LIBRARIES})
|
||||
|
||||
# Makes it so config, tdata, etc. get picked up when starting via the debugger in VS
|
||||
set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
|
||||
# Use pthreads if not generating a VS solution or MinGW makefile (because MinGW will prefer Win32 threads)
|
||||
# Checking if the compiler ID is MSVC will allow us to open the project as a CMake project in VS.
|
||||
# It's not something you should do, but it's there if you need it...
|
||||
if (NOT CMAKE_GENERATOR MATCHES "Visual Studio" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_GENERATOR MATCHES "MinGW Makefiles")
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(openfusion pthread)
|
||||
target_link_libraries(openfusion dl)
|
||||
target_link_libraries(openfusion PRIVATE pthread)
|
||||
endif()
|
||||
|
||||
@@ -26,7 +26,7 @@ Both are pretty short reads and following them will get you up to speed with bra
|
||||
|
||||
I will now cover a few examples of the complications people have encountered contributing to this project, how to understand them, overcome them and henceforth avoid them.
|
||||
|
||||
## Dirty pull requests
|
||||
### Dirty pull requests
|
||||
|
||||
Many Pull Requests OpenFusion receives fail to present a clean set of commits to merge.
|
||||
These are generally either:
|
||||
@@ -44,7 +44,7 @@ If you read the above links, you'll note that this isn't exactly a perfect solut
|
||||
The obvious issue, then, is that the people submitting dirty PRs are the exact people who don't *know* how to rebase their fork to continue submitting their work cleanly.
|
||||
So they end up creating countless merge commits when pulling upstream on top of their own incompatible histories, and then submitting those merge commits in their PRs and the cycle continues.
|
||||
|
||||
## The details
|
||||
### The details
|
||||
|
||||
A git commit is uniquely identified by its SHA1 hash.
|
||||
Its hash is generated from its contents, as well as the hash(es) of its parent commit(s).
|
||||
@@ -52,7 +52,7 @@ Its hash is generated from its contents, as well as the hash(es) of its parent c
|
||||
That means that even if two commits are exactly the same in terms of content, they're not the same commit if their parent commits differ (ex. if they've been rebased).
|
||||
So if you keep issuing `git pull`s after upstream has merged a rebased version of your PR, you will never re-synchronize with it, and will instead construct an alternate history polluted by pointless merge commits.
|
||||
|
||||
## The solution
|
||||
### The solution
|
||||
|
||||
If you already have a messed-up fork and you have no changes on it that you're afraid to lose, the solution is simple:
|
||||
|
||||
@@ -67,7 +67,7 @@ If you do have some committed changes that haven't yet been merged upstream, you
|
||||
If you do end up messing something up, don't worry, it most likely isn't really lost and `git reflog` is your friend.
|
||||
(You can checkout an arbitrary commit, and make it into its own branch with `git checkout -b BRANCH` or set a pre-exisitng branch to it with `git reset --hard COMMIT`)
|
||||
|
||||
## Avoiding the problem
|
||||
### Avoiding the problem
|
||||
|
||||
When working on a changeset you want to submit back upstream, don't do it on the main branch.
|
||||
Create a work branch just for your changeset with `git checkout -b work`.
|
||||
@@ -81,3 +81,34 @@ That way you can always keep master in sync with upstream with `git pull --ff-on
|
||||
Creating new branches for the rebase isn't strictly necessary since you can always return a branch to its previous state with `git reflog`.
|
||||
|
||||
For moving uncommited changes around between branches, `git stash` is a real blessing.
|
||||
|
||||
## Code guidelines
|
||||
|
||||
Alright, you're up to speed on Git and ready to go. Here are a few specific code guidelines to try and follow:
|
||||
|
||||
### Match the styling
|
||||
|
||||
Pretty straightforward, make sure your code looks similar to the code around it. Match whitespacing, bracket styling, variable naming conventions, etc.
|
||||
|
||||
### Prefer short-circuiting
|
||||
|
||||
To minimize branching complexity (as this makes the code hard to read), we prefer to keep the number of `if-else` statements as low as possible. One easy way to achieve this is by doing an early return after branching into an `if` block and then writing the code for the other path outside the block entirely. You can find examples of this in practically every source file. Note that in a few select situations, this might actually make your code less elegant, which is why this isn't a strict rule. Lean towards short-circuiting and use your better judgement.
|
||||
|
||||
### Follow the include convention
|
||||
|
||||
This one matters a lot as it can cause cyclic dependencies and other code-breaking issues.
|
||||
|
||||
FOR HEADER FILES (.hpp):
|
||||
- everything you use IN THE HEADER must be EXPLICITLY INCLUDED with the exception of things that fall under Core.hpp
|
||||
- you may NOT include ANYTHING ELSE
|
||||
|
||||
FOR SOURCE FILES (.cpp):
|
||||
- you can #include whatever you want as long as the partner header is included first
|
||||
- anything that gets included by another include is fair game
|
||||
- redundant includes are ok because they'll be harmless AS LONG AS our header files stay lean.
|
||||
|
||||
The point of this is NOT to optimize the number of includes used all around or make things more efficient necessarily. it's to improve readability & coherence and make it easier to avoid cyclical issues.
|
||||
|
||||
## When in doubt, ask
|
||||
|
||||
If you still have questions that were not answered here, feel free to ping a dev in the Discord server or on GitHub.
|
||||
|
||||
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
FROM debian:latest
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN apt-get -y update && apt-get install -y \
|
||||
git \
|
||||
clang \
|
||||
make \
|
||||
libsqlite3-dev
|
||||
|
||||
COPY . ./
|
||||
|
||||
RUN make -j8
|
||||
|
||||
# tabledata should be copied from the host;
|
||||
# clone it there before building the container
|
||||
#RUN git submodule update --init --recursive
|
||||
|
||||
CMD ["./bin/fusion"]
|
||||
|
||||
LABEL Name=openfusion Version=0.0.1
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Seth Stubbs
|
||||
Copyright (c) 2020-2023 OpenFusion Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
159
Makefile
159
Makefile
@@ -1,10 +1,12 @@
|
||||
GIT_VERSION!=git describe --tags
|
||||
|
||||
CC=clang
|
||||
CXX=clang++
|
||||
# -w suppresses all warnings (the part that's commented out helps me find memory leaks, it ruins performance though!)
|
||||
# If compiling with ASAN, invoke like this: $ LSAN_OPTIONS=suppressions=suppr.txt bin/fusion
|
||||
CFLAGS=-O3 #-g3 -fsanitize=address
|
||||
CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O2 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(shell git describe --tags)\" #-g3 -fsanitize=address
|
||||
LDFLAGS=-lpthread -ldl #-g3 -fsanitize=address
|
||||
CFLAGS=-Wall -Wno-unknown-pragmas -O2 -fPIE -D_FORTIFY_SOURCE=1 -fstack-protector #-g3 -fsanitize=address
|
||||
CXXFLAGS=$(CFLAGS) -std=c++17 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor
|
||||
LDFLAGS=-lpthread -lsqlite3 -pie -Wl,-z,relro -Wl,-z,now #-g3 -fsanitize=address
|
||||
# specifies the name of our exectuable
|
||||
SERVER=bin/fusion
|
||||
|
||||
@@ -15,79 +17,113 @@ PROTOCOL_VERSION?=104
|
||||
# Windows-specific
|
||||
WIN_CC=x86_64-w64-mingw32-gcc
|
||||
WIN_CXX=x86_64-w64-mingw32-g++
|
||||
WIN_CFLAGS=-O3 #-g3 -fsanitize=address
|
||||
WIN_CXX_VANILLA_MINGW_OPT_DISABLES=-fno-tree-dce -fno-inline-small-functions
|
||||
WIN_CXX_MSYS2_MINGW_OPT_DISABLES=-fno-tree-dce -fno-tree-fre -fno-tree-vrp -fno-ipa-sra
|
||||
WIN_CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O3 $(WIN_CXX_OPT_DISABLES) -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(shell git describe --tags)\" #-g3 -fsanitize=address
|
||||
WIN_LDFLAGS=-static -lws2_32 -lwsock32 #-g3 -fsanitize=address
|
||||
WIN_CFLAGS=-O2 -D_WIN32_WINNT=0x0601 -Wall -Wno-unknown-pragmas
|
||||
WIN_CXXFLAGS=$(WIN_CFLAGS) -std=c++17 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor
|
||||
WIN_LDFLAGS=-static -lws2_32 -lwsock32 -lsqlite3
|
||||
WIN_SERVER=bin/winfusion.exe
|
||||
|
||||
# C code; currently exclusively from vendored libraries
|
||||
CSRC=\
|
||||
src/contrib/sqlite/sqlite3.c\
|
||||
src/contrib/bcrypt/bcrypt.c\
|
||||
src/contrib/bcrypt/crypt_blowfish.c\
|
||||
src/contrib/bcrypt/crypt_gensalt.c\
|
||||
src/contrib/bcrypt/wrapper.c\
|
||||
vendor/bcrypt/bcrypt.c\
|
||||
vendor/bcrypt/crypt_blowfish.c\
|
||||
vendor/bcrypt/crypt_gensalt.c\
|
||||
vendor/bcrypt/wrapper.c\
|
||||
|
||||
CHDR=\
|
||||
vendor/bcrypt/bcrypt.h\
|
||||
vendor/bcrypt/crypt_blowfish.h\
|
||||
vendor/bcrypt/crypt_gensalt.h\
|
||||
vendor/bcrypt/ow-crypt.h\
|
||||
vendor/bcrypt/winbcrypt.h\
|
||||
|
||||
CXXSRC=\
|
||||
src/ChatManager.cpp\
|
||||
src/CNLoginServer.cpp\
|
||||
src/CNProtocol.cpp\
|
||||
src/CNShardServer.cpp\
|
||||
src/CNShared.cpp\
|
||||
src/Database.cpp\
|
||||
src/Defines.cpp\
|
||||
src/core/CNProtocol.cpp\
|
||||
src/core/CNShared.cpp\
|
||||
src/core/Packets.cpp\
|
||||
src/servers/CNLoginServer.cpp\
|
||||
src/servers/CNShardServer.cpp\
|
||||
src/servers/Monitor.cpp\
|
||||
src/db/init.cpp\
|
||||
src/db/login.cpp\
|
||||
src/db/shard.cpp\
|
||||
src/db/player.cpp\
|
||||
src/db/email.cpp\
|
||||
src/sandbox/seccomp.cpp\
|
||||
src/sandbox/openbsd.cpp\
|
||||
src/Buffs.cpp\
|
||||
src/Chat.cpp\
|
||||
src/CustomCommands.cpp\
|
||||
src/Entities.cpp\
|
||||
src/Email.cpp\
|
||||
src/Eggs.cpp\
|
||||
src/main.cpp\
|
||||
src/MissionManager.cpp\
|
||||
src/MobManager.cpp\
|
||||
src/NanoManager.cpp\
|
||||
src/ItemManager.cpp\
|
||||
src/Missions.cpp\
|
||||
src/MobAI.cpp\
|
||||
src/Combat.cpp\
|
||||
src/Nanos.cpp\
|
||||
src/Abilities.cpp\
|
||||
src/Items.cpp\
|
||||
src/NPCManager.cpp\
|
||||
src/PlayerManager.cpp\
|
||||
src/PlayerMovement.cpp\
|
||||
src/BuiltinCommands.cpp\
|
||||
src/settings.cpp\
|
||||
src/TransportManager.cpp\
|
||||
src/Transport.cpp\
|
||||
src/TableData.cpp\
|
||||
src/ChunkManager.cpp\
|
||||
src/BuddyManager.cpp\
|
||||
src/GroupManager.cpp\
|
||||
src/Chunking.cpp\
|
||||
src/Buddies.cpp\
|
||||
src/Groups.cpp\
|
||||
src/Racing.cpp\
|
||||
src/Vendors.cpp\
|
||||
src/Trading.cpp\
|
||||
src/Rand.cpp\
|
||||
|
||||
# headers (for timestamp purposes)
|
||||
CHDR=\
|
||||
src/contrib/sqlite/sqlite3.h\
|
||||
src/contrib/sqlite/sqlite_orm.h\
|
||||
src/contrib/bcrypt/bcrypt.h\
|
||||
src/contrib/bcrypt/crypt_blowfish.h\
|
||||
src/contrib/bcrypt/crypt_gensalt.h\
|
||||
src/contrib/bcrypt/ow-crypt.h\
|
||||
src/contrib/bcrypt/winbcrypt.h\
|
||||
|
||||
CXXHDR=\
|
||||
src/contrib/bcrypt/BCrypt.hpp\
|
||||
src/contrib/INIReader.hpp\
|
||||
src/contrib/JSON.hpp\
|
||||
src/ChatManager.hpp\
|
||||
src/CNLoginServer.hpp\
|
||||
src/CNProtocol.hpp\
|
||||
src/CNShardServer.hpp\
|
||||
src/CNShared.hpp\
|
||||
src/CNStructs.hpp\
|
||||
src/Database.hpp\
|
||||
src/Defines.hpp\
|
||||
src/contrib/INIReader.hpp\
|
||||
src/contrib/JSON.hpp\
|
||||
src/MissionManager.hpp\
|
||||
src/MobManager.hpp\
|
||||
src/NanoManager.hpp\
|
||||
src/ItemManager.hpp\
|
||||
src/core/CNProtocol.hpp\
|
||||
src/core/CNShared.hpp\
|
||||
src/core/CNStructs.hpp\
|
||||
src/core/Packets.hpp\
|
||||
src/core/Defines.hpp\
|
||||
src/core/Core.hpp\
|
||||
src/servers/CNLoginServer.hpp\
|
||||
src/servers/CNShardServer.hpp\
|
||||
src/servers/Monitor.hpp\
|
||||
src/db/Database.hpp\
|
||||
src/db/internal.hpp\
|
||||
src/sandbox/Sandbox.hpp\
|
||||
vendor/bcrypt/BCrypt.hpp\
|
||||
vendor/INIReader.hpp\
|
||||
vendor/JSON.hpp\
|
||||
vendor/INIReader.hpp\
|
||||
vendor/JSON.hpp\
|
||||
src/Buffs.hpp\
|
||||
src/Chat.hpp\
|
||||
src/CustomCommands.hpp\
|
||||
src/Entities.hpp\
|
||||
src/Email.hpp\
|
||||
src/Eggs.hpp\
|
||||
src/Missions.hpp\
|
||||
src/MobAI.hpp\
|
||||
src/Combat.hpp\
|
||||
src/Nanos.hpp\
|
||||
src/Abilities.hpp\
|
||||
src/Items.hpp\
|
||||
src/NPCManager.hpp\
|
||||
src/Player.hpp\
|
||||
src/PlayerManager.hpp\
|
||||
src/PlayerMovement.hpp\
|
||||
src/BuiltinCommands.hpp\
|
||||
src/settings.hpp\
|
||||
src/TransportManager.hpp\
|
||||
src/Transport.hpp\
|
||||
src/TableData.hpp\
|
||||
src/ChunkManager.hpp\
|
||||
src/BuddyManager.hpp\
|
||||
src/GroupManager.hpp\
|
||||
src/Chunking.hpp\
|
||||
src/Buddies.hpp\
|
||||
src/Groups.hpp\
|
||||
src/Racing.hpp\
|
||||
src/Vendors.hpp\
|
||||
src/Trading.hpp\
|
||||
src/Rand.hpp\
|
||||
|
||||
COBJ=$(CSRC:.c=.o)
|
||||
CXXOBJ=$(CXXSRC:.cpp=.o)
|
||||
@@ -107,9 +143,8 @@ windows : CFLAGS=$(WIN_CFLAGS)
|
||||
windows : CXXFLAGS=$(WIN_CXXFLAGS)
|
||||
windows : LDFLAGS=$(WIN_LDFLAGS)
|
||||
windows : SERVER=$(WIN_SERVER)
|
||||
windows : WIN_CXX_OPT_DISABLES=$(if $(filter-out 10, $(shell $(WIN_CXX) -dumpversion | egrep -o ^[0-9]+)), $(WIN_CXX_VANILLA_MINGW_OPT_DISABLES), $(WIN_CXX_MSYS2_MINGW_OPT_DISABLES))
|
||||
|
||||
.SUFFIX: .o .c .cpp .h .hpp
|
||||
.SUFFIXES: .o .c .cpp .h .hpp
|
||||
|
||||
.c.o:
|
||||
$(CC) -c $(CFLAGS) -o $@ $<
|
||||
@@ -118,7 +153,7 @@ windows : WIN_CXX_OPT_DISABLES=$(if $(filter-out 10, $(shell $(WIN_CXX) -dumpver
|
||||
$(CXX) -c $(CXXFLAGS) -o $@ $<
|
||||
|
||||
# header timestamps are a prerequisite for OF object files
|
||||
$(CXXOBJ): $(CXXHDR)
|
||||
$(CXXOBJ): $(HDR)
|
||||
|
||||
$(SERVER): $(OBJ) $(CHDR) $(CXXHDR)
|
||||
mkdir -p bin
|
||||
@@ -135,7 +170,7 @@ src/main.o: version.h
|
||||
# only gets rid of OpenFusion objects, so we don't need to
|
||||
# recompile the libs every time
|
||||
clean:
|
||||
rm -f src/*.o $(SERVER) $(WIN_SERVER) version.h
|
||||
rm -f src/*.o src/*/*.o $(SERVER) $(WIN_SERVER) version.h
|
||||
|
||||
# gets rid of all compiled objects, including the libraries
|
||||
nuke:
|
||||
|
||||
73
README.md
73
README.md
@@ -1,27 +1,43 @@
|
||||

|
||||
<p align="center"><img width="640" src="res/openfusion-hero.png" alt=""></p>
|
||||
|
||||
[](https://ci.appveyor.com/project/OpenFusionProject/openfusion)
|
||||
[](https://discord.gg/DYavckB)
|
||||
<p align="center">
|
||||
<a href="https://github.com/OpenFusionProject/OpenFusion/releases/latest"><img src="https://img.shields.io/github/v/release/OpenFusionProject/OpenFusion" alt="Current Release"></a>
|
||||
<a href="https://github.com/OpenFusionProject/OpenFusion/actions/workflows/check-builds.yaml"><img src="https://github.com/OpenFusionProject/OpenFusion/actions/workflows/check-builds.yaml/badge.svg" alt="Workflow"></a>
|
||||
<a href="https://discord.gg/DYavckB"><img src="https://img.shields.io/badge/chat-on%20discord-7289da.svg?logo=discord" alt="Discord"></a>
|
||||
<a href="https://github.com/OpenFusionProject/OpenFusion/blob/master/LICENSE.md"><img src="https://img.shields.io/github/license/OpenFusionProject/OpenFusion" alt="License"></a>
|
||||
</p>
|
||||
|
||||
OpenFusion is a reverse-engineered server for FusionFall. It currently primarily targets version `beta-20100104` and has some support for version `beta-20100728` of the original game.
|
||||
|
||||
Further documentation pending.
|
||||
OpenFusion is a reverse-engineered server for FusionFall. It primarily targets versions `beta-20100104` and `beta-20111013` of the original game, with [limited support](https://github.com/OpenFusionProject/OpenFusion/wiki/FusionFall-Version-Support) for others.
|
||||
|
||||
## Usage
|
||||
|
||||
tl;dr:
|
||||
### Getting Started
|
||||
#### Method A: Installer (Easiest)
|
||||
1. Download the client installer by clicking [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.5/OpenFusionClient-1.5-Installer.exe) - choose to run the file.
|
||||
2. After a few moments, the client should open: you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
|
||||
3. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
||||
4. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 3.
|
||||
|
||||
1. Download the client+server bundle from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.1/OpenFusion.zip).
|
||||
2. Run `FreeClient/installUnity.bat` once
|
||||
#### Method B: Standalone .zip file
|
||||
1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.5/OpenFusionClient-1.5.zip).
|
||||
2. Extract it to a folder of your choice. Note: if you are upgrading from an older version, it is preferable to start with a fresh folder rather than overwriting a previous install.
|
||||
3. Run OpenFusionClient.exe - you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
|
||||
4. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
||||
5. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 4.
|
||||
|
||||
From then on, any time you want to run the "game":
|
||||
Instructions for getting the client to run on Linux through Wine can be found [here](https://github.com/OpenFusionProject/OpenFusion/wiki/Running-the-game-client-on-Linux).
|
||||
|
||||
3. Run `OpenFusion/winfusion.exe` (optional if you're using the public server)
|
||||
4. Run `FreeClient/OpenFusionClient.exe`
|
||||
### Hosting a server
|
||||
|
||||
Currently the client by default connects to a public server hosted by Cake. Change the loginInfo.php to point to your own server if you want to host your own.
|
||||
1. Grab `OpenFusionServer-1.5-original.zip` or `OpenFusionServer-1.5-academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.5).
|
||||
2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server.
|
||||
3. Add a new server to the client's list:
|
||||
1. For Description, enter anything you want. This is what will show up in the server list.
|
||||
2. For Server IP, enter the IP address and port of the login server. If you're hosting and playing on the same PC, this would be `127.0.0.1:23000`.
|
||||
3. Lastly Game Version - select `beta-20100104` if you downloaded the original zip, or `beta-20111013` if you downloaded the academy zip.
|
||||
5. Once you've added the server to the list, connect to it and log in. If you're having trouble with this, refer to steps 4 and 5 from the previous section.
|
||||
|
||||
If you want, [compiled binaries (artifacts) for each new commit can be found on AppVeyor.](https://ci.appveyor.com/project/OpenFusionProject/openfusion)
|
||||
If you want to run the latest development builds of the server, [compiled binaries (artifacts) for each functional commit can be found here.](http://cdn.dexlabs.systems/of-builds/)
|
||||
|
||||
For a more detailed overview of the game's architecture and how to configure it, read the following sections.
|
||||
|
||||
@@ -40,7 +56,7 @@ The original game made use of the player's actual web browser to launch the game
|
||||
|
||||
The browser/Electron client opens a web page with an `<embed>` tag of MIME type `application/vnd.unity`, where the `src` param is the address of the game's `.unity3d` entrypoint.
|
||||
|
||||
This triggers the browser to load an NPAPI plugin that handles this MIME type, the Unity Web Player, which the browser looks for in `C:\Users\USERNAME\AppData\LocalLow\Unity\WebPlayer`.
|
||||
This triggers the browser to load an NPAPI plugin that handles this MIME type, the Unity Web Player, which the browser looks for in `C:\Users\%USERNAME%\AppData\LocalLow\Unity\WebPlayer`.
|
||||
The Web Player was previously copied there by `installUnity.bat`.
|
||||
|
||||
Note that the version of the web player distributed with OpenFusion expects a standard `UnityWeb` magic number for all assets, instead of Retro's modified `streamed` magic number.
|
||||
@@ -48,32 +64,27 @@ This will potentially become relevant later, as people start experimenting and m
|
||||
|
||||
The web player will execute the game code, which will request the following files from the server: `/assetInfo.php` and `/loginInfo.php`.
|
||||
|
||||
`FreeClient/resources/app/files/assetInfo.php` contains the address from which to fetch the rest of the game's assets (the "dongresources").
|
||||
`/assetInfo.php` contains the address from which to fetch the rest of the game's assets (the "dongresources").
|
||||
Normally those would be hosted on the same web server as the gateway, but the OpenFusion distribution (in it's default configuration) doesn't use a web server at all!
|
||||
It loads the web pages locally using the `file://` schema, and fetches the game's assets from Turner's CDN (which is still hosting them to this day!).
|
||||
|
||||
`FreeClient/resources/app/files/loginInfo.php` contains the IP:port pair of the FusionFall login server, which the client will connect to. This login server drives the client while it's in the Character Selection menu, as well as Character Creation and the Tutorial.
|
||||
`/loginInfo.php` contains the IP:port pair of the FusionFall login server, which the client will connect to. This login server drives the client while it's in the Character Selection menu, as well as Character Creation and the Tutorial.
|
||||
|
||||
When the player clicks "ENTER THE GAME" (or completes the tutorial), the login server sends it the address of the shard server, which the client will then connect to and remain connected to during gameplay.
|
||||
|
||||
## Configuration
|
||||
|
||||
You can change the ports the FusionFall server listens on in `Server/config.ini`. Make sure the login server port is in sync with `loginInfo.php`.
|
||||
The shard port needs no such synchronization.
|
||||
You can also configure the distance at which you'll be able to see other players, though by default it's already as high as you'll want it.
|
||||
You can change the ports the FusionFall server listens on in `config.ini`. Make sure the login server port is in sync with what you enter into the client's server list - the shard port needs no such synchronization.
|
||||
|
||||
If you want to play with friends, you can change the IP in `loginInfo.php` to a login server hosted elsewhere.
|
||||
This config file also has several other options you can tweak, including log verbosity, database saving interval, default account/permission level, and more. See the comments within [the config file itself](https://github.com/OpenFusionProject/OpenFusion/blob/master/config.ini) for more details.
|
||||
|
||||
If you want to play with friends, simply enter the login server details into the `Add Server` dialogue in OpenFusionClient.
|
||||
This just works if you're all under the same LAN, but if you want to play over the internet you'll need to open a port, use a service like Hamachi or nGrok, or host the server on a VPS (just like any other gameserver).
|
||||
|
||||
If you're in a region in which Turner's CDN doesn't still have the game's assets cached, you won't be able to play the game in its default configuration.
|
||||
You'll need to obtain the necessary assets elsewhere and set up your own local web server to host them, because unlike web browsers, the game itself cannot interpret the `file://` schema, and will thus need the assets hosted on an actual HTTP server.
|
||||
Don't forget to point `assetInfo.php` to where you're hosting the assets and change the `src` param of both the `<embed>` tag and the `<object>` tag in `FreeClient/resources/app/files/index.html` to where you're hosting the `.unity3d` entrypoint.
|
||||
|
||||
If you change `loginInfo.php` or `assetInfo.php`, make sure not to put any newline characters (or any other whitespace) at the end of the file(s).
|
||||
Some modern IDEs/text editors do this automatically. If all else fails, use Notepad.
|
||||
|
||||
## Compiling
|
||||
|
||||
OpenFusion has one external dependency: SQLite. The oldest compatible version is `3.33.0`. You can install it on Windows using `vcpkg`, and on Unix/Linux using your distribution's package manager. For a more indepth guide on how to set up vcpkg, [read this guide on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Installing-SQLite-on-Windows-using-vcpkg).
|
||||
|
||||
You have two choices for compiling OpenFusion: the included Makefile and the included CMakeLists file.
|
||||
|
||||
### Makefile
|
||||
@@ -82,7 +93,7 @@ A detailed compilation guide is available for Windows users in the wiki [using M
|
||||
|
||||
### CMake
|
||||
|
||||
A detailed guide is available [in the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Compilation-with-CMake-or-Visual-Studio) for people using regular old CMake or the version of CMake that comes with Visual Studio. tl;dr: `cmake -B build`
|
||||
A detailed guide is available [on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Compilation-with-CMake-or-Visual-Studio) for people using regular old CMake or the version of CMake that comes with Visual Studio. tl;dr: `cmake -B build`
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -113,6 +124,4 @@ Because the server is still in development, ordinary players are allowed access
|
||||
* `/nano_unequip [slot] (0-2)`
|
||||
* `/nano_active [slot] (0-2)`
|
||||
|
||||
## Accounts
|
||||
|
||||
A basic account system has been added, when logging in if the username doesn't exist in the database, a new account with the provided password will be made and you'll be automatically logged in. Otherwise a login attempt will be made. A username must be between 4 and 32 characters, and a password must be between 8 and 32 characters otherwise the account will be rejected.
|
||||
### A full list of commands can be found [here](https://github.com/OpenFusionProject/OpenFusion/wiki/Ingame-Command-list).
|
||||
|
||||
79
appveyor.yml
79
appveyor.yml
@@ -1,79 +0,0 @@
|
||||
version: 'openfusion-{branch}-{build}'
|
||||
|
||||
image:
|
||||
- Visual Studio 2019
|
||||
- Ubuntu2004
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
configuration:
|
||||
- Release
|
||||
|
||||
for:
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: Ubuntu2004
|
||||
build_script:
|
||||
- ps: |
|
||||
$versions = "104", "728"
|
||||
|
||||
foreach ($version in $versions) {
|
||||
Write-Output "Cleaning old output"
|
||||
Invoke-Expression "make clean"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "make clean failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Write-Output "Building version $version"
|
||||
Invoke-Expression "make PROTOCOL_VERSION=$version"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "make failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Rename-Item -Path "bin/fusion" -newName "$version-fusion"
|
||||
Write-Output "Built version $version"
|
||||
}
|
||||
artifacts:
|
||||
- path: bin
|
||||
name: ubuntu20_04-bin-x64
|
||||
type: zip
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: Visual Studio 2019
|
||||
build_script:
|
||||
- ps: |
|
||||
$versions = "104", "728"
|
||||
$configurations = "Release", "Debug"
|
||||
|
||||
# AppVeyor uses VS2019 Community
|
||||
$vsPath = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community"
|
||||
|
||||
Import-Module "$vsPath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
|
||||
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation
|
||||
|
||||
foreach ($version in $versions) {
|
||||
if (Test-Path -LiteralPath "build") {
|
||||
Remove-Item "build" -Recurse
|
||||
Write-Output "Deleted existing build folder"
|
||||
}
|
||||
Invoke-Expression "cmake -B build -DPROTOCOL_VERSION=$version"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "cmake generation failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Write-Output "Generated build files for version $version"
|
||||
|
||||
foreach ($configuration in $configurations) {
|
||||
Write-Output "Building version $version $configuration"
|
||||
Invoke-Expression "msbuild build\OpenFusion.sln /p:Configuration=$configuration"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "msbuild build failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Rename-Item -Path "bin/$configuration" -newName "$version-$configuration"
|
||||
Write-Output "Built version $version $configuration"
|
||||
}
|
||||
}
|
||||
artifacts:
|
||||
- path: bin
|
||||
name: windows-vs2019-bin-x64
|
||||
type: zip
|
||||
81
config.ini
81
config.ini
@@ -5,23 +5,29 @@
|
||||
# 3 = print all packets
|
||||
verbosity=1
|
||||
|
||||
# sandbox the process on supported platforms
|
||||
sandbox=true
|
||||
|
||||
# Login Server configuration
|
||||
[login]
|
||||
# must be kept in sync with loginInfo.php
|
||||
port=8001
|
||||
port=23000
|
||||
# will all custom names be approved instantly?
|
||||
acceptallcustomnames=true
|
||||
# should attempts to log into non-existent accounts
|
||||
# automatically create them?
|
||||
autocreateaccounts=true
|
||||
# how often should everything be flushed to the database?
|
||||
# the default is 4 minutes
|
||||
dbsaveinterval=240
|
||||
|
||||
# Shard Server configuration
|
||||
[shard]
|
||||
port=8002
|
||||
port=23001
|
||||
ip=127.0.0.1
|
||||
# distance at which other players and NPCs become visible.
|
||||
# this value is used for calculating chunk size
|
||||
viewdistance=25600
|
||||
viewdistance=16000
|
||||
# time, in milliseconds, to wait before kicking a non-responsive client
|
||||
# default is 1 minute
|
||||
timeout=60000
|
||||
@@ -30,18 +36,43 @@ timeout=60000
|
||||
simulatemobs=true
|
||||
# little message players see when they enter the game
|
||||
motd=Welcome to OpenFusion!
|
||||
# NPC json data
|
||||
npcdata=tdata/NPCs.json
|
||||
# xdt json data
|
||||
xdtdata=tdata/xdt.json
|
||||
# mob json
|
||||
mobdata=tdata/mobs.json
|
||||
# path json
|
||||
pathdata=tdata/paths.json
|
||||
# drop json
|
||||
dropdata=tdata/drops.json
|
||||
# gruntwork output (this is what you submit)
|
||||
gruntwork=tdata/gruntwork.json
|
||||
|
||||
# The following are the default locations of the JSON files the server
|
||||
# requires to run. You can override them by changing their values and
|
||||
# uncommenting them (removing the leading # character from that line).
|
||||
|
||||
# location of the tabledata folder
|
||||
#tdatadir=tdata/
|
||||
# location of the patch folder
|
||||
#patchdir=tdata/patch/
|
||||
|
||||
# Space-separated list of patch folders in patchdir to load from.
|
||||
# If you uncomment this, note that Academy builds *must* contain 1013,
|
||||
# and pre-Academy builds must *not* contain it.
|
||||
#enabledpatches=1013
|
||||
|
||||
# Use Original FusionFall's racing score and reward calculation?
|
||||
# Set false to use Retro's calculation, make sure you have the correct
|
||||
# patch(es) loaded.
|
||||
#ogracingscores=true
|
||||
|
||||
# xdt json filename
|
||||
#xdtdata=xdt.json
|
||||
# NPC json filename
|
||||
#npcdata=NPCs.json
|
||||
# mob json filename
|
||||
#mobdata=mobs.json
|
||||
# path json filename
|
||||
#pathdata=paths.json
|
||||
# drop json filename
|
||||
#dropdata=drops.json
|
||||
# gruntwork output filename (this is what you submit)
|
||||
#gruntwork=gruntwork.json
|
||||
# location of the database
|
||||
#dbpath=database.db
|
||||
|
||||
# should tutorial flags be disabled off the bat?
|
||||
disablefirstuseflag=true
|
||||
|
||||
# account permission level that will be set upon character creation
|
||||
# 1 = default, will allow *all* commands
|
||||
@@ -57,11 +88,17 @@ accountlevel=1
|
||||
# 2 = Halloween
|
||||
# 3 = Easter
|
||||
eventmode=0
|
||||
# percent chance of an event crate dropping each kill
|
||||
eventcratechance=10
|
||||
|
||||
# spawn coordinates (Z is height)
|
||||
# the supplied defaults are at Sector V (future)
|
||||
spawnx=632032
|
||||
spawny=187177
|
||||
spawnz=-5500
|
||||
# you can override the default spawn point.
|
||||
# these example coords are for the Future (Z is height):
|
||||
#spawnx=632032
|
||||
#spawny=187177
|
||||
#spawnz=-5500
|
||||
|
||||
# Player location monitor interface configuration
|
||||
[monitor]
|
||||
enabled=false
|
||||
# the port to listen for connections on
|
||||
port=8003
|
||||
# how often the listeners should be updated (in milliseconds)
|
||||
interval=5000
|
||||
|
||||
12
docker-compose.yml
Normal file
12
docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
version: '3.4'
|
||||
|
||||
services:
|
||||
openfusion:
|
||||
image: openfusion
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile
|
||||
ports:
|
||||
- "23000:23000"
|
||||
- "23001:23001"
|
||||
- "8003:8003"
|
||||
BIN
res/openfusion-hero.png
Normal file
BIN
res/openfusion-hero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 425 KiB |
18
sql/migration1.sql
Normal file
18
sql/migration1.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
BEGIN TRANSACTION;
|
||||
-- New Columns
|
||||
ALTER TABLE Accounts ADD BanReason TEXT DEFAULT '' NOT NULL;
|
||||
ALTER TABLE RaceResults ADD RingCount INTEGER DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE RaceResults ADD Time INTEGER DEFAULT 0 NOT NULL;
|
||||
-- Fix timestamps in Meta
|
||||
INSERT INTO Meta (Key, Value) VALUES ('Created', 0);
|
||||
INSERT INTO Meta (Key, Value) VALUES ('LastMigration', strftime('%s', 'now'));
|
||||
UPDATE Meta SET Value = (SELECT Created FROM Meta WHERE Key = 'ProtocolVersion') Where Key = 'Created';
|
||||
-- Get rid of 'Created' Column
|
||||
CREATE TABLE Temp(Key TEXT NOT NULL UNIQUE, Value INTEGER NOT NULL);
|
||||
INSERT INTO Temp SELECT Key, Value FROM Meta;
|
||||
DROP TABLE Meta;
|
||||
ALTER TABLE Temp RENAME TO Meta;
|
||||
-- Update DB Version
|
||||
UPDATE Meta SET Value = 2 WHERE Key = 'DatabaseVersion';
|
||||
UPDATE Meta SET Value = strftime('%s', 'now') WHERE Key = 'LastMigration';
|
||||
COMMIT;
|
||||
37
sql/migration2.sql
Normal file
37
sql/migration2.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
It is recommended in the SQLite manual to turn off
|
||||
foreign keys when making schema changes that involve them
|
||||
*/
|
||||
PRAGMA foreign_keys=OFF;
|
||||
BEGIN TRANSACTION;
|
||||
-- New table to store code items
|
||||
CREATE TABLE RedeemedCodes(
|
||||
PlayerID INTEGER NOT NULL,
|
||||
Code TEXT NOT NULL,
|
||||
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||
UNIQUE (PlayerID, Code)
|
||||
);
|
||||
-- Change Coordinates in Players table to non-plural form
|
||||
ALTER TABLE Players RENAME COLUMN XCoordinates TO XCoordinate;
|
||||
ALTER TABLE Players RENAME COLUMN YCoordinates TO YCoordinate;
|
||||
ALTER TABLE Players RENAME COLUMN ZCoordinates TO ZCoordinate;
|
||||
-- Fix email attachments not being unique enough
|
||||
CREATE TABLE Temp (
|
||||
PlayerID INTEGER NOT NULL,
|
||||
MsgIndex INTEGER NOT NULL,
|
||||
Slot INTEGER NOT NULL,
|
||||
ID INTEGER NOT NULL,
|
||||
Type INTEGER NOT NULL,
|
||||
Opt INTEGER NOT NULL,
|
||||
TimeLimit INTEGER NOT NULL,
|
||||
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||
UNIQUE (PlayerID, MsgIndex, Slot)
|
||||
);
|
||||
INSERT INTO Temp SELECT * FROM EmailItems;
|
||||
DROP TABLE EmailItems;
|
||||
ALTER TABLE Temp RENAME TO EmailItems;
|
||||
-- Update DB Version
|
||||
UPDATE Meta SET Value = 3 WHERE Key = 'DatabaseVersion';
|
||||
UPDATE Meta SET Value = strftime('%s', 'now') WHERE Key = 'LastMigration';
|
||||
COMMIT;
|
||||
PRAGMA foreign_keys=ON;
|
||||
28
sql/migration3.sql
Normal file
28
sql/migration3.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
It is recommended in the SQLite manual to turn off
|
||||
foreign keys when making schema changes that involve them
|
||||
*/
|
||||
PRAGMA foreign_keys=OFF;
|
||||
BEGIN TRANSACTION;
|
||||
-- Change username column (Login) to be case-insensitive
|
||||
CREATE TABLE Temp (
|
||||
AccountID INTEGER NOT NULL,
|
||||
Login TEXT NOT NULL UNIQUE COLLATE NOCASE,
|
||||
Password TEXT NOT NULL,
|
||||
Selected INTEGER DEFAULT 1 NOT NULL,
|
||||
AccountLevel INTEGER NOT NULL,
|
||||
Created INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||
LastLogin INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||
BannedUntil INTEGER DEFAULT 0 NOT NULL,
|
||||
BannedSince INTEGER DEFAULT 0 NOT NULL,
|
||||
BanReason TEXT DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY(AccountID AUTOINCREMENT)
|
||||
);
|
||||
INSERT INTO Temp SELECT * FROM Accounts;
|
||||
DROP TABLE Accounts;
|
||||
ALTER TABLE Temp RENAME TO Accounts;
|
||||
-- Update DB Version
|
||||
UPDATE Meta SET Value = 4 WHERE Key = 'DatabaseVersion';
|
||||
UPDATE Meta SET Value = strftime('%s', 'now') WHERE Key = 'LastMigration';
|
||||
COMMIT;
|
||||
PRAGMA foreign_keys=ON;
|
||||
161
sql/tables.sql
Normal file
161
sql/tables.sql
Normal file
@@ -0,0 +1,161 @@
|
||||
CREATE TABLE IF NOT EXISTS Accounts (
|
||||
AccountID INTEGER NOT NULL,
|
||||
Login TEXT NOT NULL UNIQUE COLLATE NOCASE,
|
||||
Password TEXT NOT NULL,
|
||||
Selected INTEGER DEFAULT 1 NOT NULL,
|
||||
AccountLevel INTEGER NOT NULL,
|
||||
Created INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||
LastLogin INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||
BannedUntil INTEGER DEFAULT 0 NOT NULL,
|
||||
BannedSince INTEGER DEFAULT 0 NOT NULL,
|
||||
BanReason TEXT DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY(AccountID AUTOINCREMENT)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Players (
|
||||
PlayerID INTEGER NOT NULL,
|
||||
AccountID INTEGER NOT NULL,
|
||||
FirstName TEXT NOT NULL COLLATE NOCASE,
|
||||
LastName TEXT NOT NULL COLLATE NOCASE,
|
||||
NameCheck INTEGER NOT NULL,
|
||||
Slot INTEGER NOT NULL,
|
||||
Created INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||
LastLogin INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||
Level INTEGER DEFAULT 1 NOT NULL,
|
||||
Nano1 INTEGER DEFAULT 0 NOT NULL,
|
||||
Nano2 INTEGER DEFAULT 0 NOT NULL,
|
||||
Nano3 INTEGER DEFAULT 0 NOT NULL,
|
||||
AppearanceFlag INTEGER DEFAULT 0 NOT NULL,
|
||||
TutorialFlag INTEGER DEFAULT 0 NOT NULL,
|
||||
PayZoneFlag INTEGER DEFAULT 0 NOT NULL,
|
||||
XCoordinate INTEGER NOT NULL,
|
||||
YCoordinate INTEGER NOT NULL,
|
||||
ZCoordinate INTEGER NOT NULL,
|
||||
Angle INTEGER NOT NULL,
|
||||
HP INTEGER NOT NULL,
|
||||
FusionMatter INTEGER DEFAULT 0 NOT NULL,
|
||||
Taros INTEGER DEFAULT 0 NOT NULL,
|
||||
BatteryW INTEGER DEFAULT 0 NOT NULL,
|
||||
BatteryN INTEGER DEFAULT 0 NOT NULL,
|
||||
Mentor INTEGER DEFAULT 5 NOT NULL,
|
||||
CurrentMissionID INTEGER DEFAULT 0 NOT NULL,
|
||||
WarpLocationFlag INTEGER DEFAULT 0 NOT NULL,
|
||||
SkywayLocationFlag BLOB NOT NULL,
|
||||
FirstUseFlag BLOB NOT NULL,
|
||||
Quests BLOB NOT NULL,
|
||||
PRIMARY KEY(PlayerID AUTOINCREMENT),
|
||||
FOREIGN KEY(AccountID) REFERENCES Accounts(AccountID) ON DELETE CASCADE,
|
||||
UNIQUE (AccountID, Slot),
|
||||
UNIQUE (FirstName, LastName)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Appearances (
|
||||
PlayerID INTEGER UNIQUE NOT NULL,
|
||||
Body INTEGER DEFAULT 0 NOT NULL,
|
||||
EyeColor INTEGER DEFAULT 1 NOT NULL,
|
||||
FaceStyle INTEGER DEFAULT 1 NOT NULL,
|
||||
Gender INTEGER DEFAULT 1 NOT NULL,
|
||||
HairColor INTEGER DEFAULT 1 NOT NULL,
|
||||
HairStyle INTEGER DEFAULT 1 NOT NULL,
|
||||
Height INTEGER DEFAULT 0 NOT NULL,
|
||||
SkinColor INTEGER DEFAULT 1 NOT NULL,
|
||||
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Inventory (
|
||||
PlayerID INTEGER NOT NULL,
|
||||
Slot INTEGER NOT NULL,
|
||||
ID INTEGER NOT NULL,
|
||||
Type INTEGER NOT NULL,
|
||||
Opt INTEGER NOT NULL,
|
||||
TimeLimit INTEGER DEFAULT 0 NOT NULL,
|
||||
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||
UNIQUE (PlayerID, Slot)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS QuestItems (
|
||||
PlayerID INTEGER NOT NULL,
|
||||
Slot INTEGER NOT NULL,
|
||||
ID INTEGER NOT NULL,
|
||||
Opt INTEGER NOT NULL,
|
||||
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||
UNIQUE (PlayerID, Slot)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Nanos (
|
||||
PlayerID INTEGER NOT NULL,
|
||||
ID INTEGER NOT NULL,
|
||||
Skill INTEGER NOT NULL,
|
||||
Stamina INTEGER DEFAULT 150 NOT NULL,
|
||||
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||
UNIQUE (PlayerID, ID)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS RunningQuests (
|
||||
PlayerID INTEGER NOT NULL,
|
||||
TaskID INTEGER NOT NULL,
|
||||
RemainingNPCCount1 INTEGER NOT NULL,
|
||||
RemainingNPCCount2 INTEGER NOT NULL,
|
||||
RemainingNPCCount3 INTEGER NOT NULL,
|
||||
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Buddyships (
|
||||
PlayerAID INTEGER NOT NULL,
|
||||
PlayerBID INTEGER NOT NULL,
|
||||
FOREIGN KEY(PlayerAID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||
FOREIGN KEY(PlayerBID) REFERENCES Players(PlayerID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Blocks (
|
||||
PlayerID INTEGER NOT NULL,
|
||||
BlockedPlayerID INTEGER NOT NULL,
|
||||
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||
FOREIGN KEY(BlockedPlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS EmailData (
|
||||
PlayerID INTEGER NOT NULL,
|
||||
MsgIndex INTEGER NOT NULL,
|
||||
ReadFlag INTEGER NOT NULL,
|
||||
ItemFlag INTEGER NOT NULL,
|
||||
SenderID INTEGER NOT NULL,
|
||||
SenderFirstName TEXT NOT NULL COLLATE NOCASE,
|
||||
SenderLastName TEXT NOT NULL COLLATE NOCASE,
|
||||
SubjectLine TEXT NOT NULL,
|
||||
MsgBody TEXT NOT NULL,
|
||||
Taros INTEGER NOT NULL,
|
||||
SendTime INTEGER NOT NULL,
|
||||
DeleteTime INTEGER NOT NULL,
|
||||
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||
UNIQUE(PlayerID, MsgIndex)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS EmailItems (
|
||||
PlayerID INTEGER NOT NULL,
|
||||
MsgIndex INTEGER NOT NULL,
|
||||
Slot INTEGER NOT NULL,
|
||||
ID INTEGER NOT NULL,
|
||||
Type INTEGER NOT NULL,
|
||||
Opt INTEGER NOT NULL,
|
||||
TimeLimit INTEGER NOT NULL,
|
||||
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||
UNIQUE (PlayerID, MsgIndex, Slot)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS RaceResults(
|
||||
EPID INTEGER NOT NULL,
|
||||
PlayerID INTEGER NOT NULL,
|
||||
Score INTEGER NOT NULL,
|
||||
RingCount INTEGER NOT NULL,
|
||||
Time INTEGER NOT NULL,
|
||||
Timestamp INTEGER NOT NULL,
|
||||
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS RedeemedCodes(
|
||||
PlayerID INTEGER NOT NULL,
|
||||
Code TEXT NOT NULL,
|
||||
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||
UNIQUE (PlayerID, Code)
|
||||
)
|
||||
517
src/Abilities.cpp
Normal file
517
src/Abilities.cpp
Normal file
@@ -0,0 +1,517 @@
|
||||
#include "Abilities.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "NPCManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Buffs.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "MobAI.hpp"
|
||||
|
||||
using namespace Abilities;
|
||||
|
||||
std::map<int32_t, SkillData> Abilities::SkillTable;
|
||||
|
||||
#pragma region Skill handlers
|
||||
static SkillResult handleSkillDamage(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
EntityRef sourceRef = source->getRef();
|
||||
double scalingFactor = 1;
|
||||
if(sourceRef.kind == EntityKind::PLAYER)
|
||||
scalingFactor = std::max(source->getMaxHP(), target->getMaxHP()) / 1000.0;
|
||||
else
|
||||
scalingFactor = source->getMaxHP() / 1500.0;
|
||||
|
||||
int damage = (int)(skill->values[0][power] * scalingFactor);
|
||||
int dealt = target->takeDamage(sourceRef, damage);
|
||||
|
||||
sSkillResult_Damage result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.bProtected = dealt <= 0;
|
||||
result.iDamage = dealt;
|
||||
result.iHP = target->getCurrentHP();
|
||||
return SkillResult(sizeof(sSkillResult_Damage), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillHealHP(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
EntityRef sourceRef = source->getRef();
|
||||
int heal = skill->values[0][power];
|
||||
double scalingFactor = target->getMaxHP() / 1000.0;
|
||||
int healed = target->heal(sourceRef, heal * scalingFactor);
|
||||
|
||||
sSkillResult_Heal_HP result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.iHealHP = healed;
|
||||
result.iHP = target->getCurrentHP();
|
||||
return SkillResult(sizeof(sSkillResult_Heal_HP), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
// take aggro
|
||||
target->takeDamage(source->getRef(), 0);
|
||||
|
||||
int duration = 0;
|
||||
int strength = 0;
|
||||
bool blocked = target->hasBuff(ECSB_FREEDOM);
|
||||
if(!blocked) {
|
||||
duration = skill->durationTime[power];
|
||||
strength = skill->values[0][power];
|
||||
BuffStack debuff = {
|
||||
(duration * 100) / MS_PER_COMBAT_TICK, // ticks
|
||||
strength, // value
|
||||
source->getRef(), // source
|
||||
BuffClass::NANO, // buff class
|
||||
};
|
||||
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||
target->addBuff(timeBuffId,
|
||||
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||
if(status == ETBU_DEL) Buffs::timeBuffTimeout(self);
|
||||
},
|
||||
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||
Buffs::timeBuffTick(self, buff);
|
||||
},
|
||||
&debuff);
|
||||
}
|
||||
|
||||
sSkillResult_Damage_N_Debuff result{};
|
||||
|
||||
result.iDamage = duration / 10; // we use the duration as the damage number (why?)
|
||||
result.iHP = target->getCurrentHP();
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.bProtected = blocked;
|
||||
result.iConditionBitFlag = target->getCompositeCondition();
|
||||
|
||||
// for player targets, make sure to update Nano stamina
|
||||
if (target->getCharType() == 1) {
|
||||
Player *plr = dynamic_cast<Player*>(target);
|
||||
result.iStamina = plr->getActiveNano()->iStamina;
|
||||
}
|
||||
|
||||
return SkillResult(sizeof(sSkillResult_Damage_N_Debuff), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillLeech(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
EntityRef sourceRef = source->getRef();
|
||||
int heal = skill->values[0][power];
|
||||
int healed = source->heal(sourceRef, heal);
|
||||
int damage = heal * 2;
|
||||
int dealt = target->takeDamage(sourceRef, damage);
|
||||
|
||||
sSkillResult_Leech result{};
|
||||
|
||||
result.Damage.eCT = target->getCharType();
|
||||
result.Damage.iID = target->getID();
|
||||
result.Damage.bProtected = dealt <= 0;
|
||||
result.Damage.iDamage = dealt;
|
||||
result.Damage.iHP = target->getCurrentHP();
|
||||
|
||||
result.Heal.eCT = result.Damage.eCT;
|
||||
result.Heal.iID = result.Damage.iID;
|
||||
result.Heal.iHealHP = healed;
|
||||
result.Heal.iHP = source->getCurrentHP();
|
||||
|
||||
return SkillResult(sizeof(sSkillResult_Leech), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
int duration = skill->durationTime[power];
|
||||
int strength = skill->values[0][power];
|
||||
BuffStack passiveBuff = {
|
||||
// if the duration is 0, it needs to be recast every tick
|
||||
duration == 0 ? 1 : (duration * 100) / MS_PER_COMBAT_TICK, // ticks
|
||||
strength, // value
|
||||
source->getRef(), // source
|
||||
source == target ? BuffClass::NANO : BuffClass::GROUP_NANO, // buff class
|
||||
};
|
||||
|
||||
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||
SkillDrainType drainType = skill->drainType;
|
||||
int combatLifetime = 0;
|
||||
if(!target->addBuff(timeBuffId,
|
||||
[drainType](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||
if(buff->id == ECSB_BOUNDINGBALL && status == ETBU_ADD) {
|
||||
// drain
|
||||
ICombatant* combatant = dynamic_cast<ICombatant*>(self.getEntity());
|
||||
combatant->takeDamage(buff->getLastSource(), 0); // aggro
|
||||
}
|
||||
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||
if(drainType == SkillDrainType::ACTIVE && status == ETBU_DEL)
|
||||
Buffs::timeBuffTimeout(self);
|
||||
},
|
||||
[combatLifetime](EntityRef self, Buff* buff, time_t currTime) mutable {
|
||||
if(buff->id == ECSB_BOUNDINGBALL &&
|
||||
combatLifetime % COMBAT_TICKS_PER_DRAIN_PROC == 0)
|
||||
Buffs::tickDrain(self, buff, COMBAT_TICKS_PER_DRAIN_PROC); // drain
|
||||
combatLifetime++;
|
||||
},
|
||||
&passiveBuff)) return SkillResult();
|
||||
|
||||
sSkillResult_Buff result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.bProtected = false;
|
||||
result.iConditionBitFlag = target->getCompositeCondition();
|
||||
return SkillResult(sizeof(sSkillResult_Buff), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
if(target->getCharType() != 1)
|
||||
return SkillResult(); // only Players are valid targets for battery drain
|
||||
Player* plr = dynamic_cast<Player*>(target);
|
||||
|
||||
const double scalingFactor = (18 + source->getLevel()) / 36.0;
|
||||
const bool blocked = target->hasBuff(ECSB_PROTECT_BATTERY);
|
||||
|
||||
int boostDrain = 0;
|
||||
int potionDrain = 0;
|
||||
if(!blocked) {
|
||||
boostDrain = (int)(skill->values[0][power] * scalingFactor);
|
||||
if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
|
||||
plr->batteryW -= boostDrain;
|
||||
|
||||
potionDrain = (int)(skill->values[1][power] * scalingFactor);
|
||||
if(potionDrain > plr->batteryN) potionDrain = plr->batteryN;
|
||||
plr->batteryN -= potionDrain;
|
||||
}
|
||||
|
||||
sSkillResult_BatteryDrain result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.bProtected = blocked;
|
||||
result.iDrainW = boostDrain;
|
||||
result.iBatteryW = plr->batteryW;
|
||||
result.iDrainN = potionDrain;
|
||||
result.iBatteryN = plr->batteryN;
|
||||
result.iStamina = plr->getActiveNano()->iStamina;
|
||||
result.bNanoDeactive = plr->getActiveNano()->iStamina <= 0;
|
||||
result.iConditionBitFlag = target->getCompositeCondition();
|
||||
return SkillResult(sizeof(sSkillResult_BatteryDrain), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillMove(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
if(source->getCharType() != 1)
|
||||
return SkillResult(); // only Players are valid sources for recall
|
||||
|
||||
Player* plr = dynamic_cast<Player*>(source);
|
||||
if(source == target) {
|
||||
// no trailing struct for self
|
||||
PlayerManager::sendPlayerTo(target->getRef().sock, plr->recallX, plr->recallY, plr->recallZ, plr->recallInstance);
|
||||
return SkillResult();
|
||||
}
|
||||
|
||||
sSkillResult_Move result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.iMapNum = plr->recallInstance;
|
||||
result.iMoveX = plr->recallX;
|
||||
result.iMoveY = plr->recallY;
|
||||
result.iMoveZ = plr->recallZ;
|
||||
return SkillResult(sizeof(sSkillResult_Move), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillResurrect(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
sSkillResult_Resurrect result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.iRegenHP = target->getCurrentHP();
|
||||
return SkillResult(sizeof(sSkillResult_Resurrect), &result);
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombatant* src, std::vector<ICombatant*> targets) {
|
||||
SkillResult (*skillHandler)(SkillData*, int, ICombatant*, ICombatant*) = nullptr;
|
||||
std::vector<SkillResult> results;
|
||||
|
||||
switch(skill->skillType)
|
||||
{
|
||||
case SkillType::CORRUPTIONATTACK:
|
||||
case SkillType::CORRUPTIONATTACKLOSE:
|
||||
case SkillType::CORRUPTIONATTACKWIN:
|
||||
// skillHandler = handleSkillCorruptionReflect;
|
||||
// break;
|
||||
case SkillType::DAMAGE:
|
||||
skillHandler = handleSkillDamage;
|
||||
break;
|
||||
case SkillType::HEAL_HP:
|
||||
case SkillType::RETURNHOMEHEAL:
|
||||
skillHandler = handleSkillHealHP;
|
||||
break;
|
||||
case SkillType::KNOCKDOWN:
|
||||
case SkillType::SLEEP:
|
||||
case SkillType::SNARE:
|
||||
case SkillType::STUN:
|
||||
skillHandler = handleSkillDamageNDebuff;
|
||||
break;
|
||||
case SkillType::JUMP:
|
||||
case SkillType::RUN:
|
||||
case SkillType::STEALTH:
|
||||
case SkillType::MINIMAPENEMY:
|
||||
case SkillType::MINIMAPTRESURE:
|
||||
case SkillType::PHOENIX:
|
||||
case SkillType::PROTECTBATTERY:
|
||||
case SkillType::PROTECTINFECTION:
|
||||
case SkillType::REWARDBLOB:
|
||||
case SkillType::REWARDCASH:
|
||||
// case SkillType::INFECTIONDAMAGE:
|
||||
case SkillType::FREEDOM:
|
||||
case SkillType::BOUNDINGBALL:
|
||||
case SkillType::INVULNERABLE:
|
||||
case SkillType::STAMINA_SELF:
|
||||
case SkillType::NANOSTIMPAK:
|
||||
case SkillType::BUFFHEAL:
|
||||
skillHandler = handleSkillBuff;
|
||||
break;
|
||||
case SkillType::BLOODSUCKING:
|
||||
skillHandler = handleSkillLeech;
|
||||
break;
|
||||
case SkillType::RETROROCKET_SELF:
|
||||
// no-op
|
||||
return results;
|
||||
case SkillType::PHOENIX_GROUP:
|
||||
skillHandler = handleSkillResurrect;
|
||||
break;
|
||||
case SkillType::RECALL:
|
||||
case SkillType::RECALL_GROUP:
|
||||
skillHandler = handleSkillMove;
|
||||
break;
|
||||
case SkillType::BATTERYDRAIN:
|
||||
skillHandler = handleSkillBatteryDrain;
|
||||
break;
|
||||
default:
|
||||
std::cout << "[WARN] Unhandled skill type " << (int)skill->skillType << std::endl;
|
||||
return results;
|
||||
}
|
||||
|
||||
for(ICombatant* target : targets) {
|
||||
assert(target != nullptr);
|
||||
SkillResult result = skillHandler(skill, power, src != nullptr ? src : target, target);
|
||||
if(result.size == 0) continue; // skill not applicable
|
||||
if(result.size > MAX_SKILLRESULT_SIZE) {
|
||||
std::cout << "[WARN] bad skill result size for " << (int)skill->skillType << " from " << (void*)handleSkillBuff << std::endl;
|
||||
continue;
|
||||
}
|
||||
results.push_back(result);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
static void attachSkillResults(std::vector<SkillResult> results, uint8_t* pivot) {
|
||||
for(SkillResult& result : results) {
|
||||
size_t sz = result.size;
|
||||
memcpy(pivot, result.payload, sz);
|
||||
pivot += sz;
|
||||
}
|
||||
}
|
||||
|
||||
void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std::vector<ICombatant*> affected) {
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
ICombatant* combatant = dynamic_cast<ICombatant*>(plr);
|
||||
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr))
|
||||
boost = 3;
|
||||
|
||||
if(skill->drainType == SkillDrainType::ACTIVE) {
|
||||
nano.iStamina -= skill->batteryUse[boost];
|
||||
if (nano.iStamina <= 0)
|
||||
nano.iStamina = 0;
|
||||
}
|
||||
|
||||
std::vector<SkillResult> results = handleSkill(skill, boost, combatant, affected);
|
||||
if(results.empty()) return; // no effect; no need for confirmation packets
|
||||
|
||||
// lazy validation since skill results might be different sizes
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), results.size(), MAX_SKILLRESULT_SIZE)) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE_SUCC packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize response struct
|
||||
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC);
|
||||
for(SkillResult& sr : results)
|
||||
resplen += sr.size;
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_NANO_SKILL_USE_SUCC* pkt = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf;
|
||||
pkt->iPC_ID = plr->iID;
|
||||
pkt->iNanoID = nano.iID;
|
||||
pkt->iSkillID = nano.iSkillID;
|
||||
pkt->iNanoStamina = nano.iStamina;
|
||||
pkt->bNanoDeactive = nano.iStamina <= 0;
|
||||
pkt->eST = (int32_t)skill->skillType;
|
||||
pkt->iTargetCnt = (int32_t)results.size();
|
||||
|
||||
attachSkillResults(results, (uint8_t*)(pkt + 1));
|
||||
sock->sendPacket(pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||
|
||||
if(skill->skillType == SkillType::RECALL_GROUP)
|
||||
// group recall packet is sent only to group members
|
||||
PlayerManager::sendToGroup(sock, pkt, P_FE2CL_NANO_SKILL_USE, resplen);
|
||||
else
|
||||
PlayerManager::sendToViewable(sock, pkt, P_FE2CL_NANO_SKILL_USE, resplen);
|
||||
}
|
||||
|
||||
void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*> affected) {
|
||||
if(SkillTable.count(skillID) == 0)
|
||||
return;
|
||||
|
||||
Entity* entity = npc.getEntity();
|
||||
ICombatant* src = nullptr;
|
||||
if(npc.kind == EntityKind::COMBAT_NPC || npc.kind == EntityKind::MOB)
|
||||
src = dynamic_cast<ICombatant*>(entity);
|
||||
|
||||
SkillData* skill = &SkillTable[skillID];
|
||||
|
||||
std::vector<SkillResult> results = handleSkill(skill, 0, src, affected);
|
||||
if(results.empty()) return; // no effect; no need for confirmation packets
|
||||
|
||||
// lazy validation since skill results might be different sizes
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), results.size(), MAX_SKILLRESULT_SIZE)) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_NPC_SKILL_HIT packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize response struct
|
||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT);
|
||||
for(SkillResult& sr : results)
|
||||
resplen += sr.size;
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
||||
pkt->iNPC_ID = npc.id;
|
||||
pkt->iSkillID = skillID;
|
||||
pkt->eST = (int32_t)skill->skillType;
|
||||
pkt->iTargetCnt = (int32_t)results.size();
|
||||
if(npc.kind == EntityKind::MOB) {
|
||||
Mob* mob = dynamic_cast<Mob*>(entity);
|
||||
pkt->iValue1 = mob->hitX;
|
||||
pkt->iValue2 = mob->hitY;
|
||||
pkt->iValue3 = mob->hitZ;
|
||||
}
|
||||
|
||||
attachSkillResults(results, (uint8_t*)(pkt + 1));
|
||||
NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
|
||||
}
|
||||
|
||||
static std::vector<ICombatant*> entityRefsToCombatants(std::vector<EntityRef> refs) {
|
||||
std::vector<ICombatant*> combatants;
|
||||
for(EntityRef ref : refs) {
|
||||
if(ref.kind == EntityKind::PLAYER)
|
||||
combatants.push_back(dynamic_cast<ICombatant*>(PlayerManager::getPlayer(ref.sock)));
|
||||
else if(ref.kind == EntityKind::COMBAT_NPC || ref.kind == EntityKind::MOB)
|
||||
combatants.push_back(dynamic_cast<ICombatant*>(ref.getEntity()));
|
||||
}
|
||||
return combatants;
|
||||
}
|
||||
|
||||
std::vector<ICombatant*> Abilities::matchTargets(ICombatant* src, SkillData* skill, int count, int32_t *ids) {
|
||||
|
||||
if(skill->targetType == SkillTargetType::GROUP)
|
||||
return entityRefsToCombatants(src->getGroupMembers());
|
||||
|
||||
// this check *has* to happen after the group check above due to cases like group recall that use both
|
||||
if(skill->effectTarget == SkillEffectTarget::SELF)
|
||||
return {src}; // client sends 0 targets for certain self-targeting skills (recall)
|
||||
|
||||
// individuals
|
||||
std::vector<ICombatant*> targets;
|
||||
for (int i = 0; i < count; i++) {
|
||||
int32_t id = ids[i];
|
||||
if (skill->targetType == SkillTargetType::MOBS) {
|
||||
// mob
|
||||
if (NPCManager::NPCs.find(id) != NPCManager::NPCs.end()) {
|
||||
BaseNPC* npc = NPCManager::NPCs[id];
|
||||
if (npc->kind == EntityKind::COMBAT_NPC || npc->kind == EntityKind::MOB) {
|
||||
targets.push_back(dynamic_cast<ICombatant*>(npc));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
std::cout << "[WARN] skill: invalid mob target (id " << id << ")\n";
|
||||
} else if(skill->targetType == SkillTargetType::PLAYERS) {
|
||||
// player
|
||||
Player* plr = PlayerManager::getPlayerFromID(id);
|
||||
if (plr != nullptr) {
|
||||
targets.push_back(dynamic_cast<ICombatant*>(plr));
|
||||
continue;
|
||||
}
|
||||
std::cout << "[WARN] skill: invalid player target (id " << id << ")\n";
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
/* ripped from client (enums emplaced) */
|
||||
int Abilities::getCSTBFromST(SkillType skillType) {
|
||||
int result = 0;
|
||||
switch (skillType)
|
||||
{
|
||||
case SkillType::RUN:
|
||||
result = ECSB_UP_MOVE_SPEED;
|
||||
break;
|
||||
case SkillType::JUMP:
|
||||
result = ECSB_UP_JUMP_HEIGHT;
|
||||
break;
|
||||
case SkillType::STEALTH:
|
||||
result = ECSB_UP_STEALTH;
|
||||
break;
|
||||
case SkillType::PHOENIX:
|
||||
result = ECSB_PHOENIX;
|
||||
break;
|
||||
case SkillType::PROTECTBATTERY:
|
||||
result = ECSB_PROTECT_BATTERY;
|
||||
break;
|
||||
case SkillType::PROTECTINFECTION:
|
||||
result = ECSB_PROTECT_INFECTION;
|
||||
break;
|
||||
case SkillType::MINIMAPENEMY:
|
||||
result = ECSB_MINIMAP_ENEMY;
|
||||
break;
|
||||
case SkillType::MINIMAPTRESURE:
|
||||
result = ECSB_MINIMAP_TRESURE;
|
||||
break;
|
||||
case SkillType::REWARDBLOB:
|
||||
result = ECSB_REWARD_BLOB;
|
||||
break;
|
||||
case SkillType::REWARDCASH:
|
||||
result = ECSB_REWARD_CASH;
|
||||
break;
|
||||
case SkillType::FREEDOM:
|
||||
result = ECSB_FREEDOM;
|
||||
break;
|
||||
case SkillType::INVULNERABLE:
|
||||
result = ECSB_INVULNERABLE;
|
||||
break;
|
||||
case SkillType::BUFFHEAL:
|
||||
result = ECSB_HEAL;
|
||||
break;
|
||||
case SkillType::NANOSTIMPAK:
|
||||
result = ECSB_STIMPAKSLOT1;
|
||||
// shift as necessary
|
||||
break;
|
||||
case SkillType::SNARE:
|
||||
result = ECSB_DN_MOVE_SPEED;
|
||||
break;
|
||||
case SkillType::STUN:
|
||||
result = ECSB_STUN;
|
||||
break;
|
||||
case SkillType::SLEEP:
|
||||
result = ECSB_MEZ;
|
||||
break;
|
||||
case SkillType::INFECTIONDAMAGE:
|
||||
result = ECSB_INFECTION;
|
||||
break;
|
||||
case SkillType::BOUNDINGBALL:
|
||||
result = ECSB_BOUNDINGBALL;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
112
src/Abilities.hpp
Normal file
112
src/Abilities.hpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "Entities.hpp"
|
||||
#include "Player.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
const int COMBAT_TICKS_PER_DRAIN_PROC = 2;
|
||||
constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain);
|
||||
|
||||
enum class SkillType {
|
||||
DAMAGE = 1,
|
||||
HEAL_HP = 2,
|
||||
KNOCKDOWN = 3, // uses DamageNDebuff
|
||||
SLEEP = 4, // uses DamageNDebuff
|
||||
SNARE = 5, // uses DamageNDebuff
|
||||
HEAL_STAMINA = 6,
|
||||
STAMINA_SELF = 7,
|
||||
STUN = 8, // uses DamageNDebuff
|
||||
WEAPONSLOW = 9,
|
||||
JUMP = 10,
|
||||
RUN = 11,
|
||||
STEALTH = 12,
|
||||
SWIM = 13,
|
||||
MINIMAPENEMY = 14,
|
||||
MINIMAPTRESURE = 15,
|
||||
PHOENIX = 16,
|
||||
PROTECTBATTERY = 17,
|
||||
PROTECTINFECTION = 18,
|
||||
REWARDBLOB = 19,
|
||||
REWARDCASH = 20,
|
||||
BATTERYDRAIN = 21,
|
||||
CORRUPTIONATTACK = 22,
|
||||
INFECTIONDAMAGE = 23,
|
||||
KNOCKBACK = 24,
|
||||
FREEDOM = 25,
|
||||
PHOENIX_GROUP = 26,
|
||||
RECALL = 27,
|
||||
RECALL_GROUP = 28,
|
||||
RETROROCKET_SELF = 29,
|
||||
BLOODSUCKING = 30,
|
||||
BOUNDINGBALL = 31,
|
||||
INVULNERABLE = 32,
|
||||
NANOSTIMPAK = 33,
|
||||
RETURNHOMEHEAL = 34,
|
||||
BUFFHEAL = 35,
|
||||
EXTRABANK = 36,
|
||||
CORRUPTIONATTACKWIN = 38,
|
||||
CORRUPTIONATTACKLOSE = 39,
|
||||
};
|
||||
|
||||
enum class SkillEffectTarget {
|
||||
POINT = 1,
|
||||
SELF = 2,
|
||||
CONE = 3,
|
||||
WEAPON = 4,
|
||||
AREA_SELF = 5,
|
||||
AREA_TARGET = 6
|
||||
};
|
||||
|
||||
enum class SkillTargetType {
|
||||
MOBS = 1,
|
||||
PLAYERS = 2,
|
||||
GROUP = 3
|
||||
};
|
||||
|
||||
enum class SkillDrainType {
|
||||
ACTIVE = 1,
|
||||
PASSIVE = 2
|
||||
};
|
||||
|
||||
struct SkillResult {
|
||||
size_t size;
|
||||
uint8_t payload[MAX_SKILLRESULT_SIZE];
|
||||
SkillResult(size_t len, void* dat) {
|
||||
assert(len <= MAX_SKILLRESULT_SIZE);
|
||||
size = len;
|
||||
memcpy(payload, dat, len);
|
||||
}
|
||||
SkillResult() {
|
||||
size = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct SkillData {
|
||||
SkillType skillType; // eST
|
||||
SkillEffectTarget effectTarget;
|
||||
int effectType; // always 1?
|
||||
SkillTargetType targetType;
|
||||
SkillDrainType drainType;
|
||||
int effectArea;
|
||||
|
||||
int batteryUse[4];
|
||||
int durationTime[4];
|
||||
|
||||
int valueTypes[3];
|
||||
int values[3][4];
|
||||
};
|
||||
|
||||
namespace Abilities {
|
||||
extern std::map<int32_t, SkillData> SkillTable;
|
||||
|
||||
void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector<ICombatant*>);
|
||||
void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>);
|
||||
|
||||
std::vector<ICombatant*> matchTargets(ICombatant*, SkillData*, int, int32_t*);
|
||||
int getCSTBFromST(SkillType skillType);
|
||||
}
|
||||
465
src/Buddies.cpp
Normal file
465
src/Buddies.cpp
Normal file
@@ -0,0 +1,465 @@
|
||||
#include "Buddies.hpp"
|
||||
|
||||
#include "db/Database.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
|
||||
using namespace Buddies;
|
||||
|
||||
#pragma region Helper methods
|
||||
|
||||
static int getAvailableBuddySlot(Player* plr) {
|
||||
int slot = -1;
|
||||
for (int i = 0; i < 50; i++) {
|
||||
if (plr->buddyIDs[i] == 0)
|
||||
return i;
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
static bool playerHasBuddyWithID(Player* plr, int buddyID) {
|
||||
for (int i = 0; i < 50; i++) {
|
||||
if (plr->buddyIDs[i] == buddyID)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
// Refresh buddy list
|
||||
void Buddies::refreshBuddyList(CNSocket* sock) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
int buddyCnt = Database::getNumBuddies(plr);
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC), buddyCnt, sizeof(sBuddyBaseInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize response struct
|
||||
size_t resplen = sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC) + buddyCnt * sizeof(sBuddyBaseInfo);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC* resp = (sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC*)respbuf;
|
||||
sBuddyBaseInfo* respdata = (sBuddyBaseInfo*)(respbuf + sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC));
|
||||
|
||||
// base response fields
|
||||
resp->iBuddyCnt = buddyCnt;
|
||||
resp->iID = plr->iID;
|
||||
resp->iPCUID = plr->PCStyle.iPC_UID;
|
||||
resp->iListNum = 0; // ???
|
||||
|
||||
int buddyIndex = 0;
|
||||
for (int i = 0; i < 50; i++) {
|
||||
int64_t buddyID = plr->buddyIDs[i];
|
||||
if (buddyID != 0) {
|
||||
sBuddyBaseInfo buddyInfo = {};
|
||||
Player buddyPlayerData = {};
|
||||
Database::getPlayer(&buddyPlayerData, buddyID);
|
||||
if (buddyPlayerData.iID == 0)
|
||||
continue;
|
||||
buddyInfo.bBlocked = plr->isBuddyBlocked[i];
|
||||
buddyInfo.bFreeChat = 1;
|
||||
buddyInfo.iGender = buddyPlayerData.PCStyle.iGender;
|
||||
buddyInfo.iID = buddyID;
|
||||
buddyInfo.iPCUID = buddyID;
|
||||
buddyInfo.iNameCheckFlag = buddyPlayerData.PCStyle.iNameCheck;
|
||||
buddyInfo.iPCState = buddyPlayerData.iPCState;
|
||||
memcpy(buddyInfo.szFirstName, buddyPlayerData.PCStyle.szFirstName, sizeof(buddyInfo.szFirstName));
|
||||
memcpy(buddyInfo.szLastName, buddyPlayerData.PCStyle.szLastName, sizeof(buddyInfo.szLastName));
|
||||
respdata[buddyIndex] = buddyInfo;
|
||||
buddyIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC, resplen);
|
||||
}
|
||||
|
||||
// Buddy request
|
||||
static void requestBuddy(CNSocket* sock, CNPacketData* data) {
|
||||
auto req = (sP_CL2FE_REQ_REQUEST_MAKE_BUDDY*)data->buf;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(req->iBuddyID);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
if (getAvailableBuddySlot(plr) == -1 || getAvailableBuddySlot(otherPlr) == -1)
|
||||
{
|
||||
INITSTRUCT(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_FAIL, failResp);
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_REQUEST_MAKE_BUDDY_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(otherPlr->iID);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC, resp);
|
||||
INITSTRUCT(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC_TO_ACCEPTER, otherResp);
|
||||
|
||||
resp.iRequestID = plr->iID;
|
||||
resp.iBuddyID = req->iBuddyID;
|
||||
resp.iBuddyPCUID = req->iBuddyPCUID;
|
||||
|
||||
otherResp.iRequestID = plr->iID;
|
||||
otherResp.iBuddyID = req->iBuddyID;
|
||||
memcpy(otherResp.szFirstName, plr->PCStyle.szFirstName, sizeof(plr->PCStyle.szFirstName));
|
||||
memcpy(otherResp.szLastName, plr->PCStyle.szLastName, sizeof(plr->PCStyle.szLastName));
|
||||
|
||||
std::cout << "Buddy ID: " << req->iBuddyID << std::endl;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC);
|
||||
otherSock->sendPacket(otherResp, P_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC_TO_ACCEPTER);
|
||||
|
||||
}
|
||||
|
||||
// Sending buddy request by player name
|
||||
static void reqBuddyByName(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY*)data->buf;
|
||||
Player* plrReq = PlayerManager::getPlayer(sock);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_FIND_NAME_MAKE_BUDDY_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromName(AUTOU16TOU8(pkt->szFirstName), AUTOU16TOU8(pkt->szLastName));
|
||||
if (otherSock == nullptr)
|
||||
return; // no player found
|
||||
|
||||
Player *otherPlr = PlayerManager::getPlayer(otherSock);
|
||||
if (playerHasBuddyWithID(plrReq, otherPlr->iID))
|
||||
return;
|
||||
|
||||
resp.iPCUID = plrReq->PCStyle.iPC_UID;
|
||||
resp.iNameCheckFlag = plrReq->PCStyle.iNameCheck;
|
||||
|
||||
memcpy(resp.szFirstName, plrReq->PCStyle.szFirstName, sizeof(plrReq->PCStyle.szFirstName));
|
||||
memcpy(resp.szLastName, plrReq->PCStyle.szLastName, sizeof(plrReq->PCStyle.szLastName));
|
||||
otherSock->sendPacket(resp, P_FE2CL_REP_PC_FIND_NAME_MAKE_BUDDY_SUCC);
|
||||
}
|
||||
|
||||
// Accepting buddy request
|
||||
static void reqAcceptBuddy(CNSocket* sock, CNPacketData* data) {
|
||||
auto req = (sP_CL2FE_REQ_ACCEPT_MAKE_BUDDY*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(req->iBuddyID);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return; // sanity check
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(otherPlr->iID);
|
||||
|
||||
int slotA = getAvailableBuddySlot(plr);
|
||||
int slotB = getAvailableBuddySlot(otherPlr);
|
||||
if (slotA == -1 || slotB == -1)
|
||||
return; // sanity check
|
||||
|
||||
if (req->iAcceptFlag == 1 && plr->iID != otherPlr->iID && !playerHasBuddyWithID(plr, otherPlr->iID))
|
||||
{
|
||||
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, resp);
|
||||
|
||||
// A to B
|
||||
resp.iBuddySlot = slotA;
|
||||
resp.BuddyInfo.iID = otherPlr->iID;
|
||||
resp.BuddyInfo.iPCUID = otherPlr->PCStyle.iPC_UID;
|
||||
resp.BuddyInfo.iPCState = 1; // assumed to be online
|
||||
resp.BuddyInfo.bBlocked = 0; // not blocked by default
|
||||
resp.BuddyInfo.iGender = otherPlr->PCStyle.iGender; // shows the other player's gender
|
||||
resp.BuddyInfo.bFreeChat = 1; // shows whether or not the other player has freechat on (hardcoded for now)
|
||||
resp.BuddyInfo.iNameCheckFlag = otherPlr->PCStyle.iNameCheck;
|
||||
memcpy(resp.BuddyInfo.szFirstName, otherPlr->PCStyle.szFirstName, sizeof(resp.BuddyInfo.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, otherPlr->PCStyle.szLastName, sizeof(resp.BuddyInfo.szLastName));
|
||||
sock->sendPacket(resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC);
|
||||
plr->buddyIDs[slotA] = otherPlr->PCStyle.iPC_UID;
|
||||
//std::cout << "Buddy's ID: " << plr->buddyIDs[slotA] << std::endl;
|
||||
|
||||
// B to A, using the same struct
|
||||
resp.iBuddySlot = slotB;
|
||||
resp.BuddyInfo.iID = plr->iID;
|
||||
resp.BuddyInfo.iPCUID = plr->PCStyle.iPC_UID;
|
||||
resp.BuddyInfo.iPCState = 1;
|
||||
resp.BuddyInfo.bBlocked = 0;
|
||||
resp.BuddyInfo.iGender = plr->PCStyle.iGender;
|
||||
resp.BuddyInfo.bFreeChat = 1;
|
||||
resp.BuddyInfo.iNameCheckFlag = plr->PCStyle.iNameCheck;
|
||||
memcpy(resp.BuddyInfo.szFirstName, plr->PCStyle.szFirstName, sizeof(resp.BuddyInfo.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, plr->PCStyle.szLastName, sizeof(resp.BuddyInfo.szLastName));
|
||||
otherSock->sendPacket(resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC);
|
||||
otherPlr->buddyIDs[slotB] = plr->PCStyle.iPC_UID;
|
||||
//std::cout << "Buddy's ID: " << plr->buddyIDs[slotB] << std::endl;
|
||||
|
||||
// add record to db
|
||||
Database::addBuddyship(plr->iID, otherPlr->iID);
|
||||
}
|
||||
else
|
||||
{
|
||||
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, declineResp);
|
||||
|
||||
declineResp.iErrorCode = 6; // Buddy declined notification
|
||||
declineResp.iBuddyID = req->iBuddyID;
|
||||
declineResp.iBuddyPCUID = req->iBuddyPCUID;
|
||||
|
||||
otherSock->sendPacket(declineResp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Accepting buddy request from the find name request
|
||||
static void reqFindNameBuddyAccept(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY*)data->buf;
|
||||
|
||||
Player* plrReq = PlayerManager::getPlayer(sock);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, resp);
|
||||
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(pkt->iBuddyPCUID);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(pkt->iBuddyPCUID);
|
||||
|
||||
int slotA = getAvailableBuddySlot(plrReq);
|
||||
int slotB = getAvailableBuddySlot(otherPlr);
|
||||
if (slotA == -1 || slotB == -1)
|
||||
return; // sanity check
|
||||
|
||||
if (pkt->iAcceptFlag == 1 && plrReq->iID != otherPlr->iID && !playerHasBuddyWithID(plrReq, otherPlr->iID)) {
|
||||
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, resp);
|
||||
|
||||
// A to B
|
||||
resp.iBuddySlot = slotA;
|
||||
resp.BuddyInfo.iID = otherPlr->iID;
|
||||
resp.BuddyInfo.iPCUID = otherPlr->PCStyle.iPC_UID;
|
||||
resp.BuddyInfo.iPCState = 1; // assumed to be online
|
||||
resp.BuddyInfo.bBlocked = 0; // not blocked by default
|
||||
resp.BuddyInfo.iGender = otherPlr->PCStyle.iGender; // shows the other player's gender
|
||||
resp.BuddyInfo.bFreeChat = 1; // shows whether or not the other player has freechat on (hardcoded for now)
|
||||
resp.BuddyInfo.iNameCheckFlag = otherPlr->PCStyle.iNameCheck;
|
||||
memcpy(resp.BuddyInfo.szFirstName, otherPlr->PCStyle.szFirstName, sizeof(resp.BuddyInfo.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, otherPlr->PCStyle.szLastName, sizeof(resp.BuddyInfo.szLastName));
|
||||
sock->sendPacket(resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC);
|
||||
plrReq->buddyIDs[slotA] = otherPlr->PCStyle.iPC_UID;
|
||||
//std::cout << "Buddy's ID: " << plr->buddyIDs[slotA] << std::endl;
|
||||
|
||||
// B to A, using the same struct
|
||||
resp.iBuddySlot = slotB;
|
||||
resp.BuddyInfo.iID = plrReq->iID;
|
||||
resp.BuddyInfo.iPCUID = plrReq->PCStyle.iPC_UID;
|
||||
resp.BuddyInfo.iPCState = 1;
|
||||
resp.BuddyInfo.bBlocked = 0;
|
||||
resp.BuddyInfo.iGender = plrReq->PCStyle.iGender;
|
||||
resp.BuddyInfo.bFreeChat = 1;
|
||||
resp.BuddyInfo.iNameCheckFlag = plrReq->PCStyle.iNameCheck;
|
||||
memcpy(resp.BuddyInfo.szFirstName, plrReq->PCStyle.szFirstName, sizeof(resp.BuddyInfo.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, plrReq->PCStyle.szLastName, sizeof(resp.BuddyInfo.szLastName));
|
||||
otherSock->sendPacket(resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC);
|
||||
otherPlr->buddyIDs[slotB] = plrReq->PCStyle.iPC_UID;
|
||||
//std::cout << "Buddy's ID: " << plr->buddyIDs[slotB] << std::endl;
|
||||
|
||||
// add record to db
|
||||
Database::addBuddyship(plrReq->iID, otherPlr->iID);
|
||||
}
|
||||
else
|
||||
{
|
||||
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, declineResp);
|
||||
|
||||
declineResp.iErrorCode = 6; // Buddy declined notification
|
||||
declineResp.iBuddyPCUID = pkt->iBuddyPCUID;
|
||||
otherSock->sendPacket(declineResp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Getting buddy state
|
||||
static void reqPktGetBuddyState(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
/*
|
||||
* If the buddy list wasn't synced a second time yet, sync it.
|
||||
* Not sure why we have to do it again for the client not to trip up.
|
||||
*/
|
||||
if (!plr->buddiesSynced) {
|
||||
refreshBuddyList(sock);
|
||||
plr->buddiesSynced = true;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_GET_BUDDY_STATE_SUCC, resp);
|
||||
|
||||
for (int slot = 0; slot < 50; slot++) {
|
||||
resp.aBuddyState[slot] = PlayerManager::getPlayerFromID(plr->buddyIDs[slot]) != nullptr ? 1 : 0;
|
||||
resp.aBuddyID[slot] = plr->buddyIDs[slot];
|
||||
}
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_GET_BUDDY_STATE_SUCC);
|
||||
}
|
||||
|
||||
// Blocking the buddy
|
||||
static void reqBuddyBlock(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_SET_BUDDY_BLOCK*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// sanity checks
|
||||
if (pkt->iBuddySlot < 0 || pkt->iBuddySlot >= 50 || plr->buddyIDs[pkt->iBuddySlot] != pkt->iBuddyPCUID)
|
||||
return;
|
||||
|
||||
// save in DB
|
||||
Database::removeBuddyship(plr->iID, pkt->iBuddyPCUID);
|
||||
Database::addBlock(plr->iID, pkt->iBuddyPCUID);
|
||||
|
||||
// save serverside
|
||||
// since ID is already in the array, just set it to blocked
|
||||
plr->isBuddyBlocked[pkt->iBuddySlot] = true;
|
||||
|
||||
// send response
|
||||
INITSTRUCT(sP_FE2CL_REP_SET_BUDDY_BLOCK_SUCC, resp);
|
||||
resp.iBuddyPCUID = pkt->iBuddyPCUID;
|
||||
resp.iBuddySlot = pkt->iBuddySlot;
|
||||
sock->sendPacket(resp, P_FE2CL_REP_SET_BUDDY_BLOCK_SUCC);
|
||||
|
||||
// notify the other player he isn't a buddy anymore
|
||||
INITSTRUCT(sP_FE2CL_REP_REMOVE_BUDDY_SUCC, otherResp);
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(pkt->iBuddyPCUID);
|
||||
if (otherSock == nullptr)
|
||||
return; // other player isn't online, no broadcast needed
|
||||
Player* otherPlr = PlayerManager::getPlayer(otherSock);
|
||||
// search for the slot with the requesting player's ID
|
||||
otherResp.iBuddyPCUID = plr->PCStyle.iPC_UID;
|
||||
for (int i = 0; i < 50; i++) {
|
||||
if (otherPlr->buddyIDs[i] == plr->PCStyle.iPC_UID) {
|
||||
// remove buddy
|
||||
otherPlr->buddyIDs[i] = 0;
|
||||
// broadcast
|
||||
otherResp.iBuddySlot = i;
|
||||
otherSock->sendPacket(otherResp, P_FE2CL_REP_REMOVE_BUDDY_SUCC);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// block non-buddy
|
||||
static void reqPlayerBlock(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_SET_PC_BLOCK*)data->buf;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
int buddySlot = getAvailableBuddySlot(plr);
|
||||
if (buddySlot == -1)
|
||||
return;
|
||||
|
||||
// save in DB
|
||||
Database::addBlock(plr->iID, pkt->iBlock_PCUID);
|
||||
|
||||
// save serverside
|
||||
plr->buddyIDs[buddySlot] = pkt->iBlock_PCUID;
|
||||
plr->isBuddyBlocked[buddySlot] = true;
|
||||
|
||||
// send response
|
||||
INITSTRUCT(sP_FE2CL_REP_SET_PC_BLOCK_SUCC, resp);
|
||||
resp.iBlock_ID = pkt->iBlock_ID;
|
||||
resp.iBlock_PCUID = pkt->iBlock_PCUID;
|
||||
resp.iBuddySlot = buddySlot;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_SET_PC_BLOCK_SUCC);
|
||||
}
|
||||
|
||||
// Deleting the buddy
|
||||
static void reqBuddyDelete(CNSocket* sock, CNPacketData* data) {
|
||||
// note! this packet is used both for removing buddies and blocks
|
||||
auto pkt = (sP_CL2FE_REQ_REMOVE_BUDDY*)data->buf;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// remove buddy on our side
|
||||
INITSTRUCT(sP_FE2CL_REP_REMOVE_BUDDY_SUCC, resp);
|
||||
resp.iBuddyPCUID = pkt->iBuddyPCUID;
|
||||
resp.iBuddySlot = pkt->iBuddySlot;
|
||||
if (pkt->iBuddySlot < 0 || pkt->iBuddySlot >= 50 || plr->buddyIDs[pkt->iBuddySlot] != pkt->iBuddyPCUID)
|
||||
return; // sanity check
|
||||
|
||||
bool wasBlocked = plr->isBuddyBlocked[resp.iBuddySlot];
|
||||
plr->buddyIDs[resp.iBuddySlot] = 0;
|
||||
plr->isBuddyBlocked[resp.iBuddySlot] = false;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_REMOVE_BUDDY_SUCC);
|
||||
|
||||
// remove record from db
|
||||
Database::removeBuddyship(plr->PCStyle.iPC_UID, pkt->iBuddyPCUID);
|
||||
// try this too
|
||||
Database::removeBlock(plr->PCStyle.iPC_UID, pkt->iBuddyPCUID);
|
||||
|
||||
if (wasBlocked)
|
||||
return;
|
||||
|
||||
// remove buddy on their side, reusing the struct
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(pkt->iBuddyPCUID);
|
||||
if (otherSock == nullptr)
|
||||
return; // other player isn't online, no broadcast needed
|
||||
Player* otherPlr = PlayerManager::getPlayer(otherSock);
|
||||
// search for the slot with the requesting player's ID
|
||||
resp.iBuddyPCUID = plr->PCStyle.iPC_UID;
|
||||
for (int i = 0; i < 50; i++) {
|
||||
if (otherPlr->buddyIDs[i] == plr->PCStyle.iPC_UID) {
|
||||
// remove buddy
|
||||
otherPlr->buddyIDs[i] = 0;
|
||||
// broadcast
|
||||
resp.iBuddySlot = i;
|
||||
otherSock->sendPacket(resp, P_FE2CL_REP_REMOVE_BUDDY_SUCC);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Warping to buddy
|
||||
static void reqBuddyWarp(CNSocket* sock, CNPacketData* data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
auto pkt = (sP_CL2FE_REQ_PC_BUDDY_WARP*)data->buf;
|
||||
|
||||
if (pkt->iSlotNum < 0 || pkt->iSlotNum >= 50)
|
||||
return; // sanity check
|
||||
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(pkt->iBuddyPCUID);
|
||||
if (otherPlr == nullptr)
|
||||
return; // buddy offline
|
||||
|
||||
// if the player is instanced; no warp allowed
|
||||
if (otherPlr->instanceID != INSTANCE_OVERWORLD)
|
||||
goto fail;
|
||||
|
||||
// check if the players are at the same point in time (or in the training area or not)
|
||||
if (otherPlr->PCStyle2.iPayzoneFlag != plr->PCStyle2.iPayzoneFlag)
|
||||
goto fail;
|
||||
|
||||
// do not warp to players on monkeys
|
||||
if (otherPlr->onMonkey)
|
||||
goto fail;
|
||||
|
||||
// does the player disallow warping?
|
||||
if (otherPlr->unwarpable)
|
||||
goto fail;
|
||||
|
||||
// otherPlr->instanceID should always be INSTANCE_OVERWORLD at this point
|
||||
PlayerManager::sendPlayerTo(sock, otherPlr->x, otherPlr->y, otherPlr->z, otherPlr->instanceID);
|
||||
return;
|
||||
|
||||
fail:
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_BUDDY_WARP_FAIL, resp);
|
||||
resp.iBuddyPCUID = pkt->iBuddyPCUID;
|
||||
resp.iErrorCode = 0;
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_BUDDY_WARP_FAIL);
|
||||
}
|
||||
|
||||
void Buddies::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REQUEST_MAKE_BUDDY, requestBuddy);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY, reqBuddyByName);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ACCEPT_MAKE_BUDDY, reqAcceptBuddy);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY, reqFindNameBuddyAccept);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_GET_BUDDY_STATE, reqPktGetBuddyState);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SET_BUDDY_BLOCK, reqBuddyBlock);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SET_PC_BLOCK, reqPlayerBlock);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REMOVE_BUDDY, reqBuddyDelete);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_BUDDY_WARP, reqBuddyWarp);
|
||||
}
|
||||
10
src/Buddies.hpp
Normal file
10
src/Buddies.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
namespace Buddies {
|
||||
void init();
|
||||
|
||||
// Buddy list
|
||||
void refreshBuddyList(CNSocket* sock);
|
||||
}
|
||||
@@ -1,441 +0,0 @@
|
||||
#include "CNShardServer.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "ChatManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "BuddyManager.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
void BuddyManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REQUEST_MAKE_BUDDY, requestBuddy);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY, reqBuddyByName);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ACCEPT_MAKE_BUDDY, reqAcceptBuddy);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY, reqFindNameBuddyAccept);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE, reqBuddyFreechat);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE, reqBuddyMenuchat);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_GET_BUDDY_STATE, reqPktGetBuddyState);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SET_BUDDY_BLOCK, reqBuddyBlock);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REMOVE_BUDDY, reqBuddyDelete);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_BUDDY_WARP, reqBuddyWarp);
|
||||
}
|
||||
|
||||
// Buddy request
|
||||
void BuddyManager::requestBuddy(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_REQUEST_MAKE_BUDDY))
|
||||
return; // malformed packet
|
||||
|
||||
Player* plrReq = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plrReq == nullptr)
|
||||
return;
|
||||
|
||||
sP_CL2FE_REQ_REQUEST_MAKE_BUDDY* pkt = (sP_CL2FE_REQ_REQUEST_MAKE_BUDDY*)data->buf;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = sock;
|
||||
|
||||
for (auto pair : PlayerManager::players) {
|
||||
if (pair.second.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
|
||||
otherSock = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PlayerView& plr = PlayerManager::players[otherSock];
|
||||
|
||||
resp.iRequestID = plr.plr->iID;
|
||||
resp.iBuddyID = plr.plr->iID;
|
||||
resp.iBuddyPCUID = plr.plr->PCStyle.iPC_UID;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC)); // informs the player that the request was sent
|
||||
requestedBuddy(otherSock, plrReq, plr); // The other player will see the request
|
||||
|
||||
}
|
||||
|
||||
// Sending buddy request by player name
|
||||
void BuddyManager::reqBuddyByName(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY)) {
|
||||
return; // malformed packet
|
||||
}
|
||||
|
||||
sP_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY* pkt = (sP_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY*)data->buf;
|
||||
Player* plrReq = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plrReq == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_FIND_NAME_MAKE_BUDDY_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = sock;
|
||||
|
||||
int sizeOfRes = sizeof(pkt->szFirstName) / 9; // Maximum size of a player's first name
|
||||
int sizeOfLNRes = sizeof(pkt->szLastName) / 17; // Maximum size of a player's last name
|
||||
|
||||
for (auto pair : PlayerManager::players) {
|
||||
int sizeOfReq = sizeof(pair.second.plr->PCStyle.szFirstName) / 9;
|
||||
int sizeOfLNReq = sizeof(pair.second.plr->PCStyle.szLastName) / 17;
|
||||
if (BuddyManager::firstNameCheck(pair.second.plr->PCStyle.szFirstName, pkt->szFirstName, sizeOfReq, sizeOfRes) == true && BuddyManager::lastNameCheck(pair.second.plr->PCStyle.szLastName, pkt->szLastName, sizeOfLNReq, sizeOfLNRes) == true) { // This long line of gorgeous parameters is to check if the player's name matches :eyes:
|
||||
otherSock = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
resp.iPCUID = plrReq->PCStyle.iPC_UID;
|
||||
resp.iNameCheckFlag = plrReq->PCStyle.iNameCheck;
|
||||
|
||||
memcpy(resp.szFirstName, plrReq->PCStyle.szFirstName, sizeof(plrReq->PCStyle.szFirstName));
|
||||
memcpy(resp.szLastName, plrReq->PCStyle.szLastName, sizeof(plrReq->PCStyle.szLastName));
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_FIND_NAME_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_PC_FIND_NAME_MAKE_BUDDY_SUCC));
|
||||
|
||||
}
|
||||
|
||||
// Accepting buddy request
|
||||
void BuddyManager::reqAcceptBuddy(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_ACCEPT_MAKE_BUDDY))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_ACCEPT_MAKE_BUDDY* pkt = (sP_CL2FE_REQ_ACCEPT_MAKE_BUDDY*)data->buf;
|
||||
Player* plrReq = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plrReq == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = sock;
|
||||
|
||||
for (auto pair : PlayerManager::players) {
|
||||
if (pair.second.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
|
||||
otherSock = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PlayerView& plr = PlayerManager::players[otherSock];
|
||||
|
||||
if (pkt->iAcceptFlag == 1) {
|
||||
//resp.iBuddySlot = 0; // hard-coding this for now
|
||||
resp.BuddyInfo.iID = pkt->iBuddyID;
|
||||
resp.BuddyInfo.iPCUID = pkt->iBuddyPCUID;
|
||||
resp.BuddyInfo.iNameCheckFlag = plr.plr->PCStyle.iNameCheck;
|
||||
resp.BuddyInfo.iPCState = plr.plr->iPCState;
|
||||
resp.BuddyInfo.iGender = plr.plr->PCStyle.iGender;
|
||||
resp.BuddyInfo.bBlocked = 0;
|
||||
resp.BuddyInfo.bFreeChat = 1;
|
||||
|
||||
memcpy(resp.BuddyInfo.szFirstName, plr.plr->PCStyle.szFirstName, sizeof(plr.plr->PCStyle.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, plr.plr->PCStyle.szLastName, sizeof(plr.plr->PCStyle.szLastName));
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
|
||||
buddyList(sock, resp.BuddyInfo); // saves buddy data to player's buddylist
|
||||
if (plr.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
|
||||
resp.BuddyInfo.iID = plrReq->iID;
|
||||
resp.BuddyInfo.iPCUID = plrReq->PCStyle.iPC_UID;
|
||||
resp.BuddyInfo.iNameCheckFlag = plrReq->PCStyle.iNameCheck;
|
||||
resp.BuddyInfo.iPCState = plrReq->iPCState;
|
||||
resp.BuddyInfo.iGender = plrReq->PCStyle.iGender;
|
||||
resp.BuddyInfo.bBlocked = 0;
|
||||
resp.BuddyInfo.bFreeChat = 1;
|
||||
memcpy(resp.BuddyInfo.szFirstName, plrReq->PCStyle.szFirstName, sizeof(plrReq->PCStyle.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, plrReq->PCStyle.szLastName, sizeof(plrReq->PCStyle.szLastName));
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
|
||||
buddyList(otherSock, resp.BuddyInfo); // saves requester's data to this player's buddylist
|
||||
}
|
||||
} else {
|
||||
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, declineResp);
|
||||
|
||||
declineResp.iErrorCode = 6; // Buddy declined notification
|
||||
declineResp.iBuddyID = pkt->iBuddyID;
|
||||
declineResp.iBuddyPCUID = pkt->iBuddyPCUID;
|
||||
|
||||
otherSock->sendPacket((void*)&declineResp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL)); // tells the requester that the player declined
|
||||
}
|
||||
}
|
||||
|
||||
// Accepting buddy request from the find name request
|
||||
void BuddyManager::reqFindNameBuddyAccept(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY)) {
|
||||
return; // malformed packet
|
||||
}
|
||||
|
||||
sP_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY* pkt = (sP_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY*)data->buf;
|
||||
Player* plrReq = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plrReq == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = sock;
|
||||
|
||||
int sizeOfRes = sizeof(pkt->szFirstName) / 9;
|
||||
int sizeOfLNRes = sizeof(pkt->szLastName) / 17;
|
||||
|
||||
for (auto pair : PlayerManager::players) {
|
||||
int sizeOfReq = sizeof(pair.second.plr->PCStyle.szFirstName) / 9;
|
||||
int sizeOfLNReq = sizeof(pair.second.plr->PCStyle.szLastName) / 17;
|
||||
if (BuddyManager::firstNameCheck(pair.second.plr->PCStyle.szFirstName, pkt->szFirstName, sizeOfReq, sizeOfRes) == true && BuddyManager::lastNameCheck(pair.second.plr->PCStyle.szLastName, pkt->szLastName, sizeOfLNReq, sizeOfLNRes) == true) {
|
||||
otherSock = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PlayerView& plr = PlayerManager::players[otherSock];
|
||||
|
||||
if (pkt->iAcceptFlag == 1) {
|
||||
//resp.iBuddySlot = 0; // hard-coding this for now
|
||||
//resp.BuddyInfo.iID = plrReq->iID;
|
||||
resp.BuddyInfo.iPCUID = pkt->iBuddyPCUID;
|
||||
resp.BuddyInfo.iNameCheckFlag = plr.plr->PCStyle.iNameCheck;
|
||||
resp.BuddyInfo.iPCState = plr.plr->iPCState;
|
||||
resp.BuddyInfo.iGender = plr.plr->PCStyle.iGender;
|
||||
resp.BuddyInfo.bBlocked = 0;
|
||||
resp.BuddyInfo.bFreeChat = 1;
|
||||
|
||||
memcpy(resp.BuddyInfo.szFirstName, plr.plr->PCStyle.szFirstName, sizeof(plr.plr->PCStyle.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, plr.plr->PCStyle.szLastName, sizeof(plr.plr->PCStyle.szLastName));
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
|
||||
buddyList(sock, resp.BuddyInfo);
|
||||
|
||||
if (plr.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
|
||||
resp.BuddyInfo.iID = plrReq->iID;
|
||||
resp.BuddyInfo.iPCUID = plrReq->PCStyle.iPC_UID;
|
||||
resp.BuddyInfo.iNameCheckFlag = plrReq->PCStyle.iNameCheck;
|
||||
resp.BuddyInfo.iPCState = plrReq->iPCState;
|
||||
resp.BuddyInfo.iGender = plrReq->PCStyle.iGender;
|
||||
resp.BuddyInfo.bBlocked = 0;
|
||||
resp.BuddyInfo.bFreeChat = 1;
|
||||
|
||||
memcpy(resp.BuddyInfo.szFirstName, plrReq->PCStyle.szFirstName, sizeof(plrReq->PCStyle.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, plrReq->PCStyle.szLastName, sizeof(plrReq->PCStyle.szLastName));
|
||||
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
|
||||
buddyList(otherSock, resp.BuddyInfo);
|
||||
}
|
||||
} else {
|
||||
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, declineResp);
|
||||
|
||||
declineResp.iErrorCode = 6; // Buddy declined notification
|
||||
declineResp.iBuddyID = plr.plr->iID;
|
||||
declineResp.iBuddyPCUID = pkt->iBuddyPCUID;
|
||||
|
||||
otherSock->sendPacket((void*)&declineResp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Buddy freechatting
|
||||
void BuddyManager::reqBuddyFreechat(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE* pkt = (sP_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = sock;
|
||||
|
||||
resp.iFromPCUID = plr->PCStyle.iPC_UID;
|
||||
resp.iToPCUID = pkt->iBuddyPCUID;
|
||||
resp.iEmoteCode = pkt->iEmoteCode;
|
||||
memcpy(resp.szFreeChat, pkt->szFreeChat, sizeof(pkt->szFreeChat));
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC)); // shows the player that they sent the message to their buddy
|
||||
|
||||
for (auto pair : PlayerManager::players) {
|
||||
if (pair.second.plr->PCStyle.iPC_UID != plr->PCStyle.iPC_UID) {
|
||||
otherSock = pair.first;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC)); // sends the message to the buddy.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Buddy menuchat
|
||||
void BuddyManager::reqBuddyMenuchat(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE* pkt = (sP_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = sock;
|
||||
|
||||
resp.iFromPCUID = plr->PCStyle.iPC_UID;
|
||||
resp.iToPCUID = pkt->iBuddyPCUID;
|
||||
resp.iEmoteCode = pkt->iEmoteCode;
|
||||
memcpy(resp.szFreeChat, pkt->szFreeChat, sizeof(pkt->szFreeChat));
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC)); // shows the player that they sent the message to their buddy
|
||||
|
||||
for (auto pair : PlayerManager::players) {
|
||||
if (pair.second.plr->PCStyle.iPC_UID != plr->PCStyle.iPC_UID) {
|
||||
otherSock = pair.first;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC)); // sends the message to the buddy.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Getting buddy state
|
||||
void BuddyManager::reqPktGetBuddyState(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_FE2CL_REP_GET_BUDDY_STATE_SUCC, resp);
|
||||
INITSTRUCT(sBuddyBaseInfo, buddyInfo);
|
||||
|
||||
for (int BuddySlot = 0; BuddySlot < 50; BuddySlot++) {
|
||||
resp.aBuddyState[BuddySlot] = 1; // this sets every buddy to online. Will get the pcstate right directly from the DB.
|
||||
resp.aBuddyID[BuddySlot] = buddyInfo.iID;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_GET_BUDDY_STATE_SUCC, sizeof(sP_FE2CL_REP_GET_BUDDY_STATE_SUCC));
|
||||
}
|
||||
}
|
||||
|
||||
// Blocking the buddy
|
||||
void BuddyManager::reqBuddyBlock(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SET_BUDDY_BLOCK))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_SET_BUDDY_BLOCK* pkt = (sP_CL2FE_REQ_SET_BUDDY_BLOCK*)data->buf;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_SET_BUDDY_BLOCK_SUCC, resp);
|
||||
|
||||
resp.iBuddyPCUID = pkt->iBuddyPCUID;
|
||||
resp.iBuddySlot = pkt->iBuddySlot;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SET_BUDDY_BLOCK_SUCC, sizeof(sP_FE2CL_REP_SET_BUDDY_BLOCK_SUCC));
|
||||
|
||||
}
|
||||
|
||||
// Deleting the buddy
|
||||
void BuddyManager::reqBuddyDelete(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_REMOVE_BUDDY))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_REMOVE_BUDDY* pkt = (sP_CL2FE_REQ_REMOVE_BUDDY*)data->buf;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_REMOVE_BUDDY_SUCC, resp);
|
||||
|
||||
resp.iBuddyPCUID = pkt->iBuddyPCUID;
|
||||
resp.iBuddySlot = pkt->iBuddySlot;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_REMOVE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_REMOVE_BUDDY_SUCC));
|
||||
}
|
||||
|
||||
// Warping to buddy
|
||||
void BuddyManager::reqBuddyWarp(CNSocket* sock, CNPacketData* data) {} // stub
|
||||
|
||||
#pragma region Helper methods
|
||||
|
||||
void BuddyManager::requestedBuddy(CNSocket* sock, Player* plrReq, PlayerView& plr) {
|
||||
INITSTRUCT(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC_TO_ACCEPTER, resp);
|
||||
|
||||
resp.iRequestID = plrReq->iID;
|
||||
resp.iBuddyID = plr.plr->iID;
|
||||
|
||||
memcpy(resp.szFirstName, plrReq->PCStyle.szFirstName, sizeof(plrReq->PCStyle.szFirstName));
|
||||
memcpy(resp.szLastName, plrReq->PCStyle.szLastName, sizeof(plrReq->PCStyle.szLastName));
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC_TO_ACCEPTER, sizeof(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC_TO_ACCEPTER)); // player get the buddy request.
|
||||
|
||||
}
|
||||
|
||||
// Buddy list load
|
||||
void BuddyManager::buddyList(CNSocket* sock, sBuddyBaseInfo BuddyInfo) {
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC) + sizeof(sBuddyBaseInfo);
|
||||
uint8_t respbuf[4096];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC* resp = (sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC*)respbuf;
|
||||
sBuddyBaseInfo* respdata = (sBuddyBaseInfo*)(respbuf + sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC));
|
||||
|
||||
resp->iID = BuddyInfo.iID;
|
||||
resp->iPCUID = BuddyInfo.iPCUID;
|
||||
|
||||
for (int i = 0; i < resp->iBuddyCnt && i < resp->iListNum; i++) {
|
||||
respdata->iID = BuddyInfo.iID;
|
||||
respdata->iPCUID = BuddyInfo.iPCUID;
|
||||
respdata->iGender = BuddyInfo.iGender;
|
||||
respdata->iPCState = BuddyInfo.iPCState;
|
||||
respdata->iNameCheckFlag = BuddyInfo.iNameCheckFlag;
|
||||
respdata->bBlocked = 0;
|
||||
respdata->bFreeChat = 1;
|
||||
memcpy(respdata->szFirstName, BuddyInfo.szFirstName, sizeof(BuddyInfo.szFirstName));
|
||||
memcpy(respdata->szLastName, BuddyInfo.szLastName, sizeof(BuddyInfo.szLastName));
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC, resplen); // updates/loads player's buddy list
|
||||
|
||||
}
|
||||
|
||||
// If the requested player accepts the buddy request, the requester's buddylist will get loaded up.
|
||||
void BuddyManager::otherAcceptBuddy(CNSocket* sock, int32_t BuddyID, int64_t BuddyPCUID, sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC resp, Player* plr) {
|
||||
|
||||
// resp.iBuddySlot = 0; //hard-coding this for now
|
||||
resp.BuddyInfo.iID = BuddyID;
|
||||
resp.BuddyInfo.iPCUID = BuddyPCUID;
|
||||
resp.BuddyInfo.iNameCheckFlag = plr->PCStyle.iNameCheck;
|
||||
resp.BuddyInfo.iPCState = plr->iPCState;
|
||||
resp.BuddyInfo.iGender = plr->PCStyle.iGender;
|
||||
resp.BuddyInfo.bBlocked = 0;
|
||||
resp.BuddyInfo.bFreeChat = 1;
|
||||
|
||||
memcpy(resp.BuddyInfo.szFirstName, plr->PCStyle.szFirstName, sizeof(plr->PCStyle.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, plr->PCStyle.szLastName, sizeof(plr->PCStyle.szLastName));
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
|
||||
buddyList(sock, resp.BuddyInfo);
|
||||
}
|
||||
|
||||
// Check if the requested name matches the requested player's name
|
||||
bool BuddyManager::firstNameCheck(char16_t reqFirstName[], char16_t resFirstName[], int sizeOfReq, int sizeOfRes) {
|
||||
// If lengths of array are not equal means
|
||||
// array are not equal
|
||||
if (sizeOfReq != sizeOfRes)
|
||||
return false;
|
||||
|
||||
// Sort both arrays
|
||||
std::sort(reqFirstName, reqFirstName + sizeOfReq);
|
||||
std::sort(resFirstName, resFirstName + sizeOfRes);
|
||||
|
||||
// Linearly compare elements
|
||||
for (int i = 0; i < sizeOfReq; i++)
|
||||
if (reqFirstName[i] != resFirstName[i])
|
||||
return false;
|
||||
|
||||
// If all elements were same.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuddyManager::lastNameCheck(char16_t reqLastName[], char16_t resLastName[], int sizeOfLNReq, int sizeOfLNRes) {
|
||||
// If lengths of array are not equal means
|
||||
// array are not equal
|
||||
if (sizeOfLNReq != sizeOfLNRes)
|
||||
return false;
|
||||
|
||||
// Sort both arrays
|
||||
std::sort(reqLastName, reqLastName + sizeOfLNReq);
|
||||
std::sort(resLastName, resLastName + sizeOfLNRes);
|
||||
|
||||
// Linearly compare elements
|
||||
for (int i = 0; i < sizeOfLNReq; i++)
|
||||
if (reqLastName[i] != resLastName[i])
|
||||
return false;
|
||||
|
||||
// If all elements were same.
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
namespace BuddyManager {
|
||||
void init();
|
||||
|
||||
// Buddy requests
|
||||
void requestBuddy(CNSocket* sock, CNPacketData* data);
|
||||
void reqBuddyByName(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// Buddy accepting
|
||||
void reqAcceptBuddy(CNSocket* sock, CNPacketData* data);
|
||||
void reqFindNameBuddyAccept(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// Buddy Messaging
|
||||
void reqBuddyFreechat(CNSocket* sock, CNPacketData* data);
|
||||
void reqBuddyMenuchat(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// Getting buddy state
|
||||
void reqPktGetBuddyState(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// Blocking/removing buddies
|
||||
void reqBuddyBlock(CNSocket* sock, CNPacketData* data);
|
||||
void reqBuddyDelete(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// Buddy warping
|
||||
void reqBuddyWarp(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// helper methods
|
||||
void requestedBuddy(CNSocket* sock, Player* plrReq, PlayerView& plr);
|
||||
void buddyList(CNSocket* sock, sBuddyBaseInfo BuddyInfo); // updates the buddylist
|
||||
void otherAcceptBuddy(CNSocket* sock, int32_t BuddyID, int64_t BuddyPCUID, sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC resp, Player* plr); // tells the other player that they are now buddies with the requester.
|
||||
|
||||
// Name checks
|
||||
bool firstNameCheck(char16_t reqFirstName[], char16_t resFirstName[], int sizeOfReq, int sizeOfRes); // checks if the request and requested player's first names match
|
||||
bool lastNameCheck(char16_t reqLastName[], char16_t resLastName[], int sizeOfLNReq, int sizeOfLNRes); // checks if the request and requested player's last names match
|
||||
}
|
||||
198
src/Buffs.cpp
Normal file
198
src/Buffs.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
#include "Buffs.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
|
||||
using namespace Buffs;
|
||||
|
||||
void Buff::tick(time_t currTime) {
|
||||
auto it = stacks.begin();
|
||||
while(it != stacks.end()) {
|
||||
BuffStack& stack = *it;
|
||||
//if(onTick) onTick(self, this, currTime);
|
||||
|
||||
if(stack.durationTicks == 0) {
|
||||
BuffStack deadStack = stack;
|
||||
it = stacks.erase(it);
|
||||
if(onUpdate) onUpdate(self, this, ETBU_DEL, &deadStack);
|
||||
} else {
|
||||
if(stack.durationTicks > 0) stack.durationTicks--;
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Buff::combatTick(time_t currTime) {
|
||||
if(onCombatTick) onCombatTick(self, this, currTime);
|
||||
}
|
||||
|
||||
void Buff::clear() {
|
||||
while(!stacks.empty()) {
|
||||
BuffStack stack = stacks.back();
|
||||
stacks.pop_back();
|
||||
if(onUpdate) onUpdate(self, this, ETBU_DEL, &stack);
|
||||
}
|
||||
}
|
||||
|
||||
void Buff::clear(BuffClass buffClass) {
|
||||
auto it = stacks.begin();
|
||||
while(it != stacks.end()) {
|
||||
BuffStack& stack = *it;
|
||||
if(stack.buffStackClass == buffClass) {
|
||||
BuffStack deadStack = stack;
|
||||
it = stacks.erase(it);
|
||||
if(onUpdate) onUpdate(self, this, ETBU_DEL, &deadStack);
|
||||
} else it++;
|
||||
}
|
||||
}
|
||||
|
||||
void Buff::addStack(BuffStack* stack) {
|
||||
stacks.push_back(*stack);
|
||||
if(onUpdate) onUpdate(self, this, ETBU_ADD, &stacks.back());
|
||||
}
|
||||
|
||||
bool Buff::hasClass(BuffClass buffClass) {
|
||||
for(BuffStack& stack : stacks) {
|
||||
if(stack.buffStackClass == buffClass)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
BuffClass Buff::maxClass() {
|
||||
BuffClass buffClass = BuffClass::NONE;
|
||||
for(BuffStack& stack : stacks) {
|
||||
if(stack.buffStackClass > buffClass)
|
||||
buffClass = stack.buffStackClass;
|
||||
}
|
||||
return buffClass;
|
||||
}
|
||||
|
||||
int Buff::getValue(BuffValueSelector selector) {
|
||||
if(isStale()) return 0;
|
||||
|
||||
int value = selector == BuffValueSelector::NET_TOTAL ? 0 : stacks.front().value;
|
||||
for(BuffStack& stack : stacks) {
|
||||
switch(selector)
|
||||
{
|
||||
case BuffValueSelector::NET_TOTAL:
|
||||
value += stack.value;
|
||||
break;
|
||||
case BuffValueSelector::MIN_VALUE:
|
||||
if(stack.value < value) value = stack.value;
|
||||
break;
|
||||
case BuffValueSelector::MAX_VALUE:
|
||||
if(stack.value > value) value = stack.value;
|
||||
break;
|
||||
case BuffValueSelector::MIN_MAGNITUDE:
|
||||
if(abs(stack.value) < abs(value)) value = stack.value;
|
||||
break;
|
||||
case BuffValueSelector::MAX_MAGNITUDE:
|
||||
default:
|
||||
if(abs(stack.value) > abs(value)) value = stack.value;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
EntityRef Buff::getLastSource() {
|
||||
if(stacks.empty())
|
||||
return self;
|
||||
return stacks.back().source;
|
||||
}
|
||||
|
||||
bool Buff::isStale() {
|
||||
return stacks.empty();
|
||||
}
|
||||
|
||||
/* This will practically never do anything important, but it's here just in case */
|
||||
void Buff::updateCallbacks(BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fOnCombatTick) {
|
||||
if(!onUpdate) onUpdate = fOnUpdate;
|
||||
if(!onCombatTick) onCombatTick = fOnCombatTick;
|
||||
}
|
||||
|
||||
#pragma region Handlers
|
||||
void Buffs::timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||
|
||||
if(self.kind != EntityKind::PLAYER)
|
||||
return; // not implemented
|
||||
|
||||
Player* plr = (Player*)self.getEntity();
|
||||
if(plr == nullptr)
|
||||
return; // sanity check
|
||||
|
||||
if(status == ETBU_DEL && !buff->isStale())
|
||||
return; // no premature effect deletion
|
||||
|
||||
int cbf = plr->getCompositeCondition();
|
||||
sTimeBuff payload{};
|
||||
if(status == ETBU_ADD) {
|
||||
payload.iValue = buff->getValue(BuffValueSelector::MAX_MAGNITUDE);
|
||||
// we need to explicitly add the ECSB for this buff,
|
||||
// in case this is the first stack in and the entry
|
||||
// in the buff map doesn't yet exist
|
||||
if(buff->id > 0) cbf |= CSB_FROM_ECSB(buff->id);
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
||||
pkt.eCSTB = buff->id; // eCharStatusTimeBuffID
|
||||
pkt.eTBU = status; // eTimeBuffUpdate
|
||||
pkt.eTBT = (int)stack->buffStackClass;
|
||||
pkt.iConditionBitFlag = cbf;
|
||||
pkt.TimeBuff = payload;
|
||||
self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
}
|
||||
|
||||
void Buffs::timeBuffTick(EntityRef self, Buff* buff) {
|
||||
if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||
return; // not implemented
|
||||
Entity* entity = self.getEntity();
|
||||
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK, pkt);
|
||||
pkt.eCT = combatant->getCharType();
|
||||
pkt.iID = combatant->getID();
|
||||
pkt.iTB_ID = buff->id;
|
||||
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
||||
}
|
||||
|
||||
void Buffs::timeBuffTimeout(EntityRef self) {
|
||||
if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||
return; // not a combatant
|
||||
Entity* entity = self.getEntity();
|
||||
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players
|
||||
int32_t eCharType = combatant->getCharType();
|
||||
pkt.eCT = eCharType == 4 ? 2 : eCharType; // convention not followed by client here
|
||||
pkt.iID = combatant->getID();
|
||||
pkt.iConditionBitFlag = combatant->getCompositeCondition();
|
||||
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||
}
|
||||
|
||||
void Buffs::tickDrain(EntityRef self, Buff* buff, int mult) {
|
||||
if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||
return; // not implemented
|
||||
Entity* entity = self.getEntity();
|
||||
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||
int damage = combatant->getMaxHP() / 100 * mult;
|
||||
int dealt = combatant->takeDamage(buff->getLastSource(), damage);
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
|
||||
pkt->iID = self.id;
|
||||
pkt->eCT = combatant->getCharType();
|
||||
pkt->iTB_ID = ECSB_BOUNDINGBALL;
|
||||
|
||||
sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
||||
drain->iDamage = dealt;
|
||||
drain->iHP = combatant->getCurrentHP();
|
||||
drain->eCT = pkt->eCT;
|
||||
drain->iID = pkt->iID;
|
||||
|
||||
NPCManager::sendToViewable(self.getEntity(), (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
||||
}
|
||||
#pragma endregion
|
||||
93
src/Buffs.hpp
Normal file
93
src/Buffs.hpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "EntityRef.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
/* forward declaration(s) */
|
||||
class Buff;
|
||||
template<class... Types>
|
||||
using BuffCallback = std::function<void(EntityRef, Buff*, Types...)>;
|
||||
|
||||
#define CSB_FROM_ECSB(x) (1 << (x - 1))
|
||||
|
||||
enum class BuffClass {
|
||||
NONE = ETBT_NONE,
|
||||
NANO = ETBT_NANO,
|
||||
GROUP_NANO = ETBT_GROUPNANO,
|
||||
EGG = ETBT_SHINY,
|
||||
ENVIRONMENT = ETBT_LANDEFFECT,
|
||||
ITEM = ETBT_ITEM,
|
||||
CASH_ITEM = ETBT_CASHITEM
|
||||
};
|
||||
|
||||
enum class BuffValueSelector {
|
||||
MAX_VALUE,
|
||||
MIN_VALUE,
|
||||
MAX_MAGNITUDE,
|
||||
MIN_MAGNITUDE,
|
||||
NET_TOTAL
|
||||
};
|
||||
|
||||
struct BuffStack {
|
||||
int durationTicks;
|
||||
int value;
|
||||
EntityRef source;
|
||||
BuffClass buffStackClass;
|
||||
};
|
||||
|
||||
class Buff {
|
||||
private:
|
||||
EntityRef self;
|
||||
std::vector<BuffStack> stacks;
|
||||
|
||||
public:
|
||||
int id;
|
||||
/* called just after a stack is added or removed */
|
||||
BuffCallback<int, BuffStack*> onUpdate;
|
||||
/* called when the buff is combat-ticked */
|
||||
BuffCallback<time_t> onCombatTick;
|
||||
|
||||
void tick(time_t);
|
||||
void combatTick(time_t);
|
||||
void clear();
|
||||
void clear(BuffClass buffClass);
|
||||
void addStack(BuffStack* stack);
|
||||
|
||||
/*
|
||||
* Sometimes we need to determine if a buff
|
||||
* is covered by a certain class, ex: nano
|
||||
* vs. coco egg in the case of infection protection
|
||||
*/
|
||||
bool hasClass(BuffClass buffClass);
|
||||
BuffClass maxClass();
|
||||
|
||||
int getValue(BuffValueSelector selector);
|
||||
EntityRef getLastSource();
|
||||
|
||||
/*
|
||||
* In general, a Buff object won't exist
|
||||
* unless it has stacks. However, when
|
||||
* popping stacks during iteration (onExpire),
|
||||
* stacks will be empty for a brief moment
|
||||
* when the last stack is popped.
|
||||
*/
|
||||
bool isStale();
|
||||
|
||||
void updateCallbacks(BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fonTick);
|
||||
|
||||
Buff(int iid, EntityRef pSelf, BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fOnCombatTick, BuffStack* firstStack)
|
||||
: self(pSelf), id(iid), onUpdate(fOnUpdate), onCombatTick(fOnCombatTick) {
|
||||
addStack(firstStack);
|
||||
}
|
||||
};
|
||||
|
||||
namespace Buffs {
|
||||
void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack);
|
||||
void timeBuffTick(EntityRef self, Buff* buff);
|
||||
void timeBuffTimeout(EntityRef self);
|
||||
void tickDrain(EntityRef self, Buff* buff, int mult);
|
||||
}
|
||||
379
src/BuiltinCommands.cpp
Normal file
379
src/BuiltinCommands.cpp
Normal file
@@ -0,0 +1,379 @@
|
||||
#include "BuiltinCommands.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Chat.hpp"
|
||||
#include "Nanos.hpp"
|
||||
|
||||
// helper function, not a packet handler
|
||||
void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) {
|
||||
auto setData = (sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// HACK: work around the invisible weapon bug
|
||||
if (setData->iSpecialStateFlag == CN_SPECIAL_STATE_FLAG__FULL_UI)
|
||||
Items::updateEquips(sock, plr);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_SPECIAL_STATE_CHANGE, response);
|
||||
|
||||
plr->iSpecialState ^= setData->iSpecialStateFlag;
|
||||
|
||||
response.iPC_ID = setData->iPC_ID;
|
||||
response.iReqSpecialStateFlag = setData->iSpecialStateFlag;
|
||||
response.iSpecialState = plr->iSpecialState;
|
||||
|
||||
sock->sendPacket(response, P_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC);
|
||||
PlayerManager::sendToViewable(sock, response, P_FE2CL_PC_SPECIAL_STATE_CHANGE);
|
||||
}
|
||||
|
||||
static void setGMSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
if (PlayerManager::getPlayer(sock)->accountLevel > 30)
|
||||
return;
|
||||
|
||||
BuiltinCommands::setSpecialState(sock, data);
|
||||
}
|
||||
|
||||
static void gotoPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
if (plr->accountLevel > 50)
|
||||
return;
|
||||
|
||||
auto gotoData = (sP_CL2FE_REQ_PC_GOTO*)data->buf;
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "P_CL2FE_REQ_PC_GOTO:" << std::endl;
|
||||
std::cout << "\tX: " << gotoData->iToX << std::endl;
|
||||
std::cout << "\tY: " << gotoData->iToY << std::endl;
|
||||
std::cout << "\tZ: " << gotoData->iToZ << std::endl;
|
||||
)
|
||||
|
||||
PlayerManager::sendPlayerTo(sock, gotoData->iToX, gotoData->iToY, gotoData->iToZ, INSTANCE_OVERWORLD);
|
||||
}
|
||||
|
||||
static void setValuePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
if (plr->accountLevel > 50)
|
||||
return;
|
||||
|
||||
auto setData = (sP_CL2FE_GM_REQ_PC_SET_VALUE*)data->buf;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_GM_REP_PC_SET_VALUE, response);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "P_CL2FE_GM_REQ_PC_SET_VALUE:" << std::endl;
|
||||
std::cout << "\tPC_ID: " << setData->iPC_ID << std::endl;
|
||||
std::cout << "\tSetValueType: " << setData->iSetValueType << std::endl;
|
||||
std::cout << "\tSetValue: " << setData->iSetValue << std::endl;
|
||||
)
|
||||
|
||||
// Handle serverside value-changes
|
||||
switch (setData->iSetValueType) {
|
||||
case CN_GM_SET_VALUE_TYPE__HP:
|
||||
response.iSetValue = plr->HP = setData->iSetValue;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__WEAPON_BATTERY :
|
||||
plr->batteryW = setData->iSetValue;
|
||||
|
||||
// caps
|
||||
if (plr->batteryW > 9999)
|
||||
plr->batteryW = 9999;
|
||||
|
||||
response.iSetValue = plr->batteryW;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__NANO_BATTERY:
|
||||
plr->batteryN = setData->iSetValue;
|
||||
|
||||
// caps
|
||||
if (plr->batteryN > 9999)
|
||||
plr->batteryN = 9999;
|
||||
|
||||
response.iSetValue = plr->batteryN;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__FUSION_MATTER:
|
||||
Missions::updateFusionMatter(sock, setData->iSetValue - plr->fusionmatter);
|
||||
response.iSetValue = plr->fusionmatter;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__CANDY:
|
||||
response.iSetValue = plr->money = setData->iSetValue;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__SPEED:
|
||||
case CN_GM_SET_VALUE_TYPE__JUMP:
|
||||
response.iSetValue = setData->iSetValue;
|
||||
break;
|
||||
}
|
||||
|
||||
response.iPC_ID = setData->iPC_ID;
|
||||
response.iSetValueType = setData->iSetValueType;
|
||||
|
||||
sock->sendPacket(response, P_FE2CL_GM_REP_PC_SET_VALUE);
|
||||
|
||||
// if one lowers their own health to 0, make sure others can see it
|
||||
if (plr->HP <= 0) {
|
||||
INITSTRUCT(sP_FE2CL_PC_SUDDEN_DEAD, dead);
|
||||
|
||||
dead.iPC_ID = plr->iID;
|
||||
dead.iDamage = plr->HP;
|
||||
dead.iHP = plr->HP = 0;
|
||||
|
||||
PlayerManager::sendToViewable(sock, dead, P_FE2CL_PC_SUDDEN_DEAD);
|
||||
}
|
||||
}
|
||||
|
||||
static void setGMSpecialOnOff(CNSocket *sock, CNPacketData *data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// access check
|
||||
if (plr->accountLevel > 30)
|
||||
return;
|
||||
|
||||
auto req = (sP_CL2FE_GM_REQ_TARGET_PC_SPECIAL_STATE_ONOFF*)data->buf;
|
||||
|
||||
CNSocket *otherSock = PlayerManager::getSockFromAny(req->eTargetSearchBy, req->iTargetPC_ID, req->iTargetPC_UID,
|
||||
AUTOU16TOU8(req->szTargetPC_FirstName), AUTOU16TOU8(req->szTargetPC_LastName));
|
||||
if (otherSock == nullptr) {
|
||||
Chat::sendServerMessage(sock, "player not found");
|
||||
return;
|
||||
}
|
||||
|
||||
Player *otherPlr = PlayerManager::getPlayer(otherSock);
|
||||
if (req->iONOFF)
|
||||
otherPlr->iSpecialState |= req->iSpecialStateFlag;
|
||||
else
|
||||
otherPlr->iSpecialState &= ~req->iSpecialStateFlag;
|
||||
|
||||
// this is only used for muting players, so no need to update the client since that logic is server-side
|
||||
}
|
||||
|
||||
static void locatePlayer(CNSocket *sock, CNPacketData *data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// access check
|
||||
if (plr->accountLevel > 30)
|
||||
return;
|
||||
|
||||
auto req = (sP_CL2FE_GM_REQ_PC_LOCATION*)data->buf;
|
||||
|
||||
CNSocket *otherSock = PlayerManager::getSockFromAny(req->eTargetSearchBy, req->iTargetPC_ID, req->iTargetPC_UID,
|
||||
AUTOU16TOU8(req->szTargetPC_FirstName), AUTOU16TOU8(req->szTargetPC_LastName));
|
||||
if (otherSock == nullptr) {
|
||||
Chat::sendServerMessage(sock, "player not found");
|
||||
return;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_GM_REP_PC_LOCATION, resp);
|
||||
Player *otherPlr = PlayerManager::getPlayer(otherSock);
|
||||
|
||||
resp.iTargetPC_UID = otherPlr->accountId;
|
||||
resp.iTargetPC_ID = otherPlr->iID;
|
||||
resp.iShardID = 0; // sharding is unsupported
|
||||
resp.iMapType = !!PLAYERID(otherPlr->instanceID); // private instance or not
|
||||
resp.iMapID = PLAYERID(otherPlr->instanceID);
|
||||
resp.iMapNum = MAPNUM(otherPlr->instanceID);
|
||||
resp.iX = otherPlr->x;
|
||||
resp.iY = otherPlr->y;
|
||||
resp.iZ = otherPlr->z;
|
||||
|
||||
memcpy(resp.szTargetPC_FirstName, otherPlr->PCStyle.szFirstName, sizeof(resp.szTargetPC_FirstName));
|
||||
memcpy(resp.szTargetPC_LastName, otherPlr->PCStyle.szLastName, sizeof(resp.szTargetPC_LastName));
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_GM_REP_PC_LOCATION);
|
||||
}
|
||||
|
||||
static void kickPlayer(CNSocket *sock, CNPacketData *data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// access check
|
||||
if (plr->accountLevel > 30)
|
||||
return;
|
||||
|
||||
auto req = (sP_CL2FE_GM_REQ_KICK_PLAYER*)data->buf;
|
||||
|
||||
CNSocket *otherSock = PlayerManager::getSockFromAny(req->eTargetSearchBy, req->iTargetPC_ID, req->iTargetPC_UID,
|
||||
AUTOU16TOU8(req->szTargetPC_FirstName), AUTOU16TOU8(req->szTargetPC_LastName));
|
||||
if (otherSock == nullptr) {
|
||||
Chat::sendServerMessage(sock, "player not found");
|
||||
return;
|
||||
}
|
||||
|
||||
Player *otherPlr = PlayerManager::getPlayer(otherSock);
|
||||
|
||||
if (plr->accountLevel > otherPlr->accountLevel) {
|
||||
Chat::sendServerMessage(sock, "player has higher access level");
|
||||
return;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, response);
|
||||
|
||||
response.iID = otherPlr->iID;
|
||||
response.iExitCode = 3; // "a GM has terminated your connection"
|
||||
|
||||
// send to target player
|
||||
otherSock->sendPacket(response, P_FE2CL_REP_PC_EXIT_SUCC);
|
||||
|
||||
// ensure that the connection has terminated
|
||||
otherSock->kill();
|
||||
}
|
||||
|
||||
static void warpToPlayer(CNSocket *sock, CNPacketData *data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// access check
|
||||
if (plr->accountLevel > 30)
|
||||
return;
|
||||
|
||||
auto req = (sP_CL2FE_REQ_PC_WARP_TO_PC*)data->buf;
|
||||
|
||||
Player *otherPlr = PlayerManager::getPlayerFromID(req->iPC_ID);
|
||||
if (otherPlr == nullptr) {
|
||||
Chat::sendServerMessage(sock, "player not found");
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerManager::sendPlayerTo(sock, otherPlr->x, otherPlr->y, otherPlr->z, otherPlr->instanceID);
|
||||
}
|
||||
|
||||
// GM teleport command
|
||||
static void teleportPlayer(CNSocket *sock, CNPacketData *data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// access check
|
||||
if (plr->accountLevel > 30)
|
||||
return;
|
||||
|
||||
auto req = (sP_CL2FE_GM_REQ_TARGET_PC_TELEPORT*)data->buf;
|
||||
|
||||
// player to teleport
|
||||
CNSocket *targetSock = PlayerManager::getSockFromAny(req->eTargetPCSearchBy, req->iTargetPC_ID, req->iTargetPC_UID,
|
||||
AUTOU16TOU8(req->szTargetPC_FirstName), AUTOU16TOU8(req->szTargetPC_LastName));
|
||||
if (targetSock == nullptr) {
|
||||
Chat::sendServerMessage(sock, "player to teleport not found");
|
||||
return;
|
||||
}
|
||||
|
||||
CNSocket *goalSock = nullptr;
|
||||
Player *goalPlr = nullptr;
|
||||
Player *targetPlr = nullptr;
|
||||
uint64_t instance = plr->instanceID;
|
||||
const int unstickRange = 400;
|
||||
|
||||
switch ((eCN_GM_TeleportType)req->eTeleportType) {
|
||||
case eCN_GM_TeleportType::MyLocation:
|
||||
PlayerManager::sendPlayerTo(targetSock, plr->x, plr->y, plr->z, instance);
|
||||
break;
|
||||
case eCN_GM_TeleportType::MapXYZ:
|
||||
instance = req->iToMap;
|
||||
// fallthrough
|
||||
case eCN_GM_TeleportType::XYZ:
|
||||
PlayerManager::sendPlayerTo(targetSock, req->iToX, req->iToY, req->iToZ, instance);
|
||||
break;
|
||||
case eCN_GM_TeleportType::SomeoneLocation:
|
||||
// player to teleport to
|
||||
goalSock = PlayerManager::getSockFromAny(req->eGoalPCSearchBy, req->iGoalPC_ID, req->iGoalPC_UID,
|
||||
AUTOU16TOU8(req->szGoalPC_FirstName), AUTOU16TOU8(req->szGoalPC_LastName));
|
||||
if (goalSock == nullptr) {
|
||||
Chat::sendServerMessage(sock, "teleportation target player not found");
|
||||
return;
|
||||
}
|
||||
goalPlr = PlayerManager::getPlayer(goalSock);
|
||||
|
||||
PlayerManager::sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID);
|
||||
break;
|
||||
case eCN_GM_TeleportType::Unstick:
|
||||
targetPlr = PlayerManager::getPlayer(targetSock);
|
||||
|
||||
PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + Rand::rand(unstickRange),
|
||||
targetPlr->y - unstickRange/2 + Rand::rand(unstickRange), targetPlr->z + 80);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void itemGMGiveHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto itemreq = (sP_CL2FE_REQ_PC_GIVE_ITEM*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr->accountLevel > 50) {
|
||||
// TODO: send fail packet
|
||||
return;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_GIVE_ITEM_SUCC, resp);
|
||||
|
||||
if (itemreq->eIL == 1) {
|
||||
|
||||
if (Items::ItemData.find(std::pair<int32_t, int32_t>(itemreq->Item.iID, itemreq->Item.iType)) == Items::ItemData.end()
|
||||
|| itemreq->Item.iType < 0 || itemreq->Item.iType > 10) {
|
||||
// invalid item
|
||||
std::cout << "[WARN] Item id " << itemreq->Item.iID << " with type " << itemreq->Item.iType << " is invalid (give item)" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemreq->Item.iType == 10) {
|
||||
// item is vehicle, set expiration date
|
||||
// set time limit: current time + 7days
|
||||
itemreq->Item.iTimeLimit = getTimestamp() + 604800;
|
||||
}
|
||||
|
||||
plr->Inven[itemreq->iSlotNum] = itemreq->Item;
|
||||
} else if (itemreq->eIL == 2) {
|
||||
int id = itemreq->Item.iID;
|
||||
int slot = Missions::findQSlot(plr, id);
|
||||
|
||||
if (slot == -1) {
|
||||
std::cout << "[WARN] Player has no room for quest items" << std::endl;
|
||||
return;
|
||||
}
|
||||
if (id != 0)
|
||||
std::cout << "new qitem in slot " << slot << std::endl;
|
||||
|
||||
// update player
|
||||
if (id != 0) {
|
||||
plr->QInven[slot].iType = 8;
|
||||
plr->QInven[slot].iID = id;
|
||||
plr->QInven[slot].iOpt += itemreq->Item.iOpt;
|
||||
|
||||
// destroy the item if its 0
|
||||
if (plr->QInven[slot].iOpt == 0)
|
||||
memset(&plr->QInven[slot], 0, sizeof(sItemBase));
|
||||
}
|
||||
std::cout << "Item id " << id << " is in slot " << slot << " of count " << plr->QInven[slot].iOpt << std::endl;
|
||||
}
|
||||
|
||||
resp.eIL = itemreq->eIL;
|
||||
resp.iSlotNum = itemreq->iSlotNum;
|
||||
resp.Item = itemreq->Item;
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_GIVE_ITEM_SUCC);
|
||||
}
|
||||
|
||||
static void nanoGMGiveHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto nano = (sP_CL2FE_REQ_PC_GIVE_NANO*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr->accountLevel > 50)
|
||||
return;
|
||||
|
||||
// Add nano to player
|
||||
Nanos::addNano(sock, nano->iNanoID, 0);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << PlayerManager::getPlayerName(plr) << " requested to add nano id: " << nano->iNanoID << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
void BuiltinCommands::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GOTO, gotoPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_SET_VALUE, setValuePlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GIVE_ITEM, itemGMGiveHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GIVE_NANO, nanoGMGiveHandler);
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH, setGMSpecialSwitchPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_TARGET_PC_SPECIAL_STATE_ONOFF, setGMSpecialOnOff);
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_LOCATION, locatePlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_KICK_PLAYER, kickPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_WARP_TO_PC, warpToPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_TARGET_PC_TELEPORT, teleportPlayer);
|
||||
}
|
||||
9
src/BuiltinCommands.hpp
Normal file
9
src/BuiltinCommands.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
namespace BuiltinCommands {
|
||||
void init();
|
||||
|
||||
void setSpecialState(CNSocket *sock, CNPacketData *data);
|
||||
};
|
||||
@@ -1,445 +0,0 @@
|
||||
#include "CNLoginServer.hpp"
|
||||
#include "CNShared.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "Database.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include <regex>
|
||||
#include "contrib/bcrypt/BCrypt.hpp"
|
||||
|
||||
#include "settings.hpp"
|
||||
|
||||
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
|
||||
|
||||
CNLoginServer::CNLoginServer(uint16_t p) {
|
||||
port = p;
|
||||
pHandler = &CNLoginServer::handlePacket;
|
||||
init();
|
||||
}
|
||||
|
||||
void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
printPacket(data, CL2LS);
|
||||
|
||||
switch (data->type) {
|
||||
case P_CL2LS_REQ_LOGIN: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_LOGIN))
|
||||
return; // ignore the malformed packet
|
||||
|
||||
sP_CL2LS_REQ_LOGIN* login = (sP_CL2LS_REQ_LOGIN*)data->buf;
|
||||
// TODO: implement better way of sending credentials
|
||||
std::string userLogin((char*)login->szCookie_TEGid);
|
||||
std::string userPassword((char*)login->szCookie_authid);
|
||||
|
||||
/*
|
||||
* Sometimes the client sends garbage cookie data.
|
||||
* Validate it as normal credentials instead of using a length check before falling back.
|
||||
*/
|
||||
if (!CNLoginServer::isLoginDataGood(userLogin, userPassword)) {
|
||||
/*
|
||||
* The std::string -> char* -> std::string maneuver should remove any
|
||||
* trailing garbage after the null terminator.
|
||||
*/
|
||||
userLogin = std::string(U16toU8(login->szID).c_str());
|
||||
userPassword = std::string(U16toU8(login->szPassword).c_str());
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
int errorCode = 0;
|
||||
|
||||
// checking regex
|
||||
if (!CNLoginServer::isLoginDataGood(userLogin, userPassword)) {
|
||||
errorCode = (int)LoginError::LOGIN_ERROR;
|
||||
} else {
|
||||
std::unique_ptr<Database::Account> findUser = Database::findAccount(userLogin);
|
||||
// if account not found, make new one
|
||||
if (findUser == nullptr) {
|
||||
loginSessions[sock] = CNLoginData();
|
||||
loginSessions[sock].userID = Database::addAccount(userLogin, userPassword);
|
||||
loginSessions[sock].slot = 1;
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
success = true;
|
||||
|
||||
// if user exists, check if password is correct
|
||||
} else if (CNLoginServer::isPasswordCorrect(findUser->Password, userPassword)) {
|
||||
/*calling this here to timestamp login attempt,
|
||||
* in order to make duplicate exit sanity check work*/
|
||||
Database::updateSelected(findUser->AccountID, findUser->Selected);
|
||||
// check if account isn't currently in use
|
||||
if (CNLoginServer::isAccountInUse(findUser->AccountID)) {
|
||||
errorCode = (int)LoginError::ID_ALREADY_IN_USE;
|
||||
} else { // if not, login success
|
||||
loginSessions[sock] = CNLoginData();
|
||||
loginSessions[sock].userID = findUser->AccountID;
|
||||
loginSessions[sock].slot = findUser->Selected;
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
success = true;
|
||||
}
|
||||
} else {
|
||||
errorCode = (int)LoginError::ID_AND_PASSWORD_DO_NOT_MATCH;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (success) {
|
||||
std::vector<Player> characters = Database::getCharacters(loginSessions[sock].userID);
|
||||
int charCount = characters.size();
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REP_LOGIN_SUCC, resp);
|
||||
// set username in resp packet
|
||||
memcpy(resp.szID, login->szID, sizeof(char16_t) * 33);
|
||||
|
||||
resp.iCharCount = charCount;
|
||||
resp.iSlotNum = loginSessions[sock].slot;
|
||||
resp.iPaymentFlag = 1;
|
||||
resp.iOpenBetaFlag = 0;
|
||||
resp.uiSvrTime = getTime();
|
||||
|
||||
// send the resp in with original key
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_SUCC, sizeof(sP_LS2CL_REP_LOGIN_SUCC));
|
||||
|
||||
// update keys
|
||||
sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1));
|
||||
sock->setFEKey(CNSocketEncryption::createNewKey((uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]), login->iClientVerC, 1));
|
||||
|
||||
// now send the characters :)
|
||||
std::vector<Player>::iterator it;
|
||||
for (it = characters.begin(); it != characters.end(); it++) {
|
||||
sP_LS2CL_REP_CHAR_INFO charInfo = sP_LS2CL_REP_CHAR_INFO();
|
||||
|
||||
charInfo.iSlot = (int8_t)it->slot;
|
||||
charInfo.iLevel = (int16_t)it->level;
|
||||
charInfo.sPC_Style = it->PCStyle;
|
||||
charInfo.sPC_Style2 = it->PCStyle2;
|
||||
|
||||
// position
|
||||
charInfo.iX = it->x;
|
||||
charInfo.iY = it->y;
|
||||
charInfo.iZ = it->z;
|
||||
|
||||
// save character in session (for char select)
|
||||
int UID = it->iID;
|
||||
loginSessions[sock].characters[UID] = Player(*it);
|
||||
loginSessions[sock].characters[UID].FEKey = sock->getFEKey();
|
||||
|
||||
// Equip info
|
||||
for (int i = 0; i < AEQUIP_COUNT; i++) {
|
||||
charInfo.aEquip[i] = it->Equip[i];
|
||||
}
|
||||
|
||||
// set default to the first character
|
||||
if (it == characters.begin())
|
||||
loginSessions[sock].selectedChar = UID;
|
||||
|
||||
sock->sendPacket((void*)&charInfo, P_LS2CL_REP_CHAR_INFO, sizeof(sP_LS2CL_REP_CHAR_INFO));
|
||||
}
|
||||
} else {
|
||||
INITSTRUCT(sP_LS2CL_REP_LOGIN_FAIL, resp);
|
||||
U8toU16(userLogin, resp.szID, sizeof(resp.szID));
|
||||
resp.iErrorCode = errorCode;
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_FAIL, sizeof(sP_LS2CL_REP_LOGIN_FAIL));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REP_LIVE_CHECK: {
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_CHECK_CHAR_NAME: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHECK_CHAR_NAME))
|
||||
return;
|
||||
|
||||
sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck = (sP_CL2LS_REQ_CHECK_CHAR_NAME*)data->buf;
|
||||
bool success = true;
|
||||
int errorcode = 0;
|
||||
|
||||
// check regex
|
||||
if (!CNLoginServer::isCharacterNameGood(U16toU8(nameCheck->szFirstName), U16toU8(nameCheck->szLastName))) {
|
||||
success = false;
|
||||
errorcode = 4;
|
||||
} else if (!Database::isNameFree(nameCheck)){ // check if name isn't already occupied
|
||||
success = false;
|
||||
errorcode = 1;
|
||||
}
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
if (success) {
|
||||
INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_SUCC, resp);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "P_CL2LS_REQ_CHECK_CHAR_NAME:" << std::endl;
|
||||
std::cout << "\tFirstName: " << U16toU8(nameCheck->szFirstName) << " LastName: " << U16toU8(nameCheck->szLastName) << std::endl;
|
||||
)
|
||||
|
||||
memcpy(resp.szFirstName, nameCheck->szFirstName, sizeof(char16_t) * 9);
|
||||
memcpy(resp.szLastName, nameCheck->szLastName, sizeof(char16_t) * 17);
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHECK_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_CHECK_CHAR_NAME_SUCC));
|
||||
} else {
|
||||
INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL, resp);
|
||||
resp.iErrorCode = errorcode;
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHECK_CHAR_NAME_FAIL, sizeof(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_SAVE_CHAR_NAME: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_SAVE_CHAR_NAME))
|
||||
return;
|
||||
|
||||
sP_CL2LS_REQ_SAVE_CHAR_NAME* save = (sP_CL2LS_REQ_SAVE_CHAR_NAME*)data->buf;
|
||||
INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "P_CL2LS_REQ_SAVE_CHAR_NAME:" << std::endl;
|
||||
std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl;
|
||||
std::cout << "\tName: " << U16toU8(save->szFirstName) << " " << U16toU8(save->szLastName) << std::endl;
|
||||
)
|
||||
|
||||
resp.iSlotNum = save->iSlotNum;
|
||||
resp.iGender = save->iGender;
|
||||
resp.iPC_UID = Database::createCharacter(save, loginSessions[sock].userID);
|
||||
memcpy(resp.szFirstName, save->szFirstName, sizeof(char16_t) * 9);
|
||||
memcpy(resp.szLastName, save->szLastName, sizeof(char16_t) * 17);
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_SAVE_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC));
|
||||
|
||||
Database::updateSelected(loginSessions[sock].userID, save->iSlotNum);
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_CHAR_CREATE: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHAR_CREATE))
|
||||
return;
|
||||
|
||||
sP_CL2LS_REQ_CHAR_CREATE* character = (sP_CL2LS_REQ_CHAR_CREATE*)data->buf;
|
||||
Database::finishCharacter(character);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "P_CL2LS_REQ_CHAR_CREATE:" << std::endl;
|
||||
std::cout << "\tPC_UID: " << character->PCStyle.iPC_UID << std::endl;
|
||||
std::cout << "\tNameCheck: " << (int)character->PCStyle.iNameCheck << std::endl;
|
||||
std::cout << "\tName: " << U16toU8(character->PCStyle.szFirstName) << " " << U16toU8(character->PCStyle.szLastName) << std::endl;
|
||||
std::cout << "\tGender: " << (int)character->PCStyle.iGender << std::endl;
|
||||
std::cout << "\tFace: " << (int)character->PCStyle.iFaceStyle << std::endl;
|
||||
std::cout << "\tHair: " << (int)character->PCStyle.iHairStyle << std::endl;
|
||||
std::cout << "\tHair Color: " << (int)character->PCStyle.iHairColor << std::endl;
|
||||
std::cout << "\tSkin Color: " << (int)character->PCStyle.iSkinColor << std::endl;
|
||||
std::cout << "\tEye Color: " << (int)character->PCStyle.iEyeColor << std::endl;
|
||||
std::cout << "\tHeight: " << (int)character->PCStyle.iHeight << std::endl;
|
||||
std::cout << "\tBody: " << (int)character->PCStyle.iBody << std::endl;
|
||||
std::cout << "\tClass: " << (int)character->PCStyle.iClass << std::endl;
|
||||
std::cout << "\tiEquipUBID: " << (int)character->sOn_Item.iEquipUBID << std::endl;
|
||||
std::cout << "\tiEquipLBID: " << (int)character->sOn_Item.iEquipLBID << std::endl;
|
||||
std::cout << "\tiEquipFootID: " << (int)character->sOn_Item.iEquipFootID << std::endl;
|
||||
)
|
||||
|
||||
Player player = Database::getPlayer(character->PCStyle.iPC_UID);
|
||||
int64_t UID = player.iID;
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REP_CHAR_CREATE_SUCC, resp);
|
||||
resp.sPC_Style = player.PCStyle;
|
||||
resp.sPC_Style2 = player.PCStyle2;
|
||||
resp.iLevel = player.level;
|
||||
resp.sOn_Item = character->sOn_Item;
|
||||
|
||||
// save player in session
|
||||
loginSessions[sock].characters[UID] = Player(player);
|
||||
loginSessions[sock].characters[UID].FEKey = sock->getFEKey();
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHAR_CREATE_SUCC, sizeof(sP_LS2CL_REP_CHAR_CREATE_SUCC));
|
||||
Database::updateSelected(loginSessions[sock].userID, player.slot);
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_CHAR_DELETE: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHAR_DELETE))
|
||||
return;
|
||||
|
||||
sP_CL2LS_REQ_CHAR_DELETE* del = (sP_CL2LS_REQ_CHAR_DELETE*)data->buf;
|
||||
int operationResult = Database::deleteCharacter(del->iPC_UID, loginSessions[sock].userID);
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REP_CHAR_DELETE_SUCC, resp);
|
||||
resp.iSlotNum = operationResult;
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHAR_DELETE_SUCC, sizeof(sP_LS2CL_REP_CHAR_DELETE_SUCC));
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_CHAR_SELECT: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHAR_SELECT))
|
||||
return;
|
||||
|
||||
// character selected
|
||||
sP_CL2LS_REQ_CHAR_SELECT* chararacter = (sP_CL2LS_REQ_CHAR_SELECT*)data->buf;
|
||||
INITSTRUCT(sP_LS2CL_REP_CHAR_SELECT_SUCC, resp);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "P_CL2LS_REQ_CHAR_SELECT:" << std::endl;
|
||||
std::cout << "\tPC_UID: " << chararacter->iPC_UID << std::endl;
|
||||
)
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
loginSessions[sock].selectedChar = chararacter->iPC_UID;
|
||||
Database::updateSelected(loginSessions[sock].userID, loginSessions[sock].characters[chararacter->iPC_UID].slot);
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHAR_SELECT_SUCC, sizeof(sP_LS2CL_REP_CHAR_SELECT_SUCC));
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_SHARD_SELECT: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_SHARD_SELECT))
|
||||
return;
|
||||
|
||||
// tell client to connect to the shard server
|
||||
sP_CL2LS_REQ_SHARD_SELECT* shard = (sP_CL2LS_REQ_SHARD_SELECT*)data->buf;
|
||||
INITSTRUCT(sP_LS2CL_REP_SHARD_SELECT_SUCC, resp);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "P_CL2LS_REQ_SHARD_SELECT:" << std::endl;
|
||||
std::cout << "\tShard: " << (int)shard->ShardNum << std::endl;
|
||||
)
|
||||
|
||||
const char* SHARD_IP = settings::SHARDSERVERIP.c_str();
|
||||
resp.iEnterSerialKey = rand();
|
||||
resp.g_FE_ServerPort = settings::SHARDPORT;
|
||||
|
||||
// copy IP to resp (this struct uses ASCII encoding so we don't have to goof around converting encodings)
|
||||
memcpy(resp.g_FE_ServerIP, SHARD_IP, strlen(SHARD_IP));
|
||||
resp.g_FE_ServerIP[strlen(SHARD_IP)] = '\0';
|
||||
|
||||
// pass player to CNSharedData
|
||||
CNSharedData::setPlayer(resp.iEnterSerialKey, loginSessions[sock].characters[loginSessions[sock].selectedChar]);
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_SHARD_SELECT_SUCC, sizeof(sP_LS2CL_REP_SHARD_SELECT_SUCC));
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_SAVE_CHAR_TUTOR: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_SAVE_CHAR_TUTOR))
|
||||
return;
|
||||
sP_CL2LS_REQ_SAVE_CHAR_TUTOR* save = (sP_CL2LS_REQ_SAVE_CHAR_TUTOR*)data->buf;
|
||||
Database::finishTutorial(save->iPC_UID);
|
||||
// update character in session
|
||||
auto key = loginSessions[sock].characters[save->iPC_UID].FEKey;
|
||||
loginSessions[sock].characters[save->iPC_UID] = Player(Database::getPlayer(save->iPC_UID));
|
||||
loginSessions[sock].characters[save->iPC_UID].FEKey = key;
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
// no response here
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_CHANGE_CHAR_NAME: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHANGE_CHAR_NAME))
|
||||
return;
|
||||
|
||||
sP_CL2LS_REQ_CHANGE_CHAR_NAME* save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf;
|
||||
Database::changeName(save);
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, resp);
|
||||
resp.iPC_UID = save->iPCUID;
|
||||
memcpy(resp.szFirstName, save->szFirstName, sizeof(char16_t)*9);
|
||||
memcpy(resp.szLastName, save->szLastName, sizeof(char16_t) * 17);
|
||||
resp.iSlotNum = save->iSlotNum;
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC));
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_PC_EXIT_DUPLICATE:{
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_PC_EXIT_DUPLICATE))
|
||||
return;
|
||||
|
||||
sP_CL2LS_REQ_PC_EXIT_DUPLICATE* exit = (sP_CL2LS_REQ_PC_EXIT_DUPLICATE*)data->buf;
|
||||
auto account = Database::findAccount(U16toU8(exit->szID));
|
||||
|
||||
// sanity check
|
||||
if (account == nullptr) {
|
||||
std::cout << "[WARN] P_CL2LS_REQ_PC_EXIT_DUPLICATE submitted unknown username: " << exit->szID << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
exitDuplicate(account->AccountID);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (settings::VERBOSITY)
|
||||
std::cerr << "OpenFusion: LOGIN UNIMPLM ERR. PacketType: " << Defines::p2str(CL2LS, data->type) << " (" << data->type << ")" << std::endl;
|
||||
break;
|
||||
/*
|
||||
* Unimplemented CL2LS packets:
|
||||
* P_CL2LS_CHECK_NAME_LIST - unused by the client
|
||||
* P_CL2LS_REQ_SERVER_SELECT
|
||||
* P_CL2LS_REQ_SHARD_LIST_INFO - dev commands, useless as we only run 1 server
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
void CNLoginServer::newConnection(CNSocket* cns) {
|
||||
cns->setActiveKey(SOCKETKEY_E); // by default they accept keys encrypted with the default key
|
||||
}
|
||||
|
||||
void CNLoginServer::killConnection(CNSocket* cns) {
|
||||
loginSessions.erase(cns);
|
||||
}
|
||||
|
||||
void CNLoginServer::onStep() {
|
||||
time_t currTime = getTime();
|
||||
static time_t lastCheck = 0;
|
||||
|
||||
if (currTime - lastCheck < 16000)
|
||||
return;
|
||||
lastCheck = currTime;
|
||||
|
||||
for (auto& pair : loginSessions) {
|
||||
if (pair.second.lastHeartbeat != 0 && currTime - pair.second.lastHeartbeat > 32000) {
|
||||
pair.first->kill();
|
||||
continue;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REQ_LIVE_CHECK, pkt);
|
||||
pair.first->sendPacket((void*)&pkt, P_LS2CL_REQ_LIVE_CHECK, sizeof(sP_LS2CL_REQ_LIVE_CHECK));
|
||||
}
|
||||
}
|
||||
|
||||
#pragma region helperMethods
|
||||
bool CNLoginServer::isAccountInUse(int accountId) {
|
||||
std::map<CNSocket*, CNLoginData>::iterator it;
|
||||
for (it = CNLoginServer::loginSessions.begin(); it != CNLoginServer::loginSessions.end(); it++) {
|
||||
if (it->second.userID == accountId)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CNLoginServer::exitDuplicate(int accountId) {
|
||||
std::map<CNSocket*, CNLoginData>::iterator it;
|
||||
for (it = CNLoginServer::loginSessions.begin(); it != CNLoginServer::loginSessions.end(); it++) {
|
||||
if (it->second.userID == accountId) {
|
||||
CNSocket* sock = it->first;
|
||||
INITSTRUCT(sP_LS2CL_REP_PC_EXIT_DUPLICATE, resp);
|
||||
resp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_PC_EXIT_DUPLICATE, sizeof(sP_LS2CL_REP_PC_EXIT_DUPLICATE));
|
||||
sock->kill();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CNLoginServer::isLoginDataGood(std::string login, std::string password) {
|
||||
std::regex loginRegex("[a-zA-Z0-9_-]{4,32}");
|
||||
std::regex passwordRegex("[a-zA-Z0-9!@#$%^&*()_+]{8,32}");
|
||||
|
||||
return (std::regex_match(login, loginRegex) && std::regex_match(password, passwordRegex));
|
||||
}
|
||||
|
||||
bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tryPassword) {
|
||||
return BCrypt::validatePassword(tryPassword, actualPassword);
|
||||
}
|
||||
|
||||
bool CNLoginServer::isCharacterNameGood(std::string Firstname, std::string Lastname) {
|
||||
std::regex firstnamecheck("[a-zA-Z0-9]+(?: [a-zA-Z0-9]+)*$");
|
||||
std::regex lastnamecheck("[a-zA-Z0-9]+(?: [a-zA-Z0-9]+)*$");
|
||||
return (std::regex_match(Firstname, firstnamecheck) && std::regex_match(Lastname, lastnamecheck));
|
||||
}
|
||||
#pragma endregion helperMethods
|
||||
@@ -1,371 +0,0 @@
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
|
||||
// ========================================================[[ CNSocketEncryption ]]========================================================
|
||||
|
||||
// literally C/P from the client and converted to C++ (does some byte swapping /shrug)
|
||||
int CNSocketEncryption::Encrypt_byte_change_A(int ERSize, uint8_t* data, int size) {
|
||||
int num = 0;
|
||||
int num2 = 0;
|
||||
int num3 = 0;
|
||||
|
||||
while (num + ERSize <= size) {
|
||||
int num4 = num + num3;
|
||||
int num5 = num + (ERSize - 1 - num3);
|
||||
|
||||
uint8_t b = data[num4];
|
||||
data[num4] = data[num5];
|
||||
data[num5] = b;
|
||||
num += ERSize;
|
||||
num3++;
|
||||
if (num3 > ERSize / 2) {
|
||||
num3 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
num2 = ERSize - (num + ERSize - size);
|
||||
return num + num2;
|
||||
}
|
||||
|
||||
int CNSocketEncryption::xorData(uint8_t* buffer, uint8_t* key, int size) {
|
||||
// xor every 8 bytes with 8 byte key
|
||||
for (int i = 0; i < size; i++) {
|
||||
buffer[i] ^= key[i % keyLength];
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) {
|
||||
uint64_t num = (uint64_t)(iv1 + 1);
|
||||
uint64_t num2 = (uint64_t)(iv2 + 1);
|
||||
uint64_t dEKey = (uint64_t)(*(uint64_t*)&defaultKey[0]);
|
||||
return dEKey * (uTime * num * num2);
|
||||
}
|
||||
|
||||
int CNSocketEncryption::encryptData(uint8_t* buffer, uint8_t* key, int size) {
|
||||
int eRSize = size % (keyLength / 2 + 1) * 2 + keyLength; // C/P from client
|
||||
int size2 = xorData(buffer, key, size);
|
||||
return Encrypt_byte_change_A(eRSize, buffer, size2);
|
||||
}
|
||||
|
||||
int CNSocketEncryption::decryptData(uint8_t* buffer, uint8_t* key, int size) {
|
||||
int eRSize = size % (keyLength / 2 + 1) * 2 + keyLength; // size % of 18????
|
||||
int size2 = Encrypt_byte_change_A(eRSize, buffer, size);
|
||||
return xorData(buffer, key, size2);
|
||||
}
|
||||
|
||||
// ========================================================[[ CNPacketData ]]========================================================
|
||||
|
||||
CNPacketData::CNPacketData(void* b, uint32_t t, int l): buf(b), size(l), type(t) {}
|
||||
|
||||
// ========================================================[[ CNSocket ]]========================================================
|
||||
|
||||
CNSocket::CNSocket(SOCKET s, PacketHandler ph): sock(s), pHandler(ph) {
|
||||
EKey = (uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]);
|
||||
}
|
||||
|
||||
bool CNSocket::sendData(uint8_t* data, int size) {
|
||||
int sentBytes = 0;
|
||||
int maxTries = 10;
|
||||
|
||||
while (sentBytes < size) {
|
||||
int sent = send(sock, (buffer_t*)(data + sentBytes), size - sentBytes, 0); // no flags defined
|
||||
if (SOCKETERROR(sent)) {
|
||||
if (OF_ERRNO == OF_EWOULD && maxTries > 0) {
|
||||
maxTries--;
|
||||
continue; // try again
|
||||
}
|
||||
std::cout << "[FATAL] SOCKET ERROR: " << OF_ERRNO << std::endl;
|
||||
return false; // error occured while sending bytes
|
||||
}
|
||||
sentBytes += sent;
|
||||
}
|
||||
|
||||
return true; // it worked!
|
||||
}
|
||||
|
||||
void CNSocket::setEKey(uint64_t k) {
|
||||
EKey = k;
|
||||
}
|
||||
|
||||
void CNSocket::setFEKey(uint64_t k) {
|
||||
FEKey = k;
|
||||
}
|
||||
|
||||
uint64_t CNSocket::getEKey() {
|
||||
return EKey;
|
||||
}
|
||||
|
||||
uint64_t CNSocket::getFEKey() {
|
||||
return FEKey;
|
||||
}
|
||||
|
||||
bool CNSocket::isAlive() {
|
||||
return alive;
|
||||
}
|
||||
|
||||
void CNSocket::kill() {
|
||||
alive = false;
|
||||
#ifdef _WIN32
|
||||
shutdown(sock, SD_BOTH);
|
||||
closesocket(sock);
|
||||
#else
|
||||
shutdown(sock, SHUT_RDWR);
|
||||
close(sock);
|
||||
#endif
|
||||
}
|
||||
|
||||
// we don't own buf, TODO: queue packets up to send in step()
|
||||
void CNSocket::sendPacket(void* buf, uint32_t type, size_t size) {
|
||||
if (!alive)
|
||||
return;
|
||||
|
||||
size_t bodysize = size + sizeof(uint32_t);
|
||||
uint8_t* fullpkt = (uint8_t*)xmalloc(bodysize+4);
|
||||
uint8_t* body = fullpkt+4;
|
||||
memcpy(fullpkt, (void*)&bodysize, 4);
|
||||
|
||||
// copy packet type to the front of the buffer & then the actual buffer
|
||||
memcpy(body, (void*)&type, sizeof(uint32_t));
|
||||
memcpy(body+sizeof(uint32_t), buf, size);
|
||||
|
||||
// encrypt the packet
|
||||
switch (activeKey) {
|
||||
case SOCKETKEY_E:
|
||||
CNSocketEncryption::encryptData((uint8_t*)body, (uint8_t*)(&EKey), bodysize);
|
||||
break;
|
||||
case SOCKETKEY_FE:
|
||||
CNSocketEncryption::encryptData((uint8_t*)body, (uint8_t*)(&FEKey), bodysize);
|
||||
break;
|
||||
default: {
|
||||
free(fullpkt);
|
||||
DEBUGLOG(
|
||||
std::cout << "[WARN]: UNSET KEYTYPE FOR SOCKET!! ABORTING SEND" << std::endl;
|
||||
)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// send packet data!
|
||||
if (alive && !sendData(fullpkt, bodysize+4))
|
||||
kill();
|
||||
|
||||
free(fullpkt);
|
||||
}
|
||||
|
||||
void CNSocket::setActiveKey(ACTIVEKEY key) {
|
||||
activeKey = key;
|
||||
}
|
||||
|
||||
void CNSocket::step() {
|
||||
// read step
|
||||
|
||||
if (readSize <= 0) {
|
||||
// we aren't reading a packet yet, try to start looking for one
|
||||
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
|
||||
if (!SOCKETERROR(recved)) {
|
||||
// we got our packet size!!!!
|
||||
readSize = *((int32_t*)readBuffer);
|
||||
// sanity check
|
||||
if (readSize > CN_PACKET_BUFFER_SIZE) {
|
||||
kill();
|
||||
return;
|
||||
}
|
||||
|
||||
// we'll just leave bufferIndex at 0 since we already have the packet size, it's safe to overwrite those bytes
|
||||
activelyReading = true;
|
||||
} else if (OF_ERRNO != OF_EWOULD) {
|
||||
// serious socket issue, disconnect connection
|
||||
kill();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (readSize > 0 && readBufferIndex < readSize) {
|
||||
// read until the end of the packet! (or at least try too)
|
||||
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
|
||||
if (!SOCKETERROR(recved))
|
||||
readBufferIndex += recved;
|
||||
else if (OF_ERRNO != OF_EWOULD) {
|
||||
// serious socket issue, disconnect connection
|
||||
kill();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (activelyReading && readBufferIndex - readSize <= 0) {
|
||||
// decrypt readBuffer and copy to CNPacketData
|
||||
CNSocketEncryption::decryptData((uint8_t*)&readBuffer, (uint8_t*)(&EKey), readSize);
|
||||
|
||||
void* tmpBuf = readBuffer+sizeof(uint32_t);
|
||||
CNPacketData tmp(tmpBuf, *((uint32_t*)readBuffer), readSize-sizeof(int32_t));
|
||||
|
||||
// call packet handler!!
|
||||
pHandler(this, &tmp);
|
||||
|
||||
// reset vars :)
|
||||
readSize = 0;
|
||||
readBufferIndex = 0;
|
||||
activelyReading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================[[ CNServer ]]========================================================
|
||||
|
||||
void CNServer::init() {
|
||||
// create socket file descriptor
|
||||
sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (SOCKETINVALID(sock)) {
|
||||
std::cerr << "[FATAL] OpenFusion: socket failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// attach socket to the port
|
||||
int opt = 1;
|
||||
#ifdef _WIN32
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)) != 0) {
|
||||
#else
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
|
||||
#endif
|
||||
std::cerr << "[FATAL] OpenFusion: setsockopt failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = INADDR_ANY;
|
||||
address.sin_port = htons(port);
|
||||
|
||||
addressSize = sizeof(address);
|
||||
|
||||
// Bind to the port
|
||||
if (SOCKETERROR(bind(sock, (struct sockaddr *)&address, addressSize))) {
|
||||
std::cerr << "[FATAL] OpenFusion: bind failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (SOCKETERROR(listen(sock, SOMAXCONN))) {
|
||||
std::cerr << "[FATAL] OpenFusion: listen failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// set server listener to non-blocking
|
||||
#ifdef _WIN32
|
||||
unsigned long mode = 1;
|
||||
if (ioctlsocket(sock, FIONBIO, &mode) != 0) {
|
||||
#else
|
||||
if (fcntl(sock, F_SETFL, (fcntl(sock, F_GETFL, 0) | O_NONBLOCK)) != 0) {
|
||||
#endif
|
||||
std::cerr << "[FATAL] OpenFusion: fcntl failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
CNServer::CNServer() {};
|
||||
CNServer::CNServer(uint16_t p): port(p) {}
|
||||
|
||||
void CNServer::start() {
|
||||
std::cout << "Starting server at *:" << port << std::endl;
|
||||
// listen to new connections, add to connection list
|
||||
while (active) {
|
||||
std::lock_guard<std::mutex> lock(activeCrit);
|
||||
|
||||
// listen for a new connection
|
||||
SOCKET newConnectionSocket = accept(sock, (struct sockaddr *)&(address), (socklen_t*)&(addressSize));
|
||||
if (!SOCKETINVALID(newConnectionSocket)) {
|
||||
// new connection! make sure to set non-blocking!
|
||||
#ifdef _WIN32
|
||||
unsigned long mode = 1;
|
||||
if (ioctlsocket(newConnectionSocket, FIONBIO, &mode) != 0) {
|
||||
#else
|
||||
if (fcntl(newConnectionSocket, F_SETFL, (fcntl(sock, F_GETFL, 0) | O_NONBLOCK)) != 0) {
|
||||
#endif
|
||||
std::cerr << "[WARN] OpenFusion: fcntl failed on new connection" << std::endl;
|
||||
#ifdef _WIN32
|
||||
shutdown(newConnectionSocket, SD_BOTH);
|
||||
closesocket(newConnectionSocket);
|
||||
#else
|
||||
shutdown(newConnectionSocket, SHUT_RDWR);
|
||||
close(newConnectionSocket);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
std::cout << "New connection! " << inet_ntoa(address.sin_addr) << std::endl;
|
||||
|
||||
// add connection to list!
|
||||
CNSocket* tmp = new CNSocket(newConnectionSocket, pHandler);
|
||||
connections.push_back(tmp);
|
||||
newConnection(tmp);
|
||||
}
|
||||
|
||||
// for each connection, check if it's alive, if not kill it!
|
||||
std::list<CNSocket*>::iterator i = connections.begin();
|
||||
while (i != connections.end()) {
|
||||
CNSocket* cSock = *i;
|
||||
|
||||
if (cSock->isAlive()) {
|
||||
cSock->step();
|
||||
|
||||
++i; // go to the next element
|
||||
} else {
|
||||
killConnection(cSock);
|
||||
connections.erase(i++);
|
||||
delete cSock;
|
||||
}
|
||||
}
|
||||
|
||||
onStep();
|
||||
|
||||
#ifdef _WIN32
|
||||
Sleep(0);
|
||||
#else
|
||||
sleep(0); // so your cpu isn't at 100% all the time, we don't need all of that! im not hacky! you're hacky!
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void CNServer::kill() {
|
||||
std::lock_guard<std::mutex> lock(activeCrit); // the lock will be removed when the function ends
|
||||
active = false;
|
||||
|
||||
// kill all connections
|
||||
std::list<CNSocket*>::iterator i = connections.begin();
|
||||
while (i != connections.end()) {
|
||||
CNSocket* cSock = *i;
|
||||
|
||||
if (cSock->isAlive()) {
|
||||
cSock->kill();
|
||||
}
|
||||
|
||||
++i; // go to the next element
|
||||
delete cSock;
|
||||
}
|
||||
|
||||
connections.clear();
|
||||
}
|
||||
|
||||
void CNServer::printPacket(CNPacketData *data, int type) {
|
||||
if (settings::VERBOSITY < 2)
|
||||
return;
|
||||
|
||||
if (settings::VERBOSITY < 3) switch (data->type) {
|
||||
case P_CL2LS_REP_LIVE_CHECK:
|
||||
case P_CL2FE_REP_LIVE_CHECK:
|
||||
case P_CL2FE_REQ_PC_MOVE:
|
||||
case P_CL2FE_REQ_PC_JUMP:
|
||||
case P_CL2FE_REQ_PC_SLOPE:
|
||||
case P_CL2FE_REQ_PC_MOVEPLATFORM:
|
||||
case P_CL2FE_REQ_PC_MOVETRANSPORTATION:
|
||||
case P_CL2FE_REQ_PC_ZIPLINE:
|
||||
case P_CL2FE_REQ_PC_JUMPPAD:
|
||||
case P_CL2FE_REQ_PC_LAUNCHER:
|
||||
case P_CL2FE_REQ_PC_STOP:
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "OpenFusion: received " << Defines::p2str(type, data->type) << " (" << data->type << ")" << std::endl;
|
||||
}
|
||||
|
||||
void CNServer::newConnection(CNSocket* cns) {} // stubbed
|
||||
void CNServer::killConnection(CNSocket* cns) {} // stubbed
|
||||
void CNServer::onStep() {} // stubbed
|
||||
@@ -1,27 +0,0 @@
|
||||
#include "CNShared.hpp"
|
||||
|
||||
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
|
||||
#include "mingw/mingw.mutex.h"
|
||||
#else
|
||||
#include <mutex>
|
||||
#endif
|
||||
std::map<int64_t, Player> CNSharedData::players;
|
||||
std::mutex playerCrit;
|
||||
|
||||
void CNSharedData::setPlayer(int64_t sk, Player& plr) {
|
||||
std::lock_guard<std::mutex> lock(playerCrit); // the lock will be removed when the function ends
|
||||
|
||||
players[sk] = plr;
|
||||
}
|
||||
|
||||
Player CNSharedData::getPlayer(int64_t sk) {
|
||||
std::lock_guard<std::mutex> lock(playerCrit); // the lock will be removed when the function ends
|
||||
|
||||
return players[sk];
|
||||
}
|
||||
|
||||
void CNSharedData::erasePlayer(int64_t sk) {
|
||||
std::lock_guard<std::mutex> lock(playerCrit); // the lock will be removed when the function ends
|
||||
|
||||
players.erase(sk);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* CNShared.hpp
|
||||
* There's some data shared between the Login Server and the Shard Server. Of course all of this needs to be thread-safe. No mucking about on this one!
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "Player.hpp"
|
||||
|
||||
namespace CNSharedData {
|
||||
// serialkey corresponds to player data
|
||||
extern std::map<int64_t, Player> players;
|
||||
|
||||
void setPlayer(int64_t sk, Player& plr);
|
||||
Player getPlayer(int64_t sk);
|
||||
void erasePlayer(int64_t sk);
|
||||
}
|
||||
319
src/Chat.cpp
Normal file
319
src/Chat.cpp
Normal file
@@ -0,0 +1,319 @@
|
||||
#include "Chat.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "CustomCommands.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
std::vector<std::string> Chat::dump;
|
||||
|
||||
using namespace Chat;
|
||||
|
||||
static void chatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto chat = (sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
||||
if (fullChat.length() > 1 && fullChat[0] == CMD_PREFIX) { // PREFIX
|
||||
CustomCommands::runCmd(fullChat, sock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__MUTE_FREECHAT)
|
||||
return;
|
||||
|
||||
std::string logLine = "[FreeChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, resp);
|
||||
|
||||
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
|
||||
resp.iPC_ID = plr->iID;
|
||||
resp.iEmoteCode = chat->iEmoteCode;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC);
|
||||
|
||||
// send to visible players
|
||||
PlayerManager::sendToViewable(sock, resp, P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC);
|
||||
}
|
||||
|
||||
static void menuChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto chat = (sP_CL2FE_REQ_SEND_MENUCHAT_MESSAGE*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
||||
std::string logLine = "[MenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp);
|
||||
|
||||
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
|
||||
resp.iPC_ID = PlayerManager::getPlayer(sock)->iID;
|
||||
resp.iEmoteCode = chat->iEmoteCode;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC);
|
||||
|
||||
// send to visible players
|
||||
PlayerManager::sendToViewable(sock, resp, P_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC);
|
||||
}
|
||||
|
||||
static void emoteHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto emote = (sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, resp);
|
||||
resp.iEmoteCode = emote->iEmoteCode;
|
||||
resp.iID_From = plr->iID;
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_AVATAR_EMOTES_CHAT);
|
||||
|
||||
// send to visible players (players within render distance)
|
||||
PlayerManager::sendToViewable(sock, resp, P_FE2CL_REP_PC_AVATAR_EMOTES_CHAT);
|
||||
}
|
||||
|
||||
void Chat::sendServerMessage(CNSocket* sock, std::string msg) {
|
||||
INITSTRUCT(sP_FE2CL_PC_MOTD_LOGIN, motd);
|
||||
|
||||
motd.iType = 1;
|
||||
// convert string to u16 and write it to the buffer
|
||||
U8toU16(msg, (char16_t*)motd.szSystemMsg, sizeof(motd.szSystemMsg));
|
||||
|
||||
// send the packet :)
|
||||
sock->sendPacket(motd, P_FE2CL_PC_MOTD_LOGIN);
|
||||
}
|
||||
|
||||
static void announcementHandler(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
if (plr->accountLevel > 30)
|
||||
return; // only players with account level less than 30 (GM) are allowed to use this command
|
||||
auto announcement = (sP_CL2FE_GM_REQ_PC_ANNOUNCE*)data->buf;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
|
||||
msg.iAnnounceType = announcement->iAnnounceType;
|
||||
msg.iDuringTime = announcement->iDuringTime;
|
||||
memcpy(msg.szAnnounceMsg, announcement->szAnnounceMsg, sizeof(msg.szAnnounceMsg));
|
||||
std::map<CNSocket*, Player*>::iterator it;
|
||||
|
||||
switch (announcement->iAreaType) {
|
||||
case 0: // area (all players in viewable chunks)
|
||||
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||
PlayerManager::sendToViewable(sock, msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||
break;
|
||||
case 1: // shard
|
||||
case 2: // world
|
||||
break; // not applicable to OpenFusion
|
||||
case 3: // global (all players)
|
||||
for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++) {
|
||||
CNSocket* allSock = it->first;
|
||||
allSock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
std::string logLine = "[Bcast " + std::to_string(announcement->iAreaType) + "] " + PlayerManager::getPlayerName(plr, false) + ": " + AUTOU16TOU8(msg.szAnnounceMsg);
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back("**" + logLine + "**");
|
||||
}
|
||||
|
||||
// Buddy freechatting
|
||||
static void buddyChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(pkt->iBuddyPCUID);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return; // buddy offline
|
||||
|
||||
Player *otherPlr = PlayerManager::getPlayer(otherSock);
|
||||
|
||||
resp.iFromPCUID = plr->PCStyle.iPC_UID;
|
||||
resp.iToPCUID = pkt->iBuddyPCUID;
|
||||
resp.iEmoteCode = pkt->iEmoteCode;
|
||||
|
||||
std::string fullChat = sanitizeText(AUTOU16TOU8(pkt->szFreeChat));
|
||||
|
||||
if (fullChat.length() > 1 && fullChat[0] == CMD_PREFIX) { // PREFIX
|
||||
CustomCommands::runCmd(fullChat, sock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__MUTE_FREECHAT)
|
||||
return;
|
||||
|
||||
std::string logLine = "[BuddyChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
|
||||
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC); // confirm send to sender
|
||||
otherSock->sendPacket(resp, P_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC); // broadcast send to receiver
|
||||
}
|
||||
|
||||
// Buddy menuchat
|
||||
static void buddyMenuChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(pkt->iBuddyPCUID);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return; // buddy offline
|
||||
|
||||
Player *otherPlr = PlayerManager::getPlayer(otherSock);
|
||||
|
||||
resp.iFromPCUID = plr->PCStyle.iPC_UID;
|
||||
resp.iToPCUID = pkt->iBuddyPCUID;
|
||||
resp.iEmoteCode = pkt->iEmoteCode;
|
||||
|
||||
std::string fullChat = sanitizeText(AUTOU16TOU8(pkt->szFreeChat));
|
||||
std::string logLine = "[BuddyMenuChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
|
||||
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
|
||||
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC); // confirm send to sender
|
||||
otherSock->sendPacket(resp, P_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC); // broadcast send to receiver
|
||||
}
|
||||
|
||||
static void tradeChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto pacdat = (sP_CL2FE_REQ_PC_TRADE_EMOTES_CHAT*)data->buf;
|
||||
|
||||
CNSocket* otherSock; // weird flip flop because we need to know who the other player is
|
||||
if (pacdat->iID_Request == pacdat->iID_From)
|
||||
otherSock = PlayerManager::getSockFromID(pacdat->iID_To);
|
||||
else
|
||||
otherSock = PlayerManager::getSockFromID(pacdat->iID_From);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
Player *otherPlr = PlayerManager::getPlayer(otherSock);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_EMOTES_CHAT, resp);
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
resp.iID_Request = pacdat->iID_Request;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
std::string fullChat = sanitizeText(AUTOU16TOU8(pacdat->szFreeChat));
|
||||
U8toU16(fullChat, resp.szFreeChat, sizeof(resp.szFreeChat));
|
||||
|
||||
std::string logLine = "[TradeChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
|
||||
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
|
||||
resp.iEmoteCode = pacdat->iEmoteCode;
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT);
|
||||
otherSock->sendPacket(resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT);
|
||||
}
|
||||
|
||||
static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
||||
|
||||
if (fullChat.length() > 1 && fullChat[0] == CMD_PREFIX) { // PREFIX
|
||||
CustomCommands::runCmd(fullChat, sock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__MUTE_FREECHAT)
|
||||
return;
|
||||
|
||||
std::string logLine = "[GroupChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp);
|
||||
|
||||
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
|
||||
resp.iSendPCID = plr->iID;
|
||||
resp.iEmoteCode = chat->iEmoteCode;
|
||||
|
||||
if (plr->group == nullptr)
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
|
||||
else
|
||||
Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
|
||||
}
|
||||
|
||||
static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
||||
std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp);
|
||||
|
||||
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
|
||||
resp.iSendPCID = plr->iID;
|
||||
resp.iEmoteCode = chat->iEmoteCode;
|
||||
|
||||
if (plr->group == nullptr)
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
|
||||
Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
|
||||
}
|
||||
|
||||
// we only allow plain ascii, at least for now
|
||||
std::string Chat::sanitizeText(std::string text, bool allowNewlines) {
|
||||
int i;
|
||||
const int BUFSIZE = 512;
|
||||
char buf[BUFSIZE];
|
||||
|
||||
assert(text.size() < BUFSIZE);
|
||||
|
||||
i = 0;
|
||||
for (char c : text) {
|
||||
if (i >= BUFSIZE-1)
|
||||
break;
|
||||
|
||||
if (!allowNewlines && c == '\n')
|
||||
continue;
|
||||
|
||||
if ((c >= ' ' && c <= '~') || c == '\n')
|
||||
buf[i++] = c;
|
||||
}
|
||||
buf[i] = 0;
|
||||
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
void Chat::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_FREECHAT_MESSAGE, chatHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT, emoteHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_MENUCHAT_MESSAGE, menuChatHandler);
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE, buddyChatHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE, buddyMenuChatHandler);
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_EMOTES_CHAT, tradeChatHandler);
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE, groupChatHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE, groupMenuChatHandler);
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_ANNOUNCE, announcementHandler);
|
||||
}
|
||||
16
src/Chat.hpp
Normal file
16
src/Chat.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#define CMD_PREFIX '/'
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Chat {
|
||||
extern std::vector<std::string> dump;
|
||||
void init();
|
||||
|
||||
void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD
|
||||
std::string sanitizeText(std::string text, bool allowNewlines=false);
|
||||
}
|
||||
@@ -1,539 +0,0 @@
|
||||
#include "CNShardServer.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "ChatManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "TransportManager.hpp"
|
||||
#include "TableData.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <iterator>
|
||||
|
||||
std::map<std::string, ChatCommand> ChatManager::commands;
|
||||
|
||||
std::vector<std::string> parseArgs(std::string full) {
|
||||
std::stringstream ss(full);
|
||||
std::istream_iterator<std::string> begin(ss);
|
||||
std::istream_iterator<std::string> end;
|
||||
return std::vector<std::string>(begin, end);
|
||||
}
|
||||
|
||||
bool runCmd(std::string full, CNSocket* sock) {
|
||||
std::vector<std::string> args = parseArgs(full);
|
||||
std::string cmd = args[0].substr(1, args[0].size() - 1);
|
||||
|
||||
// check if the command exists
|
||||
if (ChatManager::commands.find(cmd) != ChatManager::commands.end()) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
ChatCommand command = ChatManager::commands[cmd];
|
||||
|
||||
// sanity check + does the player have the required account level to use the command?
|
||||
if (plr != nullptr && plr->accountLevel <= command.requiredAccLevel) {
|
||||
command.handlr(full, args, sock);
|
||||
return true;
|
||||
} else {
|
||||
ChatManager::sendServerMessage(sock, "You don't have access to that command!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ChatManager::sendServerMessage(sock, "Unknown command!");
|
||||
return false;
|
||||
}
|
||||
|
||||
void helpCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
ChatManager::sendServerMessage(sock, "Commands available to you:");
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
for (auto& cmd : ChatManager::commands) {
|
||||
if (cmd.second.requiredAccLevel >= plr->accountId)
|
||||
ChatManager::sendServerMessage(sock, "/" + cmd.first + (cmd.second.help.length() > 0 ? " - " + cmd.second.help : ""));
|
||||
}
|
||||
}
|
||||
|
||||
void accessCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
ChatManager::sendServerMessage(sock, "Your access level is " + std::to_string(PlayerManager::getPlayer(sock)->accountLevel));
|
||||
}
|
||||
|
||||
void populationCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
ChatManager::sendServerMessage(sock, std::to_string(PlayerManager::players.size()) + " players online");
|
||||
}
|
||||
|
||||
void levelCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
if (args.size() < 2) {
|
||||
ChatManager::sendServerMessage(sock, "/level: no level specified");
|
||||
return;
|
||||
}
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
char *tmp;
|
||||
int level = std::strtol(args[1].c_str(), &tmp, 10);
|
||||
if (*tmp)
|
||||
return;
|
||||
|
||||
if ((level < 1 || level > 36) && plr->accountLevel > 30)
|
||||
return;
|
||||
|
||||
if (!(level < 1 || level > 36))
|
||||
plr->level = level;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_LEVEL, resp);
|
||||
|
||||
resp.iPC_ID = plr->iID;
|
||||
resp.iPC_Level = level;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_CHANGE_LEVEL, sizeof(sP_FE2CL_REP_PC_CHANGE_LEVEL));
|
||||
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_REP_PC_CHANGE_LEVEL, sizeof(sP_FE2CL_REP_PC_CHANGE_LEVEL));
|
||||
}
|
||||
|
||||
void mssCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
if (args.size() < 2) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Too few arguments");
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Usage: /mss <route> <add/remove/goto/clear/test/export> <<height>>");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate route number
|
||||
char* routeNumC;
|
||||
int routeNum = std::strtol(args[1].c_str(), &routeNumC, 10);
|
||||
if (*routeNumC) {
|
||||
// not an integer
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Invalid route number '" + args[1] + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.size() < 3) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Too few arguments");
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Usage: /mss <route> <add/remove/goto/clear/test> <<height>>");
|
||||
return;
|
||||
}
|
||||
|
||||
// get the route (if it doesn't exist yet, this will also make it)
|
||||
std::vector<WarpLocation>* route = &TableData::RunningSkywayRoutes[routeNum];
|
||||
|
||||
// mss <route> add <height>
|
||||
if (args[2] == "add") {
|
||||
// make sure height token exists
|
||||
if (args.size() < 4) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Point height must be specified");
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Usage: /mss <route> add <height>");
|
||||
return;
|
||||
}
|
||||
// validate height token
|
||||
char* heightC;
|
||||
int height = std::strtol(args[3].c_str(), &heightC, 10);
|
||||
if (*heightC) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Invalid height " + args[3]);
|
||||
return;
|
||||
}
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
route->push_back({ plr->x, plr->y, height }); // add point
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Added point (" + std::to_string(plr->x) + ", " + std::to_string(plr->y) + ", " + std::to_string(height) + ") to route " + std::to_string(routeNum));
|
||||
return;
|
||||
}
|
||||
|
||||
// mss <route> remove
|
||||
if (args[2] == "remove") {
|
||||
if (route->empty()) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Route " + std::to_string(routeNum) + " is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
WarpLocation pulled = route->back();
|
||||
route->pop_back(); // remove point at top of stack
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Removed point (" + std::to_string(pulled.x) + ", " + std::to_string(pulled.y) + ", " + std::to_string(pulled.z) + ") from route " + std::to_string(routeNum));
|
||||
return;
|
||||
}
|
||||
|
||||
// mss <route> goto
|
||||
if (args[2] == "goto") {
|
||||
if (route->empty()) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Route " + std::to_string(routeNum) + " is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
WarpLocation pulled = route->back();
|
||||
PlayerManager::sendPlayerTo(sock, pulled.x, pulled.y, pulled.z);
|
||||
return;
|
||||
}
|
||||
|
||||
// mss <route> clear
|
||||
if (args[2] == "clear") {
|
||||
route->clear();
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Cleared route " + std::to_string(routeNum));
|
||||
return;
|
||||
}
|
||||
|
||||
// mss <route> test
|
||||
if (args[2] == "test") {
|
||||
if (route->empty()) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Route " + std::to_string(routeNum) + " is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
WarpLocation pulled = route->front();
|
||||
PlayerManager::sendPlayerTo(sock, pulled.x, pulled.y, pulled.z);
|
||||
TransportManager::testMssRoute(sock, route);
|
||||
return;
|
||||
}
|
||||
|
||||
// for compatibility: mss <route> export
|
||||
if (args[2] == "export") {
|
||||
ChatManager::sendServerMessage(sock, "Wrote gruntwork to " + settings::GRUNTWORKJSON);
|
||||
TableData::flush();
|
||||
return;
|
||||
}
|
||||
|
||||
// mss ????
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Unknown command '" + args[2] + "'");
|
||||
}
|
||||
|
||||
void summonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
if (args.size() < 2) {
|
||||
ChatManager::sendServerMessage(sock, "/summonW: no mob type specified");
|
||||
return;
|
||||
}
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
char *rest;
|
||||
int type = std::strtol(args[1].c_str(), &rest, 10);
|
||||
if (*rest) {
|
||||
ChatManager::sendServerMessage(sock, "Invalid NPC number: " + args[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
// permission & sanity check
|
||||
if (plr == nullptr || type >= 3314)
|
||||
return;
|
||||
|
||||
int team = NPCManager::NPCData[type]["m_iTeam"];
|
||||
|
||||
assert(NPCManager::nextId < INT32_MAX);
|
||||
|
||||
#define EXTRA_HEIGHT 200
|
||||
BaseNPC *npc = nullptr;
|
||||
int id = NPCManager::nextId++;
|
||||
if (team == 2) {
|
||||
npc = new Mob(plr->x, plr->y, plr->z + EXTRA_HEIGHT, plr->instanceID, type, NPCManager::NPCData[type], id);
|
||||
MobManager::Mobs[npc->appearanceData.iNPC_ID] = (Mob*)npc;
|
||||
|
||||
// re-enable respawning
|
||||
((Mob*)npc)->summoned = false;
|
||||
} else {
|
||||
npc = new BaseNPC(plr->x, plr->y, plr->z + EXTRA_HEIGHT, 0, plr->instanceID, type, id);
|
||||
}
|
||||
|
||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
||||
NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc;
|
||||
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z);
|
||||
|
||||
// if we're in a lair, we need to spawn the NPC in both the private instance and the template
|
||||
if (PLAYERID(plr->instanceID) != 0) {
|
||||
id = NPCManager::nextId++;
|
||||
|
||||
if (team == 2) {
|
||||
npc = new Mob(plr->x, plr->y, plr->z + EXTRA_HEIGHT, MAPNUM(plr->instanceID), type, NPCManager::NPCData[type], id);
|
||||
|
||||
MobManager::Mobs[npc->appearanceData.iNPC_ID] = (Mob*)npc;
|
||||
|
||||
((Mob*)npc)->summoned = false;
|
||||
} else {
|
||||
npc = new BaseNPC(plr->x, plr->y, plr->z + EXTRA_HEIGHT, 0, MAPNUM(plr->instanceID), type, id);
|
||||
}
|
||||
|
||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
||||
NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc;
|
||||
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z);
|
||||
}
|
||||
|
||||
ChatManager::sendServerMessage(sock, "/summonW: placed mob with type: " + std::to_string(type) +
|
||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
||||
TableData::RunningMobs[npc->appearanceData.iNPC_ID] = npc; // only record the one in the template
|
||||
}
|
||||
|
||||
void unsummonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
PlayerView& plrv = PlayerManager::players[sock];
|
||||
Player* plr = plrv.plr;
|
||||
|
||||
BaseNPC* npc = NPCManager::getNearestNPC(plrv.currentChunks, plr->x, plr->y, plr->z);
|
||||
|
||||
if (npc == nullptr) {
|
||||
ChatManager::sendServerMessage(sock, "/unsummonW: No NPCs found nearby");
|
||||
return;
|
||||
}
|
||||
|
||||
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end()) {
|
||||
ChatManager::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob.");
|
||||
return;
|
||||
}
|
||||
|
||||
ChatManager::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->appearanceData.iNPCType) +
|
||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
||||
|
||||
TableData::RunningMobs.erase(npc->appearanceData.iNPC_ID);
|
||||
|
||||
NPCManager::destroyNPC(npc->appearanceData.iNPC_ID);
|
||||
}
|
||||
|
||||
void toggleAiCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
MobManager::simulateMobs = !MobManager::simulateMobs;
|
||||
|
||||
if (MobManager::simulateMobs)
|
||||
return;
|
||||
|
||||
// return all mobs to their spawn points
|
||||
for (auto& pair : MobManager::Mobs) {
|
||||
pair.second->state = MobState::RETREAT;
|
||||
pair.second->target = nullptr;
|
||||
pair.second->nextMovement = getTime();
|
||||
|
||||
// mobs with static paths can chill where they are
|
||||
if (pair.second->staticPath) {
|
||||
pair.second->roamX = pair.second->appearanceData.iX;
|
||||
pair.second->roamY = pair.second->appearanceData.iY;
|
||||
pair.second->roamZ = pair.second->appearanceData.iZ;
|
||||
} else {
|
||||
pair.second->roamX = pair.second->spawnX;
|
||||
pair.second->roamY = pair.second->spawnY;
|
||||
pair.second->roamZ = pair.second->spawnZ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void npcRotateCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
PlayerView& plrv = PlayerManager::players[sock];
|
||||
Player* plr = plrv.plr;
|
||||
|
||||
BaseNPC* npc = NPCManager::getNearestNPC(plrv.currentChunks, plr->x, plr->y, plr->z);
|
||||
|
||||
if (npc == nullptr) {
|
||||
ChatManager::sendServerMessage(sock, "[NPCR] No NPCs found nearby");
|
||||
return;
|
||||
}
|
||||
|
||||
int angle = (plr->angle + 180) % 360;
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ, angle);
|
||||
|
||||
// if it's a gruntwork NPC, rotate in-place
|
||||
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) != TableData::RunningMobs.end()) {
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ, angle);
|
||||
|
||||
ChatManager::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC "
|
||||
+ std::to_string(npc->appearanceData.iNPC_ID));
|
||||
} else {
|
||||
TableData::RunningNPCRotations[npc->appearanceData.iNPC_ID] = angle;
|
||||
|
||||
ChatManager::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC "
|
||||
+ std::to_string(npc->appearanceData.iNPC_ID));
|
||||
}
|
||||
|
||||
// update rotation clientside
|
||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
|
||||
pkt.NPCAppearanceData = npc->appearanceData;
|
||||
sock->sendPacket((void*)&pkt, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER));
|
||||
}
|
||||
|
||||
void refreshCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
PlayerManager::sendPlayerTo(sock, plr->x, plr->y, plr->z);
|
||||
}
|
||||
|
||||
void instanceCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// no additional arguments: report current instance ID
|
||||
if (args.size() < 2) {
|
||||
ChatManager::sendServerMessage(sock, "[INST] Current instance ID: " + std::to_string(plr->instanceID));
|
||||
ChatManager::sendServerMessage(sock, "[INST] (Map " + std::to_string(MAPNUM(plr->instanceID)) + ", instance " + std::to_string(PLAYERID(plr->instanceID)) + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
// move player to specified instance
|
||||
// validate instance ID
|
||||
char* instanceS;
|
||||
int instance = std::strtol(args[1].c_str(), &instanceS, 10);
|
||||
if (*instanceS) {
|
||||
ChatManager::sendServerMessage(sock, "[INST] Invalid instance ID: " + args[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerManager::sendPlayerTo(sock, plr->x, plr->y, plr->z, instance);
|
||||
ChatManager::sendServerMessage(sock, "[INST] Switched to instance with ID " + std::to_string(instance));
|
||||
}
|
||||
|
||||
void npcInstanceCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
PlayerView& plrv = PlayerManager::players[sock];
|
||||
Player* plr = plrv.plr;
|
||||
|
||||
if (args.size() < 2) {
|
||||
ChatManager::sendServerMessage(sock, "[NPCI] Instance ID must be specified");
|
||||
ChatManager::sendServerMessage(sock, "[NPCI] Usage: /npci <instance ID>");
|
||||
return;
|
||||
}
|
||||
|
||||
BaseNPC* npc = NPCManager::getNearestNPC(plrv.currentChunks, plr->x, plr->y, plr->z);
|
||||
|
||||
if (npc == nullptr) {
|
||||
ChatManager::sendServerMessage(sock, "[NPCI] No NPCs found nearby");
|
||||
return;
|
||||
}
|
||||
|
||||
// validate instance ID
|
||||
char* instanceS;
|
||||
int instance = std::strtol(args[1].c_str(), &instanceS, 10);
|
||||
if (*instanceS) {
|
||||
ChatManager::sendServerMessage(sock, "[NPCI] Invalid instance ID: " + args[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
ChatManager::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->appearanceData.iNPC_ID) + " to instance " + std::to_string(instance));
|
||||
TableData::RunningNPCMapNumbers[npc->appearanceData.iNPC_ID] = instance;
|
||||
NPCManager::updateNPCInstance(npc->appearanceData.iNPC_ID, instance);
|
||||
}
|
||||
|
||||
void minfoCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
ChatManager::sendServerMessage(sock, "[MINFO] Current mission ID: " + std::to_string(plr->CurrentMissionID));
|
||||
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] != 0) {
|
||||
TaskData& task = *MissionManager::Tasks[plr->tasks[i]];
|
||||
if ((int)(task["m_iHMissionID"]) == plr->CurrentMissionID) {
|
||||
ChatManager::sendServerMessage(sock, "[MINFO] Current task ID: " + std::to_string(plr->tasks[i]));
|
||||
ChatManager::sendServerMessage(sock, "[MINFO] Current task type: " + std::to_string((int)(task["m_iHTaskType"])));
|
||||
ChatManager::sendServerMessage(sock, "[MINFO] Current waypoint NPC ID: " + std::to_string((int)(task["m_iSTGrantWayPoint"])));
|
||||
ChatManager::sendServerMessage(sock, "[MINFO] Current terminator NPC ID: " + std::to_string((int)(task["m_iHTerminatorNPCID"])));
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
if ((int)(task["m_iCSUEnemyID"][j]) != 0)
|
||||
ChatManager::sendServerMessage(sock, "[MINFO] Current task mob #" + std::to_string(j+1) +": " + std::to_string((int)(task["m_iCSUEnemyID"][j])));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tasksCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] != 0) {
|
||||
TaskData& task = *MissionManager::Tasks[plr->tasks[i]];
|
||||
ChatManager::sendServerMessage(sock, "[TASK-" + std::to_string(i) + "] mission ID: " + std::to_string((int)(task["m_iHMissionID"])));
|
||||
ChatManager::sendServerMessage(sock, "[TASK-" + std::to_string(i) + "] task ID: " + std::to_string(plr->tasks[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void flushCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
TableData::flush();
|
||||
ChatManager::sendServerMessage(sock, "Wrote gruntwork to " + settings::GRUNTWORKJSON);
|
||||
}
|
||||
|
||||
void ChatManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_FREECHAT_MESSAGE, chatHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT, emoteHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_MENUCHAT_MESSAGE, menuChatHandler);
|
||||
|
||||
registerCommand("help", 100, helpCommand, "list all unlocked server-side commands");
|
||||
registerCommand("access", 100, accessCommand, "print your access level");
|
||||
registerCommand("instance", 30, instanceCommand, "print or change your current instance");
|
||||
registerCommand("mss", 30, mssCommand, "edit Monkey Skyway routes");
|
||||
registerCommand("npcr", 30, npcRotateCommand, "rotate NPCs");
|
||||
registerCommand("npci", 30, npcInstanceCommand, "move NPCs across instances");
|
||||
registerCommand("summonW", 30, summonWCommand, "permanently summon NPCs");
|
||||
registerCommand("unsummonW", 30, unsummonWCommand, "delete permanently summoned NPCs");
|
||||
registerCommand("toggleai", 30, toggleAiCommand, "enable/disable mob AI");
|
||||
registerCommand("flush", 30, flushCommand, "save gruntwork to file");
|
||||
registerCommand("level", 50, levelCommand, "change your character's level");
|
||||
registerCommand("population", 100, populationCommand, "check how many players are online");
|
||||
registerCommand("refresh", 100, refreshCommand, "teleport yourself to your current location");
|
||||
registerCommand("minfo", 30, minfoCommand, "show details of the current mission and task.");
|
||||
registerCommand("tasks", 30, tasksCommand, "list all active missions and their respective task ids.");
|
||||
}
|
||||
|
||||
void ChatManager::registerCommand(std::string cmd, int requiredLevel, CommandHandler handlr, std::string help) {
|
||||
commands[cmd] = ChatCommand(requiredLevel, handlr, help);
|
||||
}
|
||||
|
||||
void ChatManager::chatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
std::string fullChat = U16toU8(chat->szFreeChat);
|
||||
|
||||
if (fullChat.length() > 1 && fullChat[0] == CMD_PREFIX) { // PREFIX
|
||||
runCmd(fullChat, sock);
|
||||
return;
|
||||
}
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, resp);
|
||||
memcpy(resp.szFreeChat, chat->szFreeChat, sizeof(chat->szFreeChat));
|
||||
resp.iPC_ID = plr->iID;
|
||||
resp.iEmoteCode = chat->iEmoteCode;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC));
|
||||
|
||||
// send to visible players
|
||||
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC));
|
||||
}
|
||||
|
||||
void ChatManager::menuChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SEND_MENUCHAT_MESSAGE))
|
||||
return; // malformed packet
|
||||
sP_CL2FE_REQ_SEND_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_MENUCHAT_MESSAGE*)data->buf;
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp);
|
||||
memcpy(resp.szFreeChat, chat->szFreeChat, sizeof(chat->szFreeChat));
|
||||
resp.iPC_ID = PlayerManager::players[sock].plr->iID;
|
||||
resp.iEmoteCode = chat->iEmoteCode;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC));
|
||||
|
||||
// send to visible players
|
||||
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC));
|
||||
}
|
||||
|
||||
void ChatManager::emoteHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT))
|
||||
return; // ignore the malformed packet
|
||||
|
||||
// you can dance with friends!!!!!!!!
|
||||
|
||||
sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT* emote = (sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, resp);
|
||||
resp.iEmoteCode = emote->iEmoteCode;
|
||||
resp.iID_From = plr->iID;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT));
|
||||
|
||||
// send to visible players (players within render distance)
|
||||
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT));
|
||||
}
|
||||
|
||||
void ChatManager::sendServerMessage(CNSocket* sock, std::string msg) {
|
||||
INITSTRUCT(sP_FE2CL_PC_MOTD_LOGIN, motd);
|
||||
|
||||
motd.iType = 1;
|
||||
// convert string to u16 and write it to the buffer
|
||||
U8toU16(msg, (char16_t*)motd.szSystemMsg, sizeof(motd.szSystemMsg));
|
||||
|
||||
// send the packet :)
|
||||
sock->sendPacket((void*)&motd, P_FE2CL_PC_MOTD_LOGIN, sizeof(sP_FE2CL_PC_MOTD_LOGIN));
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNShardServer.hpp"
|
||||
|
||||
#define CMD_PREFIX '/'
|
||||
|
||||
typedef void (*CommandHandler)(std::string fullString, std::vector<std::string>& args, CNSocket* sock);
|
||||
|
||||
struct ChatCommand {
|
||||
int requiredAccLevel;
|
||||
std::string help;
|
||||
CommandHandler handlr;
|
||||
|
||||
ChatCommand(int r, CommandHandler h): requiredAccLevel(r), handlr(h) {}
|
||||
ChatCommand(int r, CommandHandler h, std::string str): requiredAccLevel(r), help(str), handlr(h) {}
|
||||
ChatCommand(): ChatCommand(0, nullptr) {}
|
||||
};
|
||||
|
||||
namespace ChatManager {
|
||||
extern std::map<std::string, ChatCommand> commands;
|
||||
void init();
|
||||
|
||||
void registerCommand(std::string cmd, int requiredLevel, CommandHandler handlr, std::string help = "");
|
||||
|
||||
void chatHandler(CNSocket* sock, CNPacketData* data);
|
||||
void emoteHandler(CNSocket* sock, CNPacketData* data);
|
||||
void menuChatHandler(CNSocket* sock, CNPacketData* data);
|
||||
void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
#include "ChunkManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "MobManager.hpp"
|
||||
|
||||
std::map<std::tuple<int, int, uint64_t>, Chunk*> ChunkManager::chunks;
|
||||
|
||||
void ChunkManager::init() {} // stubbed
|
||||
|
||||
void ChunkManager::newChunk(std::tuple<int, int, uint64_t> pos) {
|
||||
Chunk *chunk = new Chunk();
|
||||
|
||||
chunk->players = std::set<CNSocket*>();
|
||||
chunk->NPCs = std::set<int32_t>();
|
||||
|
||||
chunks[pos] = chunk;
|
||||
}
|
||||
|
||||
void ChunkManager::populateNewChunk(Chunk* chunk, std::tuple<int, int, uint64_t> pos) {// add the new chunk to every player and mob that's near it
|
||||
for (Chunk *c : grabChunks(pos)) {
|
||||
if (c == chunk)
|
||||
continue;
|
||||
|
||||
for (CNSocket *s : c->players)
|
||||
PlayerManager::players[s].currentChunks.push_back(chunk);
|
||||
|
||||
for (int32_t id : c->NPCs)
|
||||
NPCManager::NPCs[id]->currentChunks.push_back(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
void ChunkManager::addNPC(int posX, int posY, uint64_t instanceID, int32_t id) {
|
||||
std::tuple<int, int, uint64_t> pos = grabChunk(posX, posY, instanceID);
|
||||
|
||||
bool newChunkUsed = false;
|
||||
|
||||
// make chunk if it doesn't exist!
|
||||
if (chunks.find(pos) == chunks.end()) {
|
||||
newChunk(pos);
|
||||
newChunkUsed = true;
|
||||
}
|
||||
|
||||
Chunk* chunk = chunks[pos];
|
||||
|
||||
if (newChunkUsed)
|
||||
NPCManager::NPCs[id]->currentChunks.push_back(chunk);
|
||||
|
||||
chunk->NPCs.insert(id);
|
||||
|
||||
// we must update other players after the NPC is added to chunk
|
||||
if (newChunkUsed)
|
||||
populateNewChunk(chunk, pos);
|
||||
}
|
||||
|
||||
void ChunkManager::addPlayer(int posX, int posY, uint64_t instanceID, CNSocket* sock) {
|
||||
std::tuple<int, int, uint64_t> pos = grabChunk(posX, posY, instanceID);
|
||||
|
||||
bool newChunkUsed = false;
|
||||
|
||||
// make chunk if it doesn't exist!
|
||||
if (chunks.find(pos) == chunks.end()) {
|
||||
newChunk(pos);
|
||||
newChunkUsed = true;
|
||||
}
|
||||
|
||||
Chunk* chunk = chunks[pos];
|
||||
|
||||
if (newChunkUsed)
|
||||
PlayerManager::players[sock].currentChunks.push_back(chunk);
|
||||
|
||||
chunk->players.insert(sock);
|
||||
|
||||
// we must update other players after this player is added to chunk
|
||||
if (newChunkUsed)
|
||||
populateNewChunk(chunk, pos);
|
||||
}
|
||||
|
||||
bool ChunkManager::removePlayer(std::tuple<int, int, uint64_t> chunkPos, CNSocket* sock) {
|
||||
if (!checkChunk(chunkPos))
|
||||
return false; // do nothing if chunk doesn't even exist
|
||||
|
||||
Chunk* chunk = chunks[chunkPos];
|
||||
|
||||
chunk->players.erase(sock); // gone
|
||||
|
||||
// if players and NPCs are empty, free chunk and remove it from surrounding views
|
||||
if (chunk->NPCs.size() == 0 && chunk->players.size() == 0) {
|
||||
destroyChunk(chunkPos);
|
||||
|
||||
// the chunk we left was destroyed
|
||||
return true;
|
||||
}
|
||||
|
||||
// the chunk we left was not destroyed
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChunkManager::removeNPC(std::tuple<int, int, uint64_t> chunkPos, int32_t id) {
|
||||
if (!checkChunk(chunkPos))
|
||||
return false; // do nothing if chunk doesn't even exist
|
||||
|
||||
Chunk* chunk = chunks[chunkPos];
|
||||
|
||||
chunk->NPCs.erase(id); // gone
|
||||
|
||||
// if players and NPCs are empty, free chunk and remove it from surrounding views
|
||||
if (chunk->NPCs.size() == 0 && chunk->players.size() == 0) {
|
||||
destroyChunk(chunkPos);
|
||||
|
||||
// the chunk we left was destroyed
|
||||
return true;
|
||||
}
|
||||
|
||||
// the chunk we left was not destroyed
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChunkManager::destroyChunk(std::tuple<int, int, uint64_t> chunkPos) {
|
||||
if (!checkChunk(chunkPos))
|
||||
return; // chunk doesn't exist, we don't need to do anything
|
||||
|
||||
Chunk* chunk = chunks[chunkPos];
|
||||
|
||||
// unspawn all of the mobs/npcs
|
||||
std::set npcIDs(chunk->NPCs);
|
||||
for (uint32_t id : npcIDs) {
|
||||
NPCManager::destroyNPC(id);
|
||||
}
|
||||
|
||||
// we also need to remove it from all NPCs/Players views
|
||||
for (Chunk* otherChunk : grabChunks(chunkPos)) {
|
||||
if (otherChunk == chunk)
|
||||
continue;
|
||||
|
||||
// remove from NPCs
|
||||
for (uint32_t id : otherChunk->NPCs) {
|
||||
if (std::find(NPCManager::NPCs[id]->currentChunks.begin(), NPCManager::NPCs[id]->currentChunks.end(), chunk) != NPCManager::NPCs[id]->currentChunks.end()) {
|
||||
NPCManager::NPCs[id]->currentChunks.erase(std::remove(NPCManager::NPCs[id]->currentChunks.begin(), NPCManager::NPCs[id]->currentChunks.end(), chunk), NPCManager::NPCs[id]->currentChunks.end());
|
||||
}
|
||||
}
|
||||
|
||||
// remove from players
|
||||
for (CNSocket* sock : otherChunk->players) {
|
||||
PlayerView* plyr = &PlayerManager::players[sock];
|
||||
if (std::find(plyr->currentChunks.begin(), plyr->currentChunks.end(), chunk) != plyr->currentChunks.end()) {
|
||||
plyr->currentChunks.erase(std::remove(plyr->currentChunks.begin(), plyr->currentChunks.end(), chunk), plyr->currentChunks.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
assert(chunk->players.size() == 0);
|
||||
|
||||
// remove from the map
|
||||
chunks.erase(chunkPos);
|
||||
|
||||
delete chunk;
|
||||
}
|
||||
|
||||
bool ChunkManager::checkChunk(std::tuple<int, int, uint64_t> chunk) {
|
||||
return chunks.find(chunk) != chunks.end();
|
||||
}
|
||||
|
||||
std::tuple<int, int, uint64_t> ChunkManager::grabChunk(int posX, int posY, uint64_t instanceID) {
|
||||
return std::make_tuple(posX / (settings::VIEWDISTANCE / 3), posY / (settings::VIEWDISTANCE / 3), instanceID);
|
||||
}
|
||||
|
||||
std::vector<Chunk*> ChunkManager::grabChunks(std::tuple<int, int, uint64_t> chunk) {
|
||||
std::vector<Chunk*> chnks;
|
||||
chnks.reserve(9);
|
||||
|
||||
int x, y;
|
||||
uint64_t inst;
|
||||
std::tie(x, y, inst) = chunk;
|
||||
|
||||
// grabs surrounding chunks if they exist
|
||||
for (int i = -1; i < 2; i++) {
|
||||
for (int z = -1; z < 2; z++) {
|
||||
std::tuple<int, int, uint64_t> pos = std::make_tuple(x+i, y+z, inst);
|
||||
|
||||
// if chunk exists, add it to the vector
|
||||
if (checkChunk(pos))
|
||||
chnks.push_back(chunks[pos]);
|
||||
}
|
||||
}
|
||||
|
||||
return chnks;
|
||||
}
|
||||
|
||||
// returns the chunks that aren't shared (only from from)
|
||||
std::vector<Chunk*> ChunkManager::getDeltaChunks(std::vector<Chunk*> from, std::vector<Chunk*> to) {
|
||||
std::vector<Chunk*> delta;
|
||||
|
||||
for (Chunk* i : from) {
|
||||
bool found = false;
|
||||
|
||||
// search for it in the other array
|
||||
for (Chunk* z : to) {
|
||||
if (i == z) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// add it to the vector if we didn't find it!
|
||||
if (!found)
|
||||
delta.push_back(i);
|
||||
}
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
/*
|
||||
* inefficient algorithm to get all chunks from a specific instance
|
||||
*/
|
||||
std::vector<std::tuple<int, int, uint64_t>> ChunkManager::getChunksInMap(uint64_t mapNum) {
|
||||
std::vector<std::tuple<int, int, uint64_t>> chnks;
|
||||
|
||||
for (auto it = ChunkManager::chunks.begin(); it != ChunkManager::chunks.end(); it++) {
|
||||
if (std::get<2>(it->first) == mapNum) {
|
||||
chnks.push_back(it->first);
|
||||
}
|
||||
}
|
||||
|
||||
return chnks;
|
||||
}
|
||||
|
||||
bool ChunkManager::inPopulatedChunks(int posX, int posY, uint64_t instanceID) {
|
||||
auto chunk = ChunkManager::grabChunk(posX, posY, instanceID);
|
||||
auto nearbyChunks = ChunkManager::grabChunks(chunk);
|
||||
|
||||
for (Chunk *c: nearbyChunks) {
|
||||
if (!c->players.empty())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChunkManager::createInstance(uint64_t instanceID) {
|
||||
|
||||
std::vector<std::tuple<int, int, uint64_t>> templateChunks = ChunkManager::getChunksInMap(MAPNUM(instanceID)); // base instance chunks
|
||||
if (ChunkManager::getChunksInMap(instanceID).size() == 0) { // only instantiate if the instance doesn't exist already
|
||||
std::cout << "Creating instance " << instanceID << std::endl;
|
||||
for (std::tuple<int, int, uint64_t> &coords : templateChunks) {
|
||||
for (int npcID : chunks[coords]->NPCs) {
|
||||
// make a copy of each NPC in the template chunks and put them in the new instance
|
||||
int newID = NPCManager::nextId++;
|
||||
BaseNPC* baseNPC = NPCManager::NPCs[npcID];
|
||||
if (baseNPC->npcClass == NPC_MOB) {
|
||||
Mob* newMob = new Mob(baseNPC->appearanceData.iX, baseNPC->appearanceData.iY, baseNPC->appearanceData.iZ, baseNPC->appearanceData.iAngle,
|
||||
instanceID, baseNPC->appearanceData.iNPCType, ((Mob*)baseNPC)->maxHealth, NPCManager::NPCData[baseNPC->appearanceData.iNPCType], newID);
|
||||
NPCManager::NPCs[newID] = newMob;
|
||||
MobManager::Mobs[newID] = newMob;
|
||||
} else {
|
||||
BaseNPC* newNPC = new BaseNPC(baseNPC->appearanceData.iX, baseNPC->appearanceData.iY, baseNPC->appearanceData.iZ, baseNPC->appearanceData.iAngle,
|
||||
instanceID, baseNPC->appearanceData.iNPCType, newID);
|
||||
NPCManager::NPCs[newID] = newNPC;
|
||||
}
|
||||
NPCManager::updateNPCInstance(newID, instanceID); // make sure the npc state gets updated
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::cout << "Instance " << instanceID << " already exists" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ChunkManager::destroyInstance(uint64_t instanceID) {
|
||||
std::cout << "Deleting instance " << instanceID << std::endl;
|
||||
std::vector<std::tuple<int, int, uint64_t>> instanceChunks = ChunkManager::getChunksInMap(instanceID);
|
||||
for (std::tuple<int, int, uint64_t>& coords : instanceChunks) {
|
||||
destroyChunk(coords);
|
||||
}
|
||||
}
|
||||
|
||||
void ChunkManager::destroyInstanceIfEmpty(uint64_t instanceID) {
|
||||
if (PLAYERID(instanceID) == 0)
|
||||
return; // don't clean up overworld/IZ chunks
|
||||
|
||||
std::vector<std::tuple<int, int, uint64_t>> sourceChunkCoords = getChunksInMap(instanceID);
|
||||
|
||||
for (std::tuple<int, int, uint64_t>& coords : sourceChunkCoords) {
|
||||
Chunk* chunk = chunks[coords];
|
||||
|
||||
if (chunk->players.size() > 0)
|
||||
return; // there are still players inside
|
||||
}
|
||||
|
||||
destroyInstance(instanceID);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNProtocol.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <tuple>
|
||||
|
||||
class Chunk {
|
||||
public:
|
||||
std::set<CNSocket*> players;
|
||||
std::set<int32_t> NPCs;
|
||||
};
|
||||
|
||||
enum {
|
||||
INSTANCE_OVERWORLD, // default instance every player starts in
|
||||
INSTANCE_IZ, // these aren't actually used
|
||||
INSTANCE_UNIQUE // these aren't actually used
|
||||
};
|
||||
|
||||
namespace ChunkManager {
|
||||
void init();
|
||||
void cleanup();
|
||||
|
||||
extern std::map<std::tuple<int, int, uint64_t>, Chunk*> chunks;
|
||||
|
||||
void newChunk(std::tuple<int, int, uint64_t> pos);
|
||||
void populateNewChunk(Chunk* chunk, std::tuple<int, int, uint64_t> pos);
|
||||
void addNPC(int posX, int posY, uint64_t instanceID, int32_t id);
|
||||
void addPlayer(int posX, int posY, uint64_t instanceID, CNSocket* sock);
|
||||
bool removePlayer(std::tuple<int, int, uint64_t> chunkPos, CNSocket* sock);
|
||||
bool removeNPC(std::tuple<int, int, uint64_t> chunkPos, int32_t id);
|
||||
bool checkChunk(std::tuple<int, int, uint64_t> chunk);
|
||||
void destroyChunk(std::tuple<int, int, uint64_t> chunkPos);
|
||||
std::tuple<int, int, uint64_t> grabChunk(int posX, int posY, uint64_t instanceID);
|
||||
std::vector<Chunk*> grabChunks(std::tuple<int, int, uint64_t> chunkPos);
|
||||
std::vector<Chunk*> getDeltaChunks(std::vector<Chunk*> from, std::vector<Chunk*> to);
|
||||
std::vector<std::tuple<int, int, uint64_t>> getChunksInMap(uint64_t mapNum);
|
||||
bool inPopulatedChunks(int posX, int posY, uint64_t instanceID);
|
||||
|
||||
void createInstance(uint64_t);
|
||||
void destroyInstance(uint64_t);
|
||||
void destroyInstanceIfEmpty(uint64_t);
|
||||
}
|
||||
344
src/Chunking.cpp
Normal file
344
src/Chunking.cpp
Normal file
@@ -0,0 +1,344 @@
|
||||
#include "Chunking.hpp"
|
||||
|
||||
#include "MobAI.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
using namespace Chunking;
|
||||
|
||||
/*
|
||||
* The initial chunkPos value before a player is placed into the world.
|
||||
*/
|
||||
const ChunkPos Chunking::INVALID_CHUNK = {};
|
||||
|
||||
std::map<ChunkPos, Chunk*> Chunking::chunks;
|
||||
|
||||
static void newChunk(ChunkPos pos) {
|
||||
if (chunkExists(pos)) {
|
||||
std::cout << "[WARN] Tried to create a chunk that already exists" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
Chunk *chunk = new Chunk();
|
||||
chunks[pos] = chunk;
|
||||
|
||||
// add the chunk to the cache of all players and NPCs in the surrounding chunks
|
||||
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
||||
for (Chunk* c : surroundings)
|
||||
for (const EntityRef ref : c->entities)
|
||||
ref.getEntity()->viewableChunks.insert(chunk);
|
||||
}
|
||||
|
||||
static void deleteChunk(ChunkPos pos) {
|
||||
if (!chunkExists(pos)) {
|
||||
std::cout << "[WARN] Tried to delete a chunk that doesn't exist" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
Chunk* chunk = chunks[pos];
|
||||
|
||||
// remove the chunk from the cache of all players and NPCs in the surrounding chunks
|
||||
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
||||
for(Chunk* c : surroundings)
|
||||
for (const EntityRef ref : c->entities)
|
||||
ref.getEntity()->viewableChunks.erase(chunk);
|
||||
|
||||
chunks.erase(pos); // remove from map
|
||||
delete chunk; // free from memory
|
||||
}
|
||||
|
||||
void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef ref) {
|
||||
if (!chunkExists(chunkPos))
|
||||
return; // shouldn't happen
|
||||
|
||||
chunks[chunkPos]->entities.insert(ref);
|
||||
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
chunks[chunkPos]->nplayers++;
|
||||
}
|
||||
|
||||
void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) {
|
||||
if (!chunkExists(chunkPos))
|
||||
return; // do nothing if chunk doesn't even exist
|
||||
|
||||
Chunk* chunk = chunks[chunkPos];
|
||||
|
||||
chunk->entities.erase(ref); // gone
|
||||
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
chunks[chunkPos]->nplayers--;
|
||||
assert(chunks[chunkPos]->nplayers >= 0);
|
||||
|
||||
// if chunk is completely empty, free it
|
||||
if (chunk->entities.size() == 0)
|
||||
deleteChunk(chunkPos);
|
||||
}
|
||||
|
||||
void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) {
|
||||
Entity *ent = ref.getEntity();
|
||||
bool alive = ent->isExtant();
|
||||
|
||||
// TODO: maybe optimize this, potentially using AROUND packets?
|
||||
for (Chunk *chunk : chnks) {
|
||||
for (const EntityRef otherRef : chunk->entities) {
|
||||
// skip oneself
|
||||
if (ref == otherRef)
|
||||
continue;
|
||||
|
||||
Entity *other = otherRef.getEntity();
|
||||
|
||||
// notify all visible players of the existence of this Entity
|
||||
if (alive && otherRef.kind == EntityKind::PLAYER) {
|
||||
ent->enterIntoViewOf(otherRef.sock);
|
||||
}
|
||||
|
||||
// notify this *player* of the existence of all visible Entities
|
||||
if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
|
||||
other->enterIntoViewOf(ref.sock);
|
||||
}
|
||||
|
||||
// for mobs, increment playersInView
|
||||
if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
|
||||
((Mob*)ent)->playersInView++;
|
||||
if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
|
||||
((Mob*)other)->playersInView++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref) {
|
||||
Entity *ent = ref.getEntity();
|
||||
bool alive = ent->isExtant();
|
||||
|
||||
// TODO: same as above
|
||||
for (Chunk *chunk : chnks) {
|
||||
for (const EntityRef otherRef : chunk->entities) {
|
||||
// skip oneself
|
||||
if (ref == otherRef)
|
||||
continue;
|
||||
|
||||
Entity *other = otherRef.getEntity();
|
||||
|
||||
// notify all visible players of the departure of this Entity
|
||||
if (alive && otherRef.kind == EntityKind::PLAYER) {
|
||||
ent->disappearFromViewOf(otherRef.sock);
|
||||
}
|
||||
|
||||
// notify this *player* of the departure of all visible Entities
|
||||
if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
|
||||
other->disappearFromViewOf(ref.sock);
|
||||
}
|
||||
|
||||
// for mobs, decrement playersInView
|
||||
if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
|
||||
((Mob*)ent)->playersInView--;
|
||||
if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
|
||||
((Mob*)other)->playersInView--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void emptyChunk(ChunkPos chunkPos) {
|
||||
if (!chunkExists(chunkPos)) {
|
||||
std::cout << "[WARN] Tried to empty chunk that doesn't exist\n";
|
||||
return; // chunk doesn't exist, we don't need to do anything
|
||||
}
|
||||
|
||||
Chunk* chunk = chunks[chunkPos];
|
||||
|
||||
if (chunk->nplayers > 0) {
|
||||
std::cout << "[WARN] Tried to empty chunk that still had players\n";
|
||||
return; // chunk doesn't exist, we don't need to do anything
|
||||
}
|
||||
|
||||
// unspawn all of the mobs/npcs
|
||||
std::set refs(chunk->entities);
|
||||
for (const EntityRef ref : refs) {
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
assert(0);
|
||||
|
||||
// every call of this will check if the chunk is empty and delete it if so
|
||||
NPCManager::destroyNPC(ref.id);
|
||||
}
|
||||
}
|
||||
|
||||
void Chunking::updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to) {
|
||||
Entity* ent = ref.getEntity();
|
||||
|
||||
// move to other chunk's player set
|
||||
untrackEntity(from, ref); // this will delete the chunk if it's empty
|
||||
|
||||
// if the new chunk doesn't exist, make it first
|
||||
if (!chunkExists(to))
|
||||
newChunk(to);
|
||||
|
||||
trackEntity(to, ref);
|
||||
|
||||
// calculate viewable chunks from both points
|
||||
std::set<Chunk*> oldViewables = getViewableChunks(from);
|
||||
std::set<Chunk*> newViewables = getViewableChunks(to);
|
||||
std::set<Chunk*> toExit, toEnter;
|
||||
|
||||
/*
|
||||
* Calculate diffs. This is done to prevent phasing on chunk borders.
|
||||
* toExit will contain old viewables - new viewables, so the player will only be exited in chunks that are out of sight.
|
||||
* toEnter contains the opposite: new viewables - old viewables, chunks where we previously weren't visible from before.
|
||||
*/
|
||||
std::set_difference(oldViewables.begin(), oldViewables.end(), newViewables.begin(), newViewables.end(),
|
||||
std::inserter(toExit, toExit.end())); // chunks we must be EXITed from (old - new)
|
||||
std::set_difference(newViewables.begin(), newViewables.end(), oldViewables.begin(), oldViewables.end(),
|
||||
std::inserter(toEnter, toEnter.end())); // chunks we must be ENTERed into (new - old)
|
||||
|
||||
// update views
|
||||
removeEntityFromChunks(toExit, ref);
|
||||
addEntityToChunks(toEnter, ref);
|
||||
|
||||
ent->chunkPos = to; // update cached chunk position
|
||||
// updated cached viewable chunks
|
||||
ent->viewableChunks.clear();
|
||||
ent->viewableChunks.insert(newViewables.begin(), newViewables.end());
|
||||
}
|
||||
|
||||
bool Chunking::chunkExists(ChunkPos chunk) {
|
||||
return chunks.find(chunk) != chunks.end();
|
||||
}
|
||||
|
||||
ChunkPos Chunking::chunkPosAt(int posX, int posY, uint64_t instanceID) {
|
||||
return ChunkPos(posX / (settings::VIEWDISTANCE / 3), posY / (settings::VIEWDISTANCE / 3), instanceID);
|
||||
}
|
||||
|
||||
std::set<Chunk*> Chunking::getViewableChunks(ChunkPos chunk) {
|
||||
std::set<Chunk*> chnks;
|
||||
|
||||
int x, y;
|
||||
uint64_t inst;
|
||||
std::tie(x, y, inst) = chunk;
|
||||
|
||||
// grabs surrounding chunks if they exist
|
||||
for (int i = -1; i < 2; i++) {
|
||||
for (int z = -1; z < 2; z++) {
|
||||
ChunkPos pos = ChunkPos(x+i, y+z, inst);
|
||||
|
||||
// if chunk exists, add it to the set
|
||||
if (chunkExists(pos))
|
||||
chnks.insert(chunks[pos]);
|
||||
}
|
||||
}
|
||||
|
||||
return chnks;
|
||||
}
|
||||
|
||||
/*
|
||||
* inefficient algorithm to get all chunks from a specific instance
|
||||
*/
|
||||
std::vector<ChunkPos> Chunking::getChunksInMap(uint64_t mapNum) {
|
||||
std::vector<ChunkPos> chnks;
|
||||
|
||||
for (auto it = chunks.begin(); it != chunks.end(); it++) {
|
||||
if (std::get<2>(it->first) == mapNum) {
|
||||
chnks.push_back(it->first);
|
||||
}
|
||||
}
|
||||
|
||||
return chnks;
|
||||
}
|
||||
|
||||
/*
|
||||
* Used only for eggs; use npc->playersInView for everything visible
|
||||
*/
|
||||
bool Chunking::inPopulatedChunks(std::set<Chunk*>* chnks) {
|
||||
for (auto it = chnks->begin(); it != chnks->end(); it++) {
|
||||
if ((*it)->nplayers > 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Chunking::createInstance(uint64_t instanceID) {
|
||||
std::vector<ChunkPos> templateChunks = getChunksInMap(MAPNUM(instanceID)); // base instance chunks
|
||||
|
||||
// only instantiate if the instance doesn't exist already
|
||||
if (getChunksInMap(instanceID).size() != 0) {
|
||||
std::cout << "Instance " << instanceID << " already exists" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Creating instance " << instanceID << std::endl;
|
||||
for (ChunkPos &coords : templateChunks) {
|
||||
for (const EntityRef ref : chunks[coords]->entities) {
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
continue;
|
||||
|
||||
int npcID = ref.id;
|
||||
BaseNPC* baseNPC = (BaseNPC*)ref.getEntity();
|
||||
|
||||
// make a copy of each NPC in the template chunks and put them in the new instance
|
||||
if (baseNPC->kind == EntityKind::MOB) {
|
||||
if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID)
|
||||
continue; // follower; don't copy individually
|
||||
|
||||
Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->angle,
|
||||
instanceID, baseNPC->type, NPCManager::NPCData[baseNPC->type], NPCManager::nextId--);
|
||||
NPCManager::NPCs[newMob->id] = newMob;
|
||||
|
||||
// if in a group, copy over group members as well
|
||||
if (((Mob*)baseNPC)->groupLeader != 0) {
|
||||
newMob->groupLeader = newMob->id; // set leader ID for new leader
|
||||
Mob* mobData = (Mob*)baseNPC;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (mobData->groupMember[i] != 0) {
|
||||
int followerID = NPCManager::nextId--; // id for follower
|
||||
BaseNPC* baseFollower = NPCManager::NPCs[mobData->groupMember[i]]; // follower from template
|
||||
// new follower instance
|
||||
Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->angle,
|
||||
instanceID, baseFollower->type, NPCManager::NPCData[baseFollower->type], followerID);
|
||||
// add follower to NPC maps
|
||||
NPCManager::NPCs[followerID] = newMobFollower;
|
||||
// set follower-specific properties
|
||||
newMobFollower->groupLeader = newMob->id;
|
||||
newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX;
|
||||
newMobFollower->offsetY = ((Mob*)baseFollower)->offsetY;
|
||||
// add follower copy to leader copy
|
||||
newMob->groupMember[i] = followerID;
|
||||
NPCManager::updateNPCPosition(followerID, baseFollower->x, baseFollower->y, baseFollower->z,
|
||||
instanceID, baseFollower->angle);
|
||||
}
|
||||
}
|
||||
}
|
||||
NPCManager::updateNPCPosition(newMob->id, baseNPC->x, baseNPC->y, baseNPC->z,
|
||||
instanceID, baseNPC->angle);
|
||||
} else {
|
||||
BaseNPC* newNPC = new BaseNPC(baseNPC->angle, instanceID, baseNPC->type, NPCManager::nextId--);
|
||||
NPCManager::NPCs[newNPC->id] = newNPC;
|
||||
NPCManager::updateNPCPosition(newNPC->id, baseNPC->x, baseNPC->y, baseNPC->z,
|
||||
instanceID, baseNPC->angle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void destroyInstance(uint64_t instanceID) {
|
||||
std::vector<ChunkPos> instanceChunks = getChunksInMap(instanceID);
|
||||
std::cout << "Deleting instance " << instanceID << " (" << instanceChunks.size() << " chunks)" << std::endl;
|
||||
for (ChunkPos& coords : instanceChunks) {
|
||||
emptyChunk(coords);
|
||||
}
|
||||
}
|
||||
|
||||
void Chunking::destroyInstanceIfEmpty(uint64_t instanceID) {
|
||||
if (PLAYERID(instanceID) == 0)
|
||||
return; // don't clean up overworld/IZ chunks
|
||||
|
||||
std::vector<ChunkPos> sourceChunkCoords = getChunksInMap(instanceID);
|
||||
|
||||
for (ChunkPos& coords : sourceChunkCoords) {
|
||||
Chunk* chunk = chunks[coords];
|
||||
|
||||
if (chunk->nplayers > 0)
|
||||
return; // there are still players inside
|
||||
}
|
||||
|
||||
destroyInstance(instanceID);
|
||||
}
|
||||
51
src/Chunking.hpp
Normal file
51
src/Chunking.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "EntityRef.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
class Chunk {
|
||||
public:
|
||||
std::set<EntityRef> entities;
|
||||
int nplayers = 0;
|
||||
};
|
||||
|
||||
// to help the readability of ChunkPos
|
||||
typedef std::tuple<int, int, uint64_t> _ChunkPos;
|
||||
|
||||
class ChunkPos : public _ChunkPos {
|
||||
public:
|
||||
ChunkPos() : _ChunkPos(0, 0, (uint64_t) -1) {}
|
||||
ChunkPos(int x, int y, uint64_t inst) : _ChunkPos(x, y, inst) {}
|
||||
};
|
||||
|
||||
enum {
|
||||
INSTANCE_OVERWORLD, // default instance every player starts in
|
||||
INSTANCE_IZ, // these aren't actually used
|
||||
INSTANCE_UNIQUE // these aren't actually used
|
||||
};
|
||||
|
||||
namespace Chunking {
|
||||
extern std::map<ChunkPos, Chunk*> chunks;
|
||||
|
||||
extern const ChunkPos INVALID_CHUNK;
|
||||
|
||||
void updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to);
|
||||
|
||||
void trackEntity(ChunkPos chunkPos, const EntityRef ref);
|
||||
void untrackEntity(ChunkPos chunkPos, const EntityRef ref);
|
||||
|
||||
void addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref);
|
||||
void removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref);
|
||||
|
||||
bool chunkExists(ChunkPos chunk);
|
||||
ChunkPos chunkPosAt(int posX, int posY, uint64_t instanceID);
|
||||
std::set<Chunk*> getViewableChunks(ChunkPos chunkPos);
|
||||
std::vector<ChunkPos> getChunksInMap(uint64_t mapNum);
|
||||
|
||||
bool inPopulatedChunks(std::set<Chunk*>* chnks);
|
||||
void createInstance(uint64_t);
|
||||
void destroyInstanceIfEmpty(uint64_t);
|
||||
}
|
||||
1007
src/Combat.cpp
Normal file
1007
src/Combat.cpp
Normal file
File diff suppressed because it is too large
Load Diff
23
src/Combat.hpp
Normal file
23
src/Combat.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "MobAI.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
struct Bullet {
|
||||
int pointDamage;
|
||||
int groupDamage;
|
||||
bool weaponBoost;
|
||||
int bulletType;
|
||||
};
|
||||
|
||||
namespace Combat {
|
||||
extern std::map<int32_t, std::map<int8_t, Bullet>> Bullets;
|
||||
|
||||
void init();
|
||||
|
||||
void npcAttackPc(Mob *mob, time_t currTime);
|
||||
void genQItemRolls(std::vector<Player*> players, std::map<int, int>& rolls);
|
||||
}
|
||||
1236
src/CustomCommands.cpp
Normal file
1236
src/CustomCommands.cpp
Normal file
File diff suppressed because it is too large
Load Diff
11
src/CustomCommands.hpp
Normal file
11
src/CustomCommands.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace CustomCommands {
|
||||
void init();
|
||||
|
||||
bool runCmd(std::string full, CNSocket* sock);
|
||||
};
|
||||
694
src/Database.cpp
694
src/Database.cpp
@@ -1,694 +0,0 @@
|
||||
#include "Database.hpp"
|
||||
#include "Database.hpp"
|
||||
#include "contrib/bcrypt/BCrypt.hpp"
|
||||
#include "CNProtocol.hpp"
|
||||
#include <string>
|
||||
#include "contrib/JSON.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "Player.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "contrib/sqlite/sqlite_orm.h"
|
||||
#include "MissionManager.hpp"
|
||||
|
||||
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
|
||||
#include "mingw/mingw.mutex.h"
|
||||
#else
|
||||
#include <mutex>
|
||||
#endif
|
||||
|
||||
using namespace sqlite_orm;
|
||||
|
||||
std::mutex dbCrit;
|
||||
|
||||
# pragma region DatabaseScheme
|
||||
auto db = make_storage("database.db",
|
||||
make_table("Accounts",
|
||||
make_column("AccountID", &Database::Account::AccountID, autoincrement(), primary_key()),
|
||||
make_column("Login", &Database::Account::Login),
|
||||
make_column("Password", &Database::Account::Password),
|
||||
make_column("Selected", &Database::Account::Selected),
|
||||
make_column("Created", &Database::Account::Created),
|
||||
make_column("LastLogin", &Database::Account::LastLogin)
|
||||
),
|
||||
make_table("Players",
|
||||
make_column("PlayerID", &Database::DbPlayer::PlayerID, autoincrement(), primary_key()),
|
||||
make_column("AccountID", &Database::DbPlayer::AccountID),
|
||||
make_column("Slot", &Database::DbPlayer::slot),
|
||||
make_column("Firstname", &Database::DbPlayer::FirstName, collate_nocase()),
|
||||
make_column("LastName", &Database::DbPlayer::LastName, collate_nocase()),
|
||||
make_column("Created", &Database::DbPlayer::Created),
|
||||
make_column("LastLogin", &Database::DbPlayer::LastLogin),
|
||||
make_column("Level", &Database::DbPlayer::Level),
|
||||
make_column("Nano1", &Database::DbPlayer::Nano1),
|
||||
make_column("Nano2", &Database::DbPlayer::Nano2),
|
||||
make_column("Nano3", &Database::DbPlayer::Nano3),
|
||||
make_column("AppearanceFlag", &Database::DbPlayer::AppearanceFlag),
|
||||
make_column("TutorialFlag", &Database::DbPlayer::TutorialFlag),
|
||||
make_column("PayZoneFlag", &Database::DbPlayer::PayZoneFlag),
|
||||
make_column("XCoordinates", &Database::DbPlayer::x_coordinates),
|
||||
make_column("YCoordinates", &Database::DbPlayer::y_coordinates),
|
||||
make_column("ZCoordinates", &Database::DbPlayer::z_coordinates),
|
||||
make_column("Angle", &Database::DbPlayer::angle),
|
||||
make_column("Body", &Database::DbPlayer::Body),
|
||||
make_column("Class", &Database::DbPlayer::Class),
|
||||
make_column("EyeColor", &Database::DbPlayer::EyeColor),
|
||||
make_column("FaceStyle", &Database::DbPlayer::FaceStyle),
|
||||
make_column("Gender", &Database::DbPlayer::Gender),
|
||||
make_column("HP", &Database::DbPlayer::HP),
|
||||
make_column("HairColor", &Database::DbPlayer::HairColor),
|
||||
make_column("HairStyle", &Database::DbPlayer::HairStyle),
|
||||
make_column("Height", &Database::DbPlayer::Height),
|
||||
make_column("NameCheck", &Database::DbPlayer::NameCheck),
|
||||
make_column("SkinColor", &Database::DbPlayer::SkinColor),
|
||||
make_column("AccountLevel", &Database::DbPlayer::AccountLevel),
|
||||
make_column("FusionMatter", &Database::DbPlayer::FusionMatter),
|
||||
make_column("Taros", &Database::DbPlayer::Taros),
|
||||
make_column("Quests", &Database::DbPlayer::QuestFlag),
|
||||
make_column("BatteryW", &Database::DbPlayer::BatteryW),
|
||||
make_column("BatteryN", &Database::DbPlayer::BatteryN),
|
||||
make_column("Mentor", &Database::DbPlayer::Mentor),
|
||||
make_column("WarpLocationFlag", &Database::DbPlayer::WarpLocationFlag),
|
||||
make_column("SkywayLocationFlag1", &Database::DbPlayer::SkywayLocationFlag1),
|
||||
make_column("SkywayLocationFlag2", &Database::DbPlayer::SkywayLocationFlag2),
|
||||
make_column("CurrentMissionID", &Database::DbPlayer::CurrentMissionID)
|
||||
),
|
||||
make_table("Inventory",
|
||||
make_column("PlayerId", &Database::Inventory::playerId),
|
||||
make_column("Slot", &Database::Inventory::slot),
|
||||
make_column("Id", &Database::Inventory::id),
|
||||
make_column("Type", &Database::Inventory::Type),
|
||||
make_column("Opt", &Database::Inventory::Opt),
|
||||
make_column("TimeLimit", &Database::Inventory::TimeLimit)
|
||||
),
|
||||
make_table("Nanos",
|
||||
make_column("PlayerId", &Database::Nano::playerId),
|
||||
make_column("Id", &Database::Nano::iID),
|
||||
make_column("Skill", &Database::Nano::iSkillID),
|
||||
make_column("Stamina", &Database::Nano::iStamina)
|
||||
),
|
||||
make_table("RunningQuests",
|
||||
make_column("PlayerId", &Database::DbQuest::PlayerId),
|
||||
make_column("TaskId", &Database::DbQuest::TaskId),
|
||||
make_column("RemainingNPCCount1", &Database::DbQuest::RemainingNPCCount1),
|
||||
make_column("RemainingNPCCount2", &Database::DbQuest::RemainingNPCCount2),
|
||||
make_column("RemainingNPCCount3", &Database::DbQuest::RemainingNPCCount3)
|
||||
)
|
||||
);
|
||||
|
||||
# pragma endregion DatabaseScheme
|
||||
|
||||
#pragma region LoginServer
|
||||
|
||||
void Database::open() {
|
||||
// this parameter means it will try to preserve data during migration
|
||||
bool preserve = true;
|
||||
db.sync_schema(preserve);
|
||||
std::cout << "[INFO] Database in operation ";
|
||||
int accounts = getAccountsCount();
|
||||
int players = getPlayersCount();
|
||||
std::string message = "";
|
||||
if (accounts > 0) {
|
||||
message += ": Found " + std::to_string(accounts) + " Account";
|
||||
if (accounts > 1)
|
||||
message += "s";
|
||||
}
|
||||
if (players > 0) {
|
||||
message += " and " + std::to_string(players) + " Player Character";
|
||||
if (players > 1)
|
||||
message += "s";
|
||||
}
|
||||
std::cout << message << std::endl;
|
||||
}
|
||||
|
||||
int Database::getAccountsCount() {
|
||||
return db.count<Account>();
|
||||
}
|
||||
|
||||
int Database::getPlayersCount() {
|
||||
return db.count<DbPlayer>();
|
||||
}
|
||||
|
||||
int Database::addAccount(std::string login, std::string password) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
password = BCrypt::generateHash(password);
|
||||
Account account = {};
|
||||
account.Login = login;
|
||||
account.Password = password;
|
||||
account.Selected = 1;
|
||||
account.Created = getTimestamp();
|
||||
return db.insert(account);
|
||||
}
|
||||
|
||||
void Database::updateSelected(int accountId, int slot) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
Account acc = db.get<Account>(accountId);
|
||||
acc.Selected = slot;
|
||||
// timestamp
|
||||
acc.LastLogin = getTimestamp();
|
||||
db.update(acc);
|
||||
}
|
||||
|
||||
std::unique_ptr<Database::Account> Database::findAccount(std::string login) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
// this is awful, I've tried everything to improve it
|
||||
auto find = db.get_all<Account>(
|
||||
where(c(&Account::Login) == login), limit(1));
|
||||
if (find.empty())
|
||||
return nullptr;
|
||||
return
|
||||
std::unique_ptr<Account>(new Account(find.front()));
|
||||
}
|
||||
|
||||
bool Database::isNameFree(sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
std::string First = U16toU8(nameCheck->szFirstName);
|
||||
std::string Last = U16toU8(nameCheck->szLastName);
|
||||
return
|
||||
(db.get_all<DbPlayer>
|
||||
(where((c(&DbPlayer::FirstName) == First)
|
||||
and (c(&DbPlayer::LastName) == Last)))
|
||||
.empty());
|
||||
}
|
||||
|
||||
int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
// fail if the player already has 4 or more characters
|
||||
if (db.count<DbPlayer>(where(c(&DbPlayer::AccountID) == AccountID)) >= 4)
|
||||
return -1;
|
||||
|
||||
DbPlayer create = {};
|
||||
|
||||
// set timestamp
|
||||
create.Created = getTimestamp();
|
||||
// save packet data
|
||||
create.FirstName = U16toU8(save->szFirstName);
|
||||
create.LastName = U16toU8(save->szLastName);
|
||||
create.slot = save->iSlotNum;
|
||||
create.AccountID = AccountID;
|
||||
|
||||
// set flags
|
||||
create.AppearanceFlag = 0;
|
||||
create.TutorialFlag = 0;
|
||||
create.PayZoneFlag = 0;
|
||||
|
||||
// set namecheck based on setting
|
||||
if (settings::APPROVEALLNAMES || save->iFNCode)
|
||||
create.NameCheck = 1;
|
||||
else
|
||||
create.NameCheck = 0;
|
||||
|
||||
// create default body character
|
||||
create.Body = 0;
|
||||
create.Class = 0;
|
||||
create.EyeColor = 1;
|
||||
create.FaceStyle = 1;
|
||||
create.Gender = 1;
|
||||
create.Level = 1;
|
||||
create.HP = PC_MAXHEALTH(create.Level);
|
||||
create.HairColor = 1;
|
||||
create.HairStyle = 1;
|
||||
create.Height = 0;
|
||||
create.SkinColor = 1;
|
||||
create.AccountLevel = settings::ACCLEVEL;
|
||||
create.x_coordinates = settings::SPAWN_X;
|
||||
create.y_coordinates = settings::SPAWN_Y;
|
||||
create.z_coordinates = settings::SPAWN_Z;
|
||||
create.angle = settings::SPAWN_ANGLE;
|
||||
// set mentor to computress
|
||||
create.Mentor = 5;
|
||||
|
||||
// initialize the quest blob to 128 0-bytes
|
||||
create.QuestFlag = std::vector<char>(128, 0);
|
||||
|
||||
return db.insert(create);
|
||||
}
|
||||
|
||||
void Database::finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
DbPlayer finish = getDbPlayerById(character->PCStyle.iPC_UID);
|
||||
finish.AppearanceFlag = 1;
|
||||
finish.Body = character->PCStyle.iBody;
|
||||
finish.Class = character->PCStyle.iClass;
|
||||
finish.EyeColor = character->PCStyle.iEyeColor;
|
||||
finish.FaceStyle = character->PCStyle.iFaceStyle;
|
||||
finish.Gender = character->PCStyle.iGender;
|
||||
finish.HairColor = character->PCStyle.iHairColor;
|
||||
finish.HairStyle = character->PCStyle.iHairStyle;
|
||||
finish.Height = character->PCStyle.iHeight;
|
||||
finish.Level = 1;
|
||||
finish.SkinColor = character->PCStyle.iSkinColor;
|
||||
db.update(finish);
|
||||
// clothes
|
||||
Inventory Foot, LB, UB;
|
||||
Foot.playerId = character->PCStyle.iPC_UID;
|
||||
Foot.id = character->sOn_Item.iEquipFootID;
|
||||
Foot.Type = 3;
|
||||
Foot.slot = 3;
|
||||
Foot.Opt = 1;
|
||||
Foot.TimeLimit = 0;
|
||||
db.insert(Foot);
|
||||
LB.playerId = character->PCStyle.iPC_UID;
|
||||
LB.id = character->sOn_Item.iEquipLBID;
|
||||
LB.Type = 2;
|
||||
LB.slot = 2;
|
||||
LB.Opt = 1;
|
||||
LB.TimeLimit = 0;
|
||||
db.insert(LB);
|
||||
UB.playerId = character->PCStyle.iPC_UID;
|
||||
UB.id = character->sOn_Item.iEquipUBID;
|
||||
UB.Type = 1;
|
||||
UB.slot = 1;
|
||||
UB.Opt = 1;
|
||||
UB.TimeLimit = 0;
|
||||
db.insert(UB);
|
||||
}
|
||||
|
||||
void Database::finishTutorial(int PlayerID) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
Player finish = getPlayer(PlayerID);
|
||||
// set flag
|
||||
finish.PCStyle2.iTutorialFlag= 1;
|
||||
// add Gun
|
||||
Inventory LightningGun = {};
|
||||
LightningGun.playerId = PlayerID;
|
||||
LightningGun.id = 328;
|
||||
LightningGun.slot = 0;
|
||||
LightningGun.Type = 0;
|
||||
LightningGun.Opt = 1;
|
||||
db.insert(LightningGun);
|
||||
// add Nano
|
||||
Nano Buttercup = {};
|
||||
Buttercup.playerId = PlayerID;
|
||||
Buttercup.iID = 1;
|
||||
Buttercup.iSkillID = 1;
|
||||
Buttercup.iStamina = 150;
|
||||
finish.equippedNanos[0] = 1;
|
||||
db.insert(Buttercup);
|
||||
// save missions
|
||||
MissionManager::saveMission(&finish, 0);
|
||||
MissionManager::saveMission(&finish, 1);
|
||||
|
||||
db.update(playerToDb(&finish));
|
||||
}
|
||||
|
||||
int Database::deleteCharacter(int characterID, int userID) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
auto find =
|
||||
db.get_all<DbPlayer>(where(c(&DbPlayer::PlayerID) == characterID and c(&DbPlayer::AccountID)==userID));
|
||||
int slot = find.front().slot;
|
||||
db.remove<DbPlayer>(find.front().PlayerID);
|
||||
db.remove_all<Inventory>(where(c(&Inventory::playerId) == characterID));
|
||||
db.remove_all<Nano>(where(c(&Nano::playerId) == characterID));
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
std::vector <Player> Database::getCharacters(int UserID) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
std::vector<DbPlayer>characters =
|
||||
db.get_all<DbPlayer>(where
|
||||
(c(&DbPlayer::AccountID) == UserID));
|
||||
// parsing DbPlayer to Player
|
||||
std::vector<Player> result = std::vector<Player>();
|
||||
for (auto &character : characters) {
|
||||
Player toadd = DbToPlayer(character);
|
||||
result.push_back(
|
||||
toadd
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// XXX: This is never called?
|
||||
void Database::evaluateCustomName(int characterID, CustomName decision) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
DbPlayer player = getDbPlayerById(characterID);
|
||||
player.NameCheck = (int)decision;
|
||||
db.update(player);
|
||||
}
|
||||
|
||||
void Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
DbPlayer Player = getDbPlayerById(save->iPCUID);
|
||||
Player.FirstName = U16toU8(save->szFirstName);
|
||||
Player.LastName = U16toU8(save->szLastName);
|
||||
if (settings::APPROVEALLNAMES || save->iFNCode)
|
||||
Player.NameCheck = 1;
|
||||
else
|
||||
Player.NameCheck = 0;
|
||||
db.update(Player);
|
||||
}
|
||||
|
||||
Database::DbPlayer Database::playerToDb(Player *player) {
|
||||
// TODO: move stuff that is never updated to separate table so it doesn't try to update it every time
|
||||
DbPlayer result = {};
|
||||
|
||||
result.PlayerID = player->iID;
|
||||
result.AccountID = player->accountId;
|
||||
result.AppearanceFlag = player->PCStyle2.iAppearanceFlag;
|
||||
result.Body = player->PCStyle.iBody;
|
||||
result.Class = player->PCStyle.iClass;
|
||||
result.EyeColor = player->PCStyle.iEyeColor;
|
||||
result.FaceStyle = player->PCStyle.iFaceStyle;
|
||||
result.FirstName = U16toU8( player->PCStyle.szFirstName);
|
||||
result.FusionMatter = player->fusionmatter;
|
||||
result.Gender = player->PCStyle.iGender;
|
||||
result.HairColor = player->PCStyle.iHairColor;
|
||||
result.HairStyle = player->PCStyle.iHairStyle;
|
||||
result.Height = player->PCStyle.iHeight;
|
||||
result.HP = player->HP;
|
||||
result.AccountLevel = player->accountLevel;
|
||||
result.LastName = U16toU8(player->PCStyle.szLastName);
|
||||
result.Level = player->level;
|
||||
result.NameCheck = player->PCStyle.iNameCheck;
|
||||
result.PayZoneFlag = player->PCStyle2.iPayzoneFlag;
|
||||
result.PlayerID = player->PCStyle.iPC_UID;
|
||||
result.SkinColor = player->PCStyle.iSkinColor;
|
||||
result.slot = player->slot;
|
||||
result.Taros = player->money;
|
||||
result.TutorialFlag = player->PCStyle2.iTutorialFlag;
|
||||
if (player->instanceID == 0) { // only save coords if player isn't instanced
|
||||
result.x_coordinates = player->x;
|
||||
result.y_coordinates = player->y;
|
||||
result.z_coordinates = player->z;
|
||||
result.angle = player->angle;
|
||||
} else {
|
||||
result.x_coordinates = player->lastX;
|
||||
result.y_coordinates = player->lastY;
|
||||
result.z_coordinates = player->lastZ;
|
||||
result.angle = player->lastAngle;
|
||||
}
|
||||
result.Nano1 = player->equippedNanos[0];
|
||||
result.Nano2 = player->equippedNanos[1];
|
||||
result.Nano3 = player->equippedNanos[2];
|
||||
result.BatteryN = player->batteryN;
|
||||
result.BatteryW = player->batteryW;
|
||||
result.Mentor = player->mentor;
|
||||
result.WarpLocationFlag = player->iWarpLocationFlag;
|
||||
result.SkywayLocationFlag1 = player->aSkywayLocationFlag[0];
|
||||
result.SkywayLocationFlag2 = player->aSkywayLocationFlag[1];
|
||||
result.CurrentMissionID = player->CurrentMissionID;
|
||||
|
||||
// timestamp
|
||||
result.LastLogin = getTimestamp();
|
||||
result.Created = player->creationTime;
|
||||
|
||||
// save completed quests
|
||||
result.QuestFlag = std::vector<char>((char*)player->aQuestFlag, (char*)player->aQuestFlag + 128);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Player Database::DbToPlayer(DbPlayer player) {
|
||||
Player result = {}; // fixes some weird memory errors, this zeros out the members (not the padding inbetween though)
|
||||
|
||||
result.iID = player.PlayerID;
|
||||
result.accountId = player.AccountID;
|
||||
result.creationTime = player.Created;
|
||||
result.PCStyle2.iAppearanceFlag = player.AppearanceFlag;
|
||||
result.PCStyle.iBody = player.Body;
|
||||
result.PCStyle.iClass = player.Class;
|
||||
result.PCStyle.iEyeColor = player.EyeColor;
|
||||
result.PCStyle.iFaceStyle = player.FaceStyle;
|
||||
U8toU16(player.FirstName, result.PCStyle.szFirstName, sizeof(result.PCStyle.szFirstName));
|
||||
result.PCStyle.iGender = player.Gender;
|
||||
result.PCStyle.iHairColor = player.HairColor;
|
||||
result.PCStyle.iHairStyle = player.HairStyle;
|
||||
result.PCStyle.iHeight = player.Height;
|
||||
result.HP = player.HP;
|
||||
result.accountLevel = player.AccountLevel;
|
||||
U8toU16(player.LastName, result.PCStyle.szLastName, sizeof(result.PCStyle.szLastName));
|
||||
result.level = player.Level;
|
||||
result.PCStyle.iNameCheck = player.NameCheck;
|
||||
result.PCStyle2.iPayzoneFlag = player.PayZoneFlag;
|
||||
result.iID = player.PlayerID;
|
||||
result.PCStyle.iPC_UID = player.PlayerID;
|
||||
result.PCStyle.iSkinColor = player.SkinColor;
|
||||
result.slot = player.slot;
|
||||
result.PCStyle2.iTutorialFlag = player.TutorialFlag;
|
||||
result.x = player.x_coordinates;
|
||||
result.y = player.y_coordinates;
|
||||
result.z = player.z_coordinates;
|
||||
result.angle = player.angle;
|
||||
result.money = player.Taros;
|
||||
result.fusionmatter = player.FusionMatter;
|
||||
result.batteryN = player.BatteryN;
|
||||
result.batteryW = player.BatteryW;
|
||||
result.mentor = player.Mentor;
|
||||
result.CurrentMissionID = player.CurrentMissionID;
|
||||
|
||||
result.equippedNanos[0] = player.Nano1;
|
||||
result.equippedNanos[1] = player.Nano2;
|
||||
result.equippedNanos[2] = player.Nano3;
|
||||
|
||||
result.inCombat = false;
|
||||
|
||||
result.iWarpLocationFlag = player.WarpLocationFlag;
|
||||
result.aSkywayLocationFlag[0] = player.SkywayLocationFlag1;
|
||||
result.aSkywayLocationFlag[1] = player.SkywayLocationFlag2;
|
||||
|
||||
Database::getInventory(&result);
|
||||
Database::removeExpiredVehicles(&result);
|
||||
Database::getNanos(&result);
|
||||
Database::getQuests(&result);
|
||||
|
||||
// load completed quests
|
||||
memcpy(&result.aQuestFlag, player.QuestFlag.data(), std::min(sizeof(result.aQuestFlag), player.QuestFlag.size()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Database::DbPlayer Database::getDbPlayerById(int id) {
|
||||
return db.get_all<DbPlayer>(where(c(&DbPlayer::PlayerID) == id)).front();
|
||||
}
|
||||
|
||||
Player Database::getPlayer(int id) {
|
||||
return DbToPlayer(
|
||||
getDbPlayerById(id)
|
||||
);
|
||||
}
|
||||
|
||||
#pragma endregion LoginServer
|
||||
|
||||
#pragma region ShardServer
|
||||
|
||||
void Database::updatePlayer(Player *player) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
DbPlayer toUpdate = playerToDb(player);
|
||||
db.update(toUpdate);
|
||||
updateInventory(player);
|
||||
updateNanos(player);
|
||||
updateQuests(player);
|
||||
}
|
||||
|
||||
void Database::updateInventory(Player *player){
|
||||
// start transaction
|
||||
db.begin_transaction();
|
||||
// remove all
|
||||
db.remove_all<Inventory>(
|
||||
where(c(&Inventory::playerId) == player->iID)
|
||||
);
|
||||
// insert equip
|
||||
for (int i = 0; i < AEQUIP_COUNT; i++) {
|
||||
if (player->Equip[i].iID != 0) {
|
||||
sItemBase* next = &player->Equip[i];
|
||||
Inventory toAdd = {};
|
||||
toAdd.playerId = player->iID;
|
||||
toAdd.slot = i;
|
||||
toAdd.id = next->iID;
|
||||
toAdd.Opt = next->iOpt;
|
||||
toAdd.Type = next->iType;
|
||||
toAdd.TimeLimit = next->iTimeLimit;
|
||||
db.insert(toAdd);
|
||||
}
|
||||
}
|
||||
// insert inventory
|
||||
for (int i = 0; i < AINVEN_COUNT; i++) {
|
||||
if (player->Inven[i].iID != 0) {
|
||||
sItemBase* next = &player->Inven[i];
|
||||
Inventory toAdd = {};
|
||||
toAdd.playerId = player->iID;
|
||||
toAdd.slot = i + AEQUIP_COUNT;
|
||||
toAdd.id = next->iID;
|
||||
toAdd.Opt = next->iOpt;
|
||||
toAdd.Type = next->iType;
|
||||
toAdd.TimeLimit = next->iTimeLimit;
|
||||
db.insert(toAdd);
|
||||
}
|
||||
}
|
||||
// insert bank
|
||||
for (int i = 0; i < ABANK_COUNT; i++) {
|
||||
if (player->Bank[i].iID != 0) {
|
||||
sItemBase* next = &player->Bank[i];
|
||||
Inventory toAdd = {};
|
||||
toAdd.playerId = player->iID;
|
||||
toAdd.slot = i + AEQUIP_COUNT + AINVEN_COUNT;
|
||||
toAdd.id = next->iID;
|
||||
toAdd.Opt = next->iOpt;
|
||||
toAdd.Type = next->iType;
|
||||
toAdd.TimeLimit = next->iTimeLimit;
|
||||
db.insert(toAdd);
|
||||
}
|
||||
}
|
||||
// insert quest items
|
||||
for (int i = 0; i < AQINVEN_COUNT; i++) {
|
||||
if (player->QInven[i].iID != 0) {
|
||||
sItemBase* next = &player->QInven[i];
|
||||
Inventory toAdd = {};
|
||||
toAdd.playerId = player->iID;
|
||||
toAdd.slot = i + AEQUIP_COUNT + AINVEN_COUNT + ABANK_COUNT;
|
||||
toAdd.id = next->iID;
|
||||
toAdd.Opt = next->iOpt;
|
||||
toAdd.Type = next->iType;
|
||||
toAdd.TimeLimit = next->iTimeLimit;
|
||||
db.insert(toAdd);
|
||||
}
|
||||
}
|
||||
db.commit();
|
||||
}
|
||||
|
||||
void Database::updateNanos(Player *player) {
|
||||
// start transaction
|
||||
db.begin_transaction();
|
||||
// remove all
|
||||
db.remove_all<Nano>(
|
||||
where(c(&Nano::playerId) == player->iID)
|
||||
);
|
||||
// insert
|
||||
for (int i=1; i < SIZEOF_NANO_BANK_SLOT; i++) {
|
||||
if ((player->Nanos[i]).iID == 0)
|
||||
continue;
|
||||
Nano toAdd = {};
|
||||
sNano* next = &player->Nanos[i];
|
||||
toAdd.playerId = player->iID;
|
||||
toAdd.iID = next->iID;
|
||||
toAdd.iSkillID = next->iSkillID;
|
||||
toAdd.iStamina = next->iStamina;
|
||||
db.insert(toAdd);
|
||||
}
|
||||
db.commit();
|
||||
}
|
||||
|
||||
void Database::updateQuests(Player* player) {
|
||||
// start transaction
|
||||
db.begin_transaction();
|
||||
// remove all
|
||||
db.remove_all<DbQuest>(
|
||||
where(c(&DbQuest::PlayerId) == player->iID)
|
||||
);
|
||||
// insert
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (player->tasks[i] == 0)
|
||||
continue;
|
||||
DbQuest toAdd = {};
|
||||
toAdd.PlayerId = player->iID;
|
||||
toAdd.TaskId = player->tasks[i];
|
||||
toAdd.RemainingNPCCount1 = player->RemainingNPCCount[i][0];
|
||||
toAdd.RemainingNPCCount2 = player->RemainingNPCCount[i][1];
|
||||
toAdd.RemainingNPCCount3 = player->RemainingNPCCount[i][2];
|
||||
db.insert(toAdd);
|
||||
}
|
||||
db.commit();
|
||||
}
|
||||
|
||||
void Database::getInventory(Player* player) {
|
||||
// get items from DB
|
||||
auto items = db.get_all<Inventory>(
|
||||
where(c(&Inventory::playerId) == player->iID)
|
||||
);
|
||||
// set items
|
||||
for (const Inventory ¤t : items) {
|
||||
sItemBase toSet = {};
|
||||
toSet.iID = current.id;
|
||||
toSet.iType = current.Type;
|
||||
toSet.iOpt = current.Opt;
|
||||
toSet.iTimeLimit = current.TimeLimit;
|
||||
// assign to proper arrays
|
||||
if (current.slot < AEQUIP_COUNT)
|
||||
player->Equip[current.slot] = toSet;
|
||||
else if (current.slot < (AEQUIP_COUNT + AINVEN_COUNT))
|
||||
player->Inven[current.slot - AEQUIP_COUNT] = toSet;
|
||||
else if (current.slot < (AEQUIP_COUNT + AINVEN_COUNT + ABANK_COUNT))
|
||||
player->Bank[current.slot - AEQUIP_COUNT - AINVEN_COUNT] = toSet;
|
||||
else
|
||||
player->QInven[current.slot - AEQUIP_COUNT - AINVEN_COUNT - ABANK_COUNT] = toSet;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Database::removeExpiredVehicles(Player* player) {
|
||||
int32_t currentTime = getTimestamp();
|
||||
// remove from bank immediately
|
||||
for (int i = 0; i < ABANK_COUNT; i++) {
|
||||
if (player->Bank[i].iType == 10 && player->Bank[i].iTimeLimit < currentTime)
|
||||
player->Bank[i] = {};
|
||||
}
|
||||
// for the rest, we want to leave only 1 expired vehicle on player to delete it with the client packet
|
||||
std::vector<sItemBase*> toRemove;
|
||||
|
||||
// equiped vehicle
|
||||
if (player->Equip[8].iOpt > 0 && player->Equip[8].iTimeLimit < currentTime) {
|
||||
toRemove.push_back(&player->Equip[8]);
|
||||
player->toRemoveVehicle.eIL = 0;
|
||||
player->toRemoveVehicle.iSlotNum = 8;
|
||||
}
|
||||
// inventory
|
||||
for (int i = 0; i < AINVEN_COUNT; i++) {
|
||||
if (player->Inven[i].iType == 10 && player->Inven[i].iTimeLimit < currentTime) {
|
||||
toRemove.push_back(&player->Inven[i]);
|
||||
player->toRemoveVehicle.eIL = 1;
|
||||
player->toRemoveVehicle.iSlotNum = i;
|
||||
}
|
||||
}
|
||||
|
||||
// delete all but one vehicles, leave last one for ceremonial deletion
|
||||
for (int i = 0; i < (int)toRemove.size()-1; i++) {
|
||||
memset(toRemove[i], 0, sizeof(sItemBase));
|
||||
}
|
||||
}
|
||||
|
||||
void Database::getNanos(Player* player) {
|
||||
// get from DB
|
||||
auto nanos = db.get_all<Nano>(
|
||||
where(c(&Nano::playerId) == player->iID)
|
||||
);
|
||||
// set
|
||||
for (const Nano& current : nanos) {
|
||||
sNano *toSet = &player->Nanos[current.iID];
|
||||
toSet->iID = current.iID;
|
||||
toSet->iSkillID = current.iSkillID;
|
||||
toSet->iStamina = current.iStamina;
|
||||
}
|
||||
}
|
||||
|
||||
void Database::getQuests(Player* player) {
|
||||
// get from DB
|
||||
auto quests = db.get_all<DbQuest>(
|
||||
where(c(&DbQuest::PlayerId) == player->iID)
|
||||
);
|
||||
// set
|
||||
int i = 0;
|
||||
for (const DbQuest& current : quests) {
|
||||
player->tasks[i] = current.TaskId;
|
||||
player->RemainingNPCCount[i][0] = current.RemainingNPCCount1;
|
||||
player->RemainingNPCCount[i][1] = current.RemainingNPCCount2;
|
||||
player->RemainingNPCCount[i][2] = current.RemainingNPCCount3;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion ShardServer
|
||||
133
src/Database.hpp
133
src/Database.hpp
@@ -1,133 +0,0 @@
|
||||
#pragma once
|
||||
#include "CNStructs.hpp"
|
||||
#include "Player.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Database {
|
||||
#pragma region DatabaseStructs
|
||||
|
||||
struct Account {
|
||||
int AccountID;
|
||||
std::string Login;
|
||||
std::string Password;
|
||||
int Selected;
|
||||
uint64_t Created;
|
||||
uint64_t LastLogin;
|
||||
};
|
||||
struct Inventory {
|
||||
int playerId;
|
||||
int slot;
|
||||
int16_t Type;
|
||||
int16_t id;
|
||||
int32_t Opt;
|
||||
int32_t TimeLimit;
|
||||
};
|
||||
struct Nano {
|
||||
int playerId;
|
||||
int16_t iID;
|
||||
int16_t iSkillID;
|
||||
int16_t iStamina;
|
||||
};
|
||||
struct DbPlayer {
|
||||
int PlayerID;
|
||||
int AccountID;
|
||||
short int slot;
|
||||
std::string FirstName;
|
||||
std::string LastName;
|
||||
uint64_t Created;
|
||||
uint64_t LastLogin;
|
||||
short int Level;
|
||||
int Nano1;
|
||||
int Nano2;
|
||||
int Nano3;
|
||||
short int AppearanceFlag;
|
||||
short int Body;
|
||||
short int Class;
|
||||
short int EyeColor;
|
||||
short int FaceStyle;
|
||||
short int Gender;
|
||||
int HP;
|
||||
short int HairColor;
|
||||
short int HairStyle;
|
||||
short int Height;
|
||||
short int NameCheck;
|
||||
short int PayZoneFlag;
|
||||
short int SkinColor;
|
||||
bool TutorialFlag;
|
||||
int AccountLevel;
|
||||
int FusionMatter;
|
||||
int Taros;
|
||||
int x_coordinates;
|
||||
int y_coordinates;
|
||||
int z_coordinates;
|
||||
int angle;
|
||||
short int PCState;
|
||||
int BatteryW;
|
||||
int BatteryN;
|
||||
int16_t Mentor;
|
||||
std::vector<char> QuestFlag;
|
||||
int32_t CurrentMissionID;
|
||||
int32_t WarpLocationFlag;
|
||||
int64_t SkywayLocationFlag1;
|
||||
int64_t SkywayLocationFlag2;
|
||||
};
|
||||
struct DbQuest {
|
||||
int PlayerId;
|
||||
int32_t TaskId;
|
||||
int RemainingNPCCount1;
|
||||
int RemainingNPCCount2;
|
||||
int RemainingNPCCount3;
|
||||
};
|
||||
|
||||
|
||||
#pragma endregion DatabaseStructs
|
||||
|
||||
// handles migrations
|
||||
void open();
|
||||
int getAccountsCount();
|
||||
int getPlayersCount();
|
||||
// returns ID
|
||||
int addAccount(std::string login, std::string password);
|
||||
void updateSelected(int accountId, int playerId);
|
||||
std::unique_ptr<Account> findAccount(std::string login);
|
||||
bool isNameFree(sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck);
|
||||
// called after chosing name, returns ID
|
||||
int createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID);
|
||||
// called after finishing creation
|
||||
void finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character);
|
||||
// called after tutorial
|
||||
void finishTutorial(int PlayerID);
|
||||
// returns slot number
|
||||
int deleteCharacter(int characterID, int userID);
|
||||
std::vector <Player> getCharacters(int userID);
|
||||
// accepting/declining custom name
|
||||
enum class CustomName {
|
||||
APPROVE = 1,
|
||||
DISAPPROVE = 2
|
||||
};
|
||||
void evaluateCustomName(int characterID, CustomName decision);
|
||||
void changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save);
|
||||
|
||||
// parsing DbPlayer
|
||||
DbPlayer playerToDb(Player *player);
|
||||
Player DbToPlayer(DbPlayer player);
|
||||
|
||||
// getting players
|
||||
DbPlayer getDbPlayerById(int id);
|
||||
Player getPlayer(int id);
|
||||
|
||||
void updatePlayer(Player *player);
|
||||
void updateInventory(Player *player);
|
||||
void updateNanos(Player *player);
|
||||
void updateQuests(Player* player);
|
||||
|
||||
void getInventory(Player* player);
|
||||
void removeExpiredVehicles(Player* player);
|
||||
void getNanos(Player* player);
|
||||
void getQuests(Player* player);
|
||||
|
||||
// parsing blobs
|
||||
void appendBlob(std::vector<char>*blob, int64_t input);
|
||||
int64_t blobToInt64(std::vector<char>::iterator it);
|
||||
}
|
||||
220
src/Defines.cpp
220
src/Defines.cpp
@@ -1,220 +0,0 @@
|
||||
#include <string>
|
||||
|
||||
#include "Defines.hpp"
|
||||
|
||||
#define STRINGIFY(x) PacketMap(x, #x)
|
||||
|
||||
/*
|
||||
* Turns out there isn't better way to do this...
|
||||
* We'll only support CL2* packets for now, since we only
|
||||
* need to print those.
|
||||
*/
|
||||
struct PacketMap {
|
||||
int val;
|
||||
std::string name;
|
||||
|
||||
PacketMap(int v, std::string n) : val(v), name(n) {};
|
||||
};
|
||||
|
||||
PacketMap cl2ls_map[] = {
|
||||
STRINGIFY(P_CL2LS_REQ_LOGIN),
|
||||
STRINGIFY(P_CL2LS_REQ_CHECK_CHAR_NAME),
|
||||
STRINGIFY(P_CL2LS_REQ_SAVE_CHAR_NAME),
|
||||
STRINGIFY(P_CL2LS_REQ_CHAR_CREATE),
|
||||
STRINGIFY(P_CL2LS_REQ_CHAR_SELECT),
|
||||
STRINGIFY(P_CL2LS_REQ_CHAR_DELETE),
|
||||
STRINGIFY(P_CL2LS_REQ_SHARD_SELECT),
|
||||
STRINGIFY(P_CL2LS_REQ_SHARD_LIST_INFO),
|
||||
STRINGIFY(P_CL2LS_CHECK_NAME_LIST),
|
||||
STRINGIFY(P_CL2LS_REQ_SAVE_CHAR_TUTOR),
|
||||
STRINGIFY(P_CL2LS_REQ_PC_EXIT_DUPLICATE),
|
||||
STRINGIFY(P_CL2LS_REP_LIVE_CHECK),
|
||||
STRINGIFY(P_CL2LS_REQ_CHANGE_CHAR_NAME),
|
||||
STRINGIFY(P_CL2LS_REQ_SERVER_SELECT),
|
||||
};
|
||||
|
||||
PacketMap cl2fe_map[] = {
|
||||
STRINGIFY(P_CL2FE_REQ_PC_ENTER),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_EXIT),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_MOVE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_STOP),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_JUMP),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_ATTACK_NPCs),
|
||||
STRINGIFY(P_CL2FE_REQ_SEND_FREECHAT_MESSAGE),
|
||||
STRINGIFY(P_CL2FE_REQ_SEND_MENUCHAT_MESSAGE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_REGEN),
|
||||
STRINGIFY(P_CL2FE_REQ_ITEM_MOVE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TASK_START),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TASK_END),
|
||||
STRINGIFY(P_CL2FE_REQ_NANO_EQUIP),
|
||||
STRINGIFY(P_CL2FE_REQ_NANO_UNEQUIP),
|
||||
STRINGIFY(P_CL2FE_REQ_NANO_ACTIVE),
|
||||
STRINGIFY(P_CL2FE_REQ_NANO_TUNE),
|
||||
STRINGIFY(P_CL2FE_REQ_NANO_SKILL_USE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TASK_STOP),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TASK_CONTINUE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_GOTO),
|
||||
STRINGIFY(P_CL2FE_REQ_CHARGE_NANO_STAMINA),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_KILL_QUEST_NPCs),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_VENDOR_ITEM_BUY),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_VENDOR_ITEM_SELL),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_ITEM_DELETE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_GIVE_ITEM),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_ROCKET_STYLE_READY),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_ROCKET_STYLE_FIRE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_ROCKET_STYLE_HIT),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_GRENADE_STYLE_READY),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_GRENADE_STYLE_FIRE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_GRENADE_STYLE_HIT),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_NANO_CREATE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TRADE_OFFER),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TRADE_OFFER_CANCEL),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TRADE_OFFER_ACCEPT),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TRADE_OFFER_REFUSAL),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TRADE_OFFER_ABORT),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TRADE_CONFIRM),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TRADE_CONFIRM_CANCEL),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TRADE_CONFIRM_ABORT),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TRADE_ITEM_REGISTER),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TRADE_ITEM_UNREGISTER),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TRADE_CASH_REGISTER),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TRADE_EMOTES_CHAT),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_BANK_OPEN),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_BANK_CLOSE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_VENDOR_START),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_COMBAT_BEGIN),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_COMBAT_END),
|
||||
STRINGIFY(P_CL2FE_REQ_REQUEST_MAKE_BUDDY),
|
||||
STRINGIFY(P_CL2FE_REQ_ACCEPT_MAKE_BUDDY),
|
||||
STRINGIFY(P_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE),
|
||||
STRINGIFY(P_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE),
|
||||
STRINGIFY(P_CL2FE_REQ_GET_BUDDY_STYLE),
|
||||
STRINGIFY(P_CL2FE_REQ_SET_BUDDY_BLOCK),
|
||||
STRINGIFY(P_CL2FE_REQ_REMOVE_BUDDY),
|
||||
STRINGIFY(P_CL2FE_REQ_GET_BUDDY_STATE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_JUMPPAD),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_LAUNCHER),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_ZIPLINE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_MOVEPLATFORM),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_SLOPE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_STATE_CHANGE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_MAP_WARP),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_GIVE_NANO),
|
||||
STRINGIFY(P_CL2FE_REQ_NPC_SUMMON),
|
||||
STRINGIFY(P_CL2FE_REQ_NPC_UNSUMMON),
|
||||
STRINGIFY(P_CL2FE_REQ_ITEM_CHEST_OPEN),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_GIVE_NANO_SKILL),
|
||||
STRINGIFY(P_CL2FE_DOT_DAMAGE_ONOFF),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_VENDOR_BATTERY_BUY),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_WARP_USE_NPC),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_GROUP_INVITE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_GROUP_INVITE_REFUSE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_GROUP_JOIN),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_GROUP_LEAVE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_BUDDY_WARP),
|
||||
STRINGIFY(P_CL2FE_REQ_GET_MEMBER_STYLE),
|
||||
STRINGIFY(P_CL2FE_REQ_GET_GROUP_STYLE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_CHANGE_MENTOR),
|
||||
STRINGIFY(P_CL2FE_REQ_GET_BUDDY_LOCATION),
|
||||
STRINGIFY(P_CL2FE_REQ_NPC_GROUP_SUMMON),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_WARP_TO_PC),
|
||||
STRINGIFY(P_CL2FE_REQ_EP_RANK_GET_LIST),
|
||||
STRINGIFY(P_CL2FE_REQ_EP_RANK_GET_DETAIL),
|
||||
STRINGIFY(P_CL2FE_REQ_EP_RANK_GET_PC_INFO),
|
||||
STRINGIFY(P_CL2FE_REQ_EP_RACE_START),
|
||||
STRINGIFY(P_CL2FE_REQ_EP_RACE_END),
|
||||
STRINGIFY(P_CL2FE_REQ_EP_RACE_CANCEL),
|
||||
STRINGIFY(P_CL2FE_REQ_EP_GET_RING),
|
||||
STRINGIFY(P_CL2FE_REQ_IM_CHANGE_SWITCH_STATUS),
|
||||
STRINGIFY(P_CL2FE_REQ_SHINY_PICKUP),
|
||||
STRINGIFY(P_CL2FE_REQ_SHINY_SUMMON),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_MOVETRANSPORTATION),
|
||||
STRINGIFY(P_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE),
|
||||
STRINGIFY(P_CL2FE_REQ_SEND_ANY_GROUP_FREECHAT_MESSAGE),
|
||||
STRINGIFY(P_CL2FE_REQ_BARKER),
|
||||
STRINGIFY(P_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE),
|
||||
STRINGIFY(P_CL2FE_REQ_SEND_ANY_GROUP_MENUCHAT_MESSAGE),
|
||||
STRINGIFY(P_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION),
|
||||
STRINGIFY(P_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH),
|
||||
STRINGIFY(P_CL2FE_GM_REQ_PC_SET_VALUE),
|
||||
STRINGIFY(P_CL2FE_GM_REQ_KICK_PLAYER),
|
||||
STRINGIFY(P_CL2FE_GM_REQ_TARGET_PC_TELEPORT),
|
||||
STRINGIFY(P_CL2FE_GM_REQ_PC_LOCATION),
|
||||
STRINGIFY(P_CL2FE_GM_REQ_PC_ANNOUNCE),
|
||||
STRINGIFY(P_CL2FE_REQ_SET_PC_BLOCK),
|
||||
STRINGIFY(P_CL2FE_REQ_REGIST_RXCOM),
|
||||
STRINGIFY(P_CL2FE_GM_REQ_PC_MOTD_REGISTER),
|
||||
STRINGIFY(P_CL2FE_REQ_ITEM_USE),
|
||||
STRINGIFY(P_CL2FE_REQ_WARP_USE_RECALL),
|
||||
STRINGIFY(P_CL2FE_REP_LIVE_CHECK),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_MISSION_COMPLETE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TASK_COMPLETE),
|
||||
STRINGIFY(P_CL2FE_REQ_NPC_INTERACTION),
|
||||
STRINGIFY(P_CL2FE_DOT_HEAL_ONOFF),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_SPECIAL_STATE_SWITCH),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_EMAIL_UPDATE_CHECK),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_READ_EMAIL),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_RECV_EMAIL_PAGE_LIST),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_DELETE_EMAIL),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_SEND_EMAIL),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_RECV_EMAIL_ITEM),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_RECV_EMAIL_CANDY),
|
||||
STRINGIFY(P_CL2FE_GM_REQ_TARGET_PC_SPECIAL_STATE_ONOFF),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID),
|
||||
STRINGIFY(P_CL2FE_REQ_NPC_GROUP_INVITE),
|
||||
STRINGIFY(P_CL2FE_REQ_NPC_GROUP_KICK),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_FIRST_USE_FLAG_SET),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TRANSPORT_WARP),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_TIME_TO_GO_WARP),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_RECV_EMAIL_ITEM_ALL),
|
||||
STRINGIFY(P_CL2FE_REQ_CHANNEL_INFO),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_CHANNEL_NUM),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_WARP_CHANNEL),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_LOADING_COMPLETE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_ATTACK_CHARs),
|
||||
STRINGIFY(P_CL2FE_PC_STREETSTALL_REQ_READY),
|
||||
STRINGIFY(P_CL2FE_PC_STREETSTALL_REQ_CANCEL),
|
||||
STRINGIFY(P_CL2FE_PC_STREETSTALL_REQ_REGIST_ITEM),
|
||||
STRINGIFY(P_CL2FE_PC_STREETSTALL_REQ_UNREGIST_ITEM),
|
||||
STRINGIFY(P_CL2FE_PC_STREETSTALL_REQ_SALE_START),
|
||||
STRINGIFY(P_CL2FE_PC_STREETSTALL_REQ_ITEM_LIST),
|
||||
STRINGIFY(P_CL2FE_PC_STREETSTALL_REQ_ITEM_BUY),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_ITEM_COMBINATION),
|
||||
STRINGIFY(P_CL2FE_GM_REQ_SET_PC_SKILL),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_SKILL_ADD),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_SKILL_DEL),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_SKILL_USE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_ROPE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_BELT),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_VEHICLE_ON),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_VEHICLE_OFF),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_REGIST_QUICK_SLOT),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_DISASSEMBLE_ITEM),
|
||||
STRINGIFY(P_CL2FE_GM_REQ_REWARD_RATE),
|
||||
STRINGIFY(P_CL2FE_REQ_PC_ITEM_ENCHANT),
|
||||
};
|
||||
|
||||
std::string Defines::p2str(int type, int val) {
|
||||
switch (type) {
|
||||
case CL2LS:
|
||||
val = val - CL2LS - 1;
|
||||
if (val > N_CL2LS || val < 0)
|
||||
break;
|
||||
|
||||
return cl2ls_map[val].name;
|
||||
case CL2FE:
|
||||
val = val - CL2FE - 1;
|
||||
if (val > N_CL2FE || val < 0)
|
||||
break;
|
||||
|
||||
return cl2fe_map[val].name;
|
||||
}
|
||||
|
||||
return "UNKNOWN";
|
||||
}
|
||||
246
src/Eggs.cpp
Normal file
246
src/Eggs.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
#include "Eggs.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "Entities.hpp"
|
||||
#include "Items.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
using namespace Eggs;
|
||||
|
||||
std::unordered_map<int, EggType> Eggs::EggTypes;
|
||||
|
||||
void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// eggId might be 0 if the buff is made by the /buff command
|
||||
EntityRef src = eggId == 0 ? sock : EntityRef(eggId);
|
||||
|
||||
if(Abilities::SkillTable.count(skillId) == 0) {
|
||||
std::cout << "[WARN] egg " << eggId << " has skill ID " << skillId << " which doesn't exist" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
SkillResult result = SkillResult();
|
||||
SkillData* skill = &Abilities::SkillTable[skillId];
|
||||
if(skill->drainType == SkillDrainType::PASSIVE) {
|
||||
// apply buff
|
||||
if(skill->targetType != SkillTargetType::PLAYERS) {
|
||||
std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl;
|
||||
}
|
||||
|
||||
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||
int value = skill->values[0][0];
|
||||
BuffStack eggBuff = {
|
||||
duration * 1000 / MS_PER_PLAYER_TICK,
|
||||
value,
|
||||
src,
|
||||
BuffClass::EGG
|
||||
};
|
||||
plr->addBuff(timeBuffId,
|
||||
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||
if(status == ETBU_DEL) Buffs::timeBuffTimeout(self);
|
||||
},
|
||||
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||
// no-op
|
||||
},
|
||||
&eggBuff);
|
||||
|
||||
sSkillResult_Buff resultBuff{};
|
||||
resultBuff.eCT = plr->getCharType();
|
||||
resultBuff.iID = plr->getID();
|
||||
resultBuff.bProtected = false;
|
||||
resultBuff.iConditionBitFlag = plr->getCompositeCondition();
|
||||
result = SkillResult(sizeof(sSkillResult_Buff), &resultBuff);
|
||||
} else {
|
||||
int value = plr->getMaxHP() * skill->values[0][0] / 1000;
|
||||
sSkillResult_Damage resultDamage{};
|
||||
sSkillResult_Heal_HP resultHeal{};
|
||||
switch(skill->skillType)
|
||||
{
|
||||
case SkillType::DAMAGE:
|
||||
resultDamage.bProtected = false;
|
||||
resultDamage.eCT = plr->getCharType();
|
||||
resultDamage.iID = plr->getID();
|
||||
resultDamage.iDamage = plr->takeDamage(src, value);
|
||||
resultDamage.iHP = plr->getCurrentHP();
|
||||
result = SkillResult(sizeof(sSkillResult_Damage), &resultDamage);
|
||||
break;
|
||||
case SkillType::HEAL_HP:
|
||||
resultHeal.eCT = plr->getCharType();
|
||||
resultHeal.iID = plr->getID();
|
||||
resultHeal.iHealHP = plr->heal(src, value);
|
||||
resultHeal.iHP = plr->getCurrentHP();
|
||||
result = SkillResult(sizeof(sSkillResult_Heal_HP), &resultHeal);
|
||||
break;
|
||||
default:
|
||||
std::cout << "[WARN] oops, egg with active skill type " << (int)skill->skillType << " unhandled";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// initialize response struct
|
||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + result.size;
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
||||
pkt->iNPC_ID = eggId;
|
||||
pkt->iSkillID = skillId;
|
||||
pkt->eST = (int32_t)skill->skillType;
|
||||
pkt->iTargetCnt = 1;
|
||||
|
||||
if(result.size > 0) {
|
||||
void* attached = (void*)(pkt + 1);
|
||||
memcpy(attached, result.payload, result.size);
|
||||
}
|
||||
|
||||
NPCManager::sendToViewable(src.getEntity(), pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
|
||||
}
|
||||
|
||||
static void eggStep(CNServer* serv, time_t currTime) {
|
||||
// check dead eggs and eggs in inactive chunks
|
||||
for (auto npc : NPCManager::NPCs) {
|
||||
if (npc.second->kind != EntityKind::EGG)
|
||||
continue;
|
||||
|
||||
auto egg = (Egg*)npc.second;
|
||||
if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks))
|
||||
continue;
|
||||
|
||||
if (egg->deadUntil <= currTime) {
|
||||
// respawn it
|
||||
egg->dead = false;
|
||||
egg->deadUntil = 0;
|
||||
egg->hp = 400;
|
||||
|
||||
Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Eggs::npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg) {
|
||||
egg->iX = x;
|
||||
egg->iY = y;
|
||||
egg->iZ = z;
|
||||
// client doesn't care about egg->iMapNum
|
||||
egg->iShinyType = npc->iNPCType;
|
||||
egg->iShiny_ID = npc->iNPC_ID;
|
||||
}
|
||||
|
||||
static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
||||
auto pickup = (sP_CL2FE_REQ_SHINY_PICKUP*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
EntityRef eggRef = {pickup->iShinyID};
|
||||
|
||||
if (!eggRef.isValid()) {
|
||||
std::cout << "[WARN] Player tried to open non existing egg?!" << std::endl;
|
||||
return;
|
||||
}
|
||||
auto egg = (Egg*)eggRef.getEntity();
|
||||
if (egg->kind != EntityKind::EGG) {
|
||||
std::cout << "[WARN] Player tried to open something other than an?!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (egg->dead) {
|
||||
std::cout << "[WARN] Player tried to open a dead egg?!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
/* this has some issues with position desync, leaving it out for now
|
||||
if (abs(egg->x - plr->x)>500 || abs(egg->y - plr->y) > 500) {
|
||||
std::cout << "[WARN] Player tried to open an egg isn't nearby?!" << std::endl;
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
int typeId = egg->type;
|
||||
if (EggTypes.find(typeId) == EggTypes.end()) {
|
||||
std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
EggType* type = &EggTypes[typeId];
|
||||
|
||||
/*
|
||||
* SHINY_PICKUP_SUCC is only causing a GUI effect in the client
|
||||
* (buff icon pops up in the bottom of the screen)
|
||||
* so we don't send it for non-effect
|
||||
*/
|
||||
if (type->effectId != 0) {
|
||||
eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration);
|
||||
INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp);
|
||||
resp.iSkillID = type->effectId;
|
||||
|
||||
// in general client finds correct icon on it's own,
|
||||
// but for damage we have to supply correct CSTB
|
||||
if (resp.iSkillID == 183)
|
||||
resp.eCSTB = ECSB_INFECTION;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_SHINY_PICKUP_SUCC);
|
||||
}
|
||||
|
||||
// drop
|
||||
if (type->dropCrateId != 0) {
|
||||
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||
// we know it's only one trailing struct, so we can skip full validation
|
||||
|
||||
uint8_t respbuf[resplen]; // not a variable length array, don't worry
|
||||
sP_FE2CL_REP_REWARD_ITEM* reward = (sP_FE2CL_REP_REWARD_ITEM*)respbuf;
|
||||
sItemReward* item = (sItemReward*)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
|
||||
|
||||
// don't forget to zero the buffer!
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
// send back player's stats
|
||||
reward->m_iCandy = plr->money;
|
||||
reward->m_iFusionMatter = plr->fusionmatter;
|
||||
reward->m_iBatteryN = plr->batteryN;
|
||||
reward->m_iBatteryW = plr->batteryW;
|
||||
reward->iFatigue = 100; // prevents warning message
|
||||
reward->iFatigue_Level = 1;
|
||||
reward->iItemCnt = 1; // remember to update resplen if you change this
|
||||
|
||||
int slot = Items::findFreeSlot(plr);
|
||||
|
||||
// no space for drop
|
||||
if (slot != -1) {
|
||||
|
||||
// item reward
|
||||
item->sItem.iType = 9;
|
||||
item->sItem.iOpt = 1;
|
||||
item->sItem.iID = type->dropCrateId;
|
||||
item->iSlotNum = slot;
|
||||
item->eIL = 1; // Inventory Location. 1 means player inventory.
|
||||
|
||||
// update player
|
||||
plr->Inven[slot] = item->sItem;
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
||||
}
|
||||
}
|
||||
|
||||
if (egg->summoned)
|
||||
NPCManager::destroyNPC(eggRef.id);
|
||||
else {
|
||||
Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef);
|
||||
egg->dead = true;
|
||||
egg->deadUntil = getTime() + (time_t)type->regen * 1000;
|
||||
egg->hp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Eggs::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SHINY_PICKUP, eggPickup);
|
||||
|
||||
REGISTER_SHARD_TIMER(eggStep, 1000);
|
||||
}
|
||||
19
src/Eggs.hpp
Normal file
19
src/Eggs.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
struct EggType {
|
||||
int dropCrateId;
|
||||
int effectId;
|
||||
int duration;
|
||||
int regen;
|
||||
};
|
||||
|
||||
namespace Eggs {
|
||||
extern std::unordered_map<int, EggType> EggTypes;
|
||||
|
||||
void init();
|
||||
|
||||
void eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
|
||||
void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg);
|
||||
}
|
||||
339
src/Email.cpp
Normal file
339
src/Email.cpp
Normal file
@@ -0,0 +1,339 @@
|
||||
#include "Email.hpp"
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "db/Database.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Chat.hpp"
|
||||
|
||||
using namespace Email;
|
||||
|
||||
std::vector<std::string> Email::dump;
|
||||
|
||||
// New email notification
|
||||
static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_NEW_EMAIL, resp);
|
||||
resp.iNewEmailCnt = Database::getUnreadEmailCount(PlayerManager::getPlayer(sock)->iID);
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_NEW_EMAIL);
|
||||
}
|
||||
|
||||
// Retrieve page of emails
|
||||
static void emailReceivePageList(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_PAGE_LIST*)data->buf;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_RECV_EMAIL_PAGE_LIST_SUCC, resp);
|
||||
resp.iPageNum = pkt->iPageNum;
|
||||
|
||||
std::vector<Database::EmailData> emails = Database::getEmails(PlayerManager::getPlayer(sock)->iID, pkt->iPageNum);
|
||||
for (int i = 0; i < emails.size(); i++) {
|
||||
// convert each email and load them into the packet
|
||||
Database::EmailData* email = &emails.at(i);
|
||||
sEmailInfo* emailInfo = new sEmailInfo();
|
||||
emailInfo->iEmailIndex = email->MsgIndex;
|
||||
emailInfo->iReadFlag = email->ReadFlag;
|
||||
emailInfo->iItemCandyFlag = email->ItemFlag;
|
||||
emailInfo->iFromPCUID = email->SenderId;
|
||||
emailInfo->SendTime = timeStampToStruct(email->SendTime);
|
||||
emailInfo->DeleteTime = timeStampToStruct(email->DeleteTime);
|
||||
U8toU16(email->SenderFirstName, emailInfo->szFirstName, sizeof(emailInfo->szFirstName));
|
||||
U8toU16(email->SenderLastName, emailInfo->szLastName, sizeof(emailInfo->szLastName));
|
||||
U8toU16(email->SubjectLine, emailInfo->szSubject, sizeof(emailInfo->szSubject));
|
||||
resp.aEmailInfo[i] = *emailInfo;
|
||||
}
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_RECV_EMAIL_PAGE_LIST_SUCC);
|
||||
}
|
||||
|
||||
// Read individual email
|
||||
static void emailRead(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_READ_EMAIL*)data->buf;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
Database::EmailData email = Database::getEmail(plr->iID, pkt->iEmailIndex);
|
||||
sItemBase* attachments = Database::getEmailAttachments(plr->iID, pkt->iEmailIndex);
|
||||
email.ReadFlag = 1; // mark as read
|
||||
Database::updateEmailContent(&email);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_READ_EMAIL_SUCC, resp);
|
||||
resp.iEmailIndex = pkt->iEmailIndex;
|
||||
resp.iCash = email.Taros;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
resp.aItem[i] = attachments[i];
|
||||
}
|
||||
U8toU16(email.MsgBody, (char16_t*)resp.szContent, sizeof(resp.szContent));
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_READ_EMAIL_SUCC);
|
||||
}
|
||||
|
||||
// Retrieve attached taros from email
|
||||
static void emailReceiveTaros(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_CANDY*)data->buf;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
Database::EmailData email = Database::getEmail(plr->iID, pkt->iEmailIndex);
|
||||
// money transfer
|
||||
plr->money += email.Taros;
|
||||
email.Taros = 0;
|
||||
// update Taros in email
|
||||
Database::updateEmailContent(&email);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_RECV_EMAIL_CANDY_SUCC, resp);
|
||||
resp.iCandy = plr->money;
|
||||
resp.iEmailIndex = pkt->iEmailIndex;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_RECV_EMAIL_CANDY_SUCC);
|
||||
}
|
||||
|
||||
// Retrieve individual attached item from email
|
||||
static void emailReceiveItemSingle(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_ITEM*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iEmailItemSlot < 1 || pkt->iEmailItemSlot > 4)
|
||||
return; // sanity check
|
||||
|
||||
// get email item from db and delete it
|
||||
sItemBase* attachments = Database::getEmailAttachments(plr->iID, pkt->iEmailIndex);
|
||||
sItemBase itemFrom = attachments[pkt->iEmailItemSlot - 1];
|
||||
Database::deleteEmailAttachments(plr->iID, pkt->iEmailIndex, pkt->iEmailItemSlot);
|
||||
|
||||
// move item to player inventory
|
||||
sItemBase& itemTo = plr->Inven[pkt->iSlotNum];
|
||||
itemTo.iID = itemFrom.iID;
|
||||
itemTo.iOpt = itemFrom.iOpt;
|
||||
itemTo.iTimeLimit = itemFrom.iTimeLimit;
|
||||
itemTo.iType = itemFrom.iType;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_RECV_EMAIL_ITEM_SUCC, resp);
|
||||
resp.iEmailIndex = pkt->iEmailIndex;
|
||||
resp.iEmailItemSlot = pkt->iEmailItemSlot;
|
||||
resp.iSlotNum = pkt->iSlotNum;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_RECV_EMAIL_ITEM_SUCC);
|
||||
|
||||
// update inventory
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_GIVE_ITEM_SUCC, resp2);
|
||||
resp2.eIL = 1;
|
||||
resp2.iSlotNum = resp.iSlotNum;
|
||||
resp2.Item = itemTo;
|
||||
|
||||
sock->sendPacket(resp2, P_FE2CL_REP_PC_GIVE_ITEM_SUCC);
|
||||
}
|
||||
|
||||
// Retrieve all attached items from email
|
||||
static void emailReceiveItemAll(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_ITEM_ALL*)data->buf;
|
||||
|
||||
// move items to player inventory
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
sItemBase* itemsFrom = Database::getEmailAttachments(plr->iID, pkt->iEmailIndex);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int slot = Items::findFreeSlot(plr);
|
||||
if (slot < 0 || slot >= AINVEN_COUNT) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_RECV_EMAIL_ITEM_ALL_FAIL, failResp);
|
||||
failResp.iEmailIndex = pkt->iEmailIndex;
|
||||
failResp.iErrorCode = 0; // ???
|
||||
break; // sanity check; should never happen
|
||||
}
|
||||
|
||||
// copy data over
|
||||
sItemBase itemFrom = itemsFrom[i];
|
||||
sItemBase& itemTo = plr->Inven[slot];
|
||||
itemTo.iID = itemFrom.iID;
|
||||
itemTo.iOpt = itemFrom.iOpt;
|
||||
itemTo.iTimeLimit = itemFrom.iTimeLimit;
|
||||
itemTo.iType = itemFrom.iType;
|
||||
|
||||
// update inventory
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_GIVE_ITEM_SUCC, resp2);
|
||||
resp2.eIL = 1;
|
||||
resp2.iSlotNum = slot;
|
||||
resp2.Item = itemTo;
|
||||
|
||||
sock->sendPacket(resp2, P_FE2CL_REP_PC_GIVE_ITEM_SUCC);
|
||||
}
|
||||
|
||||
// delete all items from db
|
||||
Database::deleteEmailAttachments(plr->iID, pkt->iEmailIndex, -1);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_RECV_EMAIL_ITEM_ALL_SUCC, resp);
|
||||
resp.iEmailIndex = pkt->iEmailIndex;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_RECV_EMAIL_ITEM_ALL_SUCC);
|
||||
}
|
||||
|
||||
// Delete an email
|
||||
static void emailDelete(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_DELETE_EMAIL*)data->buf;
|
||||
|
||||
Database::deleteEmails(PlayerManager::getPlayer(sock)->iID, pkt->iEmailIndexArray);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_DELETE_EMAIL_SUCC, resp);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
resp.iEmailIndexArray[i] = pkt->iEmailIndexArray[i]; // i'm scared of memcpy
|
||||
}
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_DELETE_EMAIL_SUCC);
|
||||
}
|
||||
|
||||
// Send an email
|
||||
static void emailSend(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_SEND_EMAIL*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// sanity checks
|
||||
bool invalid = false;
|
||||
int itemCount = 0;
|
||||
|
||||
std::set<int> seen;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int slot = pkt->aItem[i].iSlotNum;
|
||||
if (slot < 0 || slot >= AINVEN_COUNT) {
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
sItemBase* item = &pkt->aItem[i].ItemInven;
|
||||
sItemBase* real = &plr->Inven[slot];
|
||||
|
||||
if (item->iID == 0)
|
||||
continue;
|
||||
|
||||
// was the same item added multiple times?
|
||||
if (seen.count(slot) > 0) {
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
seen.insert(slot);
|
||||
|
||||
itemCount++;
|
||||
if (item->iType != real->iType || item->iID != real->iID
|
||||
|| item->iOpt <= 0 || item->iOpt > real->iOpt) {
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pkt->iCash < 0 || pkt->iCash > plr->money + 50 + 20 * itemCount || invalid) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_SEND_EMAIL_FAIL, errResp);
|
||||
errResp.iErrorCode = 1;
|
||||
errResp.iTo_PCUID = pkt->iTo_PCUID;
|
||||
sock->sendPacket(errResp, P_FE2CL_REP_PC_SEND_EMAIL_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_SEND_EMAIL_SUCC, resp);
|
||||
|
||||
Player otherPlr = {};
|
||||
Database::getPlayer(&otherPlr, pkt->iTo_PCUID);
|
||||
if (pkt->iCash || pkt->aItem[0].ItemInven.iID) {
|
||||
// if there are item or taro attachments
|
||||
if (otherPlr.iID != 0 && plr->PCStyle2.iPayzoneFlag != otherPlr.PCStyle2.iPayzoneFlag) {
|
||||
// if the players are not in the same time period
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_SEND_EMAIL_FAIL, resp);
|
||||
resp.iErrorCode = 9; // error code 9 tells the player they can't send attachments across time
|
||||
resp.iTo_PCUID = pkt->iTo_PCUID;
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_SEND_EMAIL_FAIL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// handle items
|
||||
std::vector<sItemBase> attachments;
|
||||
std::vector<int> attSlots;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
sEmailItemInfoFromCL attachment = pkt->aItem[i];
|
||||
|
||||
// skip empty slots
|
||||
if (attachment.ItemInven.iID == 0)
|
||||
continue;
|
||||
|
||||
sItemBase* item = &pkt->aItem[i].ItemInven;
|
||||
sItemBase* real = &plr->Inven[attachment.iSlotNum];
|
||||
|
||||
resp.aItem[i] = attachment;
|
||||
attachments.push_back(attachment.ItemInven);
|
||||
attSlots.push_back(attachment.iSlotNum);
|
||||
if (real->iOpt <= item->iOpt) // delete item (if they attached the whole stack)
|
||||
*real = { 0, 0, 0, 0 };
|
||||
else // otherwise, decrement the item
|
||||
real->iOpt -= item->iOpt;
|
||||
|
||||
// HACK: update the slot
|
||||
INITSTRUCT(sP_FE2CL_PC_ITEM_MOVE_SUCC, itemResp);
|
||||
itemResp.iFromSlotNum = attachment.iSlotNum;
|
||||
itemResp.iToSlotNum = attachment.iSlotNum;
|
||||
itemResp.FromSlotItem = *real;
|
||||
itemResp.ToSlotItem = *real;
|
||||
itemResp.eFrom = (int32_t)Items::SlotType::INVENTORY;
|
||||
itemResp.eTo = (int32_t)Items::SlotType::INVENTORY;
|
||||
sock->sendPacket(itemResp, P_FE2CL_PC_ITEM_MOVE_SUCC);
|
||||
}
|
||||
|
||||
int cost = pkt->iCash + 50 + 20 * attachments.size(); // attached taros + postage
|
||||
plr->money -= cost;
|
||||
Database::EmailData email = {
|
||||
(int)pkt->iTo_PCUID, // PlayerId
|
||||
Database::getNextEmailIndex(pkt->iTo_PCUID), // MsgIndex
|
||||
0, // ReadFlag (unread)
|
||||
(pkt->iCash > 0 || attachments.size() > 0) ? 1 : 0, // ItemFlag
|
||||
plr->iID, // SenderID
|
||||
AUTOU16TOU8(plr->PCStyle.szFirstName), // SenderFirstName
|
||||
AUTOU16TOU8(plr->PCStyle.szLastName), // SenderLastName
|
||||
Chat::sanitizeText(AUTOU16TOU8(pkt->szSubject)), // SubjectLine
|
||||
Chat::sanitizeText(AUTOU16TOU8(pkt->szContent), true), // MsgBody
|
||||
pkt->iCash, // Taros
|
||||
(uint64_t)getTimestamp(), // SendTime
|
||||
0 // DeleteTime (unimplemented)
|
||||
};
|
||||
|
||||
if (!Database::sendEmail(&email, attachments, plr)) {
|
||||
plr->money += cost; // give money back
|
||||
// give items back
|
||||
while (!attachments.empty()) {
|
||||
sItemBase attachment = attachments.back();
|
||||
plr->Inven[attSlots.back()] = attachment;
|
||||
|
||||
attachments.pop_back();
|
||||
attSlots.pop_back();
|
||||
}
|
||||
|
||||
// send error message
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_SEND_EMAIL_FAIL, errResp);
|
||||
errResp.iErrorCode = 1;
|
||||
errResp.iTo_PCUID = pkt->iTo_PCUID;
|
||||
sock->sendPacket(errResp, P_FE2CL_REP_PC_SEND_EMAIL_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
// HACK: use set value packet to force GUI taros update
|
||||
INITSTRUCT(sP_FE2CL_GM_REP_PC_SET_VALUE, tarosResp);
|
||||
tarosResp.iPC_ID = plr->iID;
|
||||
tarosResp.iSetValueType = 5;
|
||||
tarosResp.iSetValue = plr->money;
|
||||
sock->sendPacket(tarosResp, P_FE2CL_GM_REP_PC_SET_VALUE);
|
||||
|
||||
resp.iCandy = plr->money;
|
||||
resp.iTo_PCUID = pkt->iTo_PCUID;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_SEND_EMAIL_SUCC);
|
||||
|
||||
std::string logEmail = "[Email] " + PlayerManager::getPlayerName(plr, true) + " (to " + PlayerManager::getPlayerName(&otherPlr, true) + "): <" + email.SubjectLine + ">\n" + email.MsgBody;
|
||||
std::cout << logEmail << std::endl;
|
||||
dump.push_back(logEmail);
|
||||
}
|
||||
|
||||
void Email::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_EMAIL_UPDATE_CHECK, emailUpdateCheck);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_RECV_EMAIL_PAGE_LIST, emailReceivePageList);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_READ_EMAIL, emailRead);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_RECV_EMAIL_CANDY, emailReceiveTaros);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_RECV_EMAIL_ITEM, emailReceiveItemSingle);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_RECV_EMAIL_ITEM_ALL, emailReceiveItemAll);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_DELETE_EMAIL, emailDelete);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SEND_EMAIL, emailSend);
|
||||
}
|
||||
10
src/Email.hpp
Normal file
10
src/Email.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace Email {
|
||||
extern std::vector<std::string> dump;
|
||||
|
||||
void init();
|
||||
}
|
||||
146
src/Entities.cpp
Normal file
146
src/Entities.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
#include "Entities.hpp"
|
||||
|
||||
#include "NPCManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
static_assert(std::is_standard_layout<EntityRef>::value);
|
||||
static_assert(std::is_trivially_copyable<EntityRef>::value);
|
||||
|
||||
EntityRef::EntityRef(CNSocket *s) {
|
||||
kind = EntityKind::PLAYER;
|
||||
sock = s;
|
||||
}
|
||||
|
||||
EntityRef::EntityRef(int32_t i) {
|
||||
id = i;
|
||||
|
||||
assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end());
|
||||
kind = NPCManager::NPCs[id]->kind;
|
||||
}
|
||||
|
||||
bool EntityRef::isValid() const {
|
||||
if (kind == EntityKind::PLAYER)
|
||||
return PlayerManager::players.find(sock) != PlayerManager::players.end();
|
||||
|
||||
return NPCManager::NPCs.find(id) != NPCManager::NPCs.end();
|
||||
}
|
||||
|
||||
Entity *EntityRef::getEntity() const {
|
||||
assert(isValid());
|
||||
|
||||
if (kind == EntityKind::PLAYER)
|
||||
return PlayerManager::getPlayer(sock);
|
||||
|
||||
return NPCManager::NPCs[id];
|
||||
}
|
||||
|
||||
sNPCAppearanceData BaseNPC::getAppearanceData() {
|
||||
sNPCAppearanceData data = {};
|
||||
data.iAngle = angle;
|
||||
data.iBarkerType = 0; // unused?
|
||||
data.iConditionBitFlag = 0;
|
||||
data.iHP = hp;
|
||||
data.iNPCType = type;
|
||||
data.iNPC_ID = id;
|
||||
data.iX = x;
|
||||
data.iY = y;
|
||||
data.iZ = z;
|
||||
return data;
|
||||
}
|
||||
|
||||
sNPCAppearanceData CombatNPC::getAppearanceData() {
|
||||
sNPCAppearanceData data = BaseNPC::getAppearanceData();
|
||||
data.iConditionBitFlag = getCompositeCondition();
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
* Entity coming into view.
|
||||
*/
|
||||
void BaseNPC::enterIntoViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
|
||||
pkt.NPCAppearanceData = getAppearanceData();
|
||||
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
|
||||
}
|
||||
|
||||
void Bus::enterIntoViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, pkt);
|
||||
|
||||
// TODO: Potentially decouple this from BaseNPC?
|
||||
pkt.AppearanceData = {
|
||||
3, id, type,
|
||||
x, y, z
|
||||
};
|
||||
|
||||
sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_ENTER);
|
||||
}
|
||||
|
||||
void Egg::enterIntoViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt);
|
||||
|
||||
// TODO: Potentially decouple this from BaseNPC?
|
||||
pkt.ShinyAppearanceData = {
|
||||
id, type, 0, // client doesn't care about map num
|
||||
x, y, z
|
||||
};
|
||||
|
||||
sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER);
|
||||
}
|
||||
|
||||
sNano* Player::getActiveNano() {
|
||||
return &Nanos[activeNano];
|
||||
}
|
||||
|
||||
sPCAppearanceData Player::getAppearanceData() {
|
||||
sPCAppearanceData data = {};
|
||||
data.iID = iID;
|
||||
data.iHP = HP;
|
||||
data.iLv = level;
|
||||
data.iX = x;
|
||||
data.iY = y;
|
||||
data.iZ = z;
|
||||
data.iAngle = angle;
|
||||
data.PCStyle = PCStyle;
|
||||
data.Nano = Nanos[activeNano];
|
||||
data.iPCState = iPCState;
|
||||
data.iSpecialState = iSpecialState;
|
||||
memcpy(data.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT);
|
||||
return data;
|
||||
}
|
||||
|
||||
// TODO: this is less effiecient than it was, because of memset()
|
||||
void Player::enterIntoViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_PC_NEW, pkt);
|
||||
pkt.PCAppearanceData = getAppearanceData();
|
||||
sock->sendPacket(pkt, P_FE2CL_PC_NEW);
|
||||
}
|
||||
|
||||
/*
|
||||
* Entity leaving view.
|
||||
*/
|
||||
void BaseNPC::disappearFromViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
|
||||
pkt.iNPC_ID = id;
|
||||
sock->sendPacket(pkt, P_FE2CL_NPC_EXIT);
|
||||
}
|
||||
|
||||
void Bus::disappearFromViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, pkt);
|
||||
pkt.eTT = 3;
|
||||
pkt.iT_ID = id;
|
||||
sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_EXIT);
|
||||
}
|
||||
|
||||
void Egg::disappearFromViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_SHINY_EXIT, pkt);
|
||||
pkt.iShinyID = id;
|
||||
sock->sendPacket(pkt, P_FE2CL_SHINY_EXIT);
|
||||
}
|
||||
|
||||
void Player::disappearFromViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_PC_EXIT, pkt);
|
||||
pkt.iID = iID;
|
||||
sock->sendPacket(pkt, P_FE2CL_PC_EXIT);
|
||||
}
|
||||
175
src/Entities.hpp
Normal file
175
src/Entities.hpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "EntityRef.hpp"
|
||||
#include "Buffs.hpp"
|
||||
#include "Chunking.hpp"
|
||||
#include "Groups.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
enum class AIState {
|
||||
INACTIVE,
|
||||
ROAMING,
|
||||
COMBAT,
|
||||
RETREAT,
|
||||
DEAD
|
||||
};
|
||||
|
||||
struct Entity {
|
||||
EntityKind kind = EntityKind::INVALID;
|
||||
int x = 0, y = 0, z = 0;
|
||||
uint64_t instanceID = 0;
|
||||
ChunkPos chunkPos = {};
|
||||
std::set<Chunk*> viewableChunks = {};
|
||||
|
||||
// destructor must be virtual, apparently
|
||||
virtual ~Entity() {}
|
||||
|
||||
virtual bool isExtant() { return true; }
|
||||
|
||||
// stubs
|
||||
virtual void enterIntoViewOf(CNSocket *sock) = 0;
|
||||
virtual void disappearFromViewOf(CNSocket *sock) = 0;
|
||||
};
|
||||
|
||||
/*
|
||||
* Interfaces
|
||||
*/
|
||||
class ICombatant {
|
||||
public:
|
||||
ICombatant() {}
|
||||
virtual ~ICombatant() {}
|
||||
|
||||
virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
|
||||
virtual Buff* getBuff(int) = 0;
|
||||
virtual void removeBuff(int) = 0;
|
||||
virtual void removeBuff(int, BuffClass) = 0;
|
||||
virtual void clearBuffs(bool) = 0;
|
||||
virtual bool hasBuff(int) = 0;
|
||||
virtual int getCompositeCondition() = 0;
|
||||
virtual int takeDamage(EntityRef, int) = 0;
|
||||
virtual int heal(EntityRef, int) = 0;
|
||||
virtual bool isAlive() = 0;
|
||||
virtual int getCurrentHP() = 0;
|
||||
virtual int getMaxHP() = 0;
|
||||
virtual int getLevel() = 0;
|
||||
virtual std::vector<EntityRef> getGroupMembers() = 0;
|
||||
virtual int32_t getCharType() = 0;
|
||||
virtual int32_t getID() = 0;
|
||||
virtual EntityRef getRef() = 0;
|
||||
virtual void step(time_t currTime) = 0;
|
||||
};
|
||||
|
||||
/*
|
||||
* Subclasses
|
||||
*/
|
||||
class BaseNPC : public Entity {
|
||||
public:
|
||||
int id;
|
||||
int type;
|
||||
int hp;
|
||||
int angle;
|
||||
bool loopingPath = false;
|
||||
|
||||
BaseNPC(int _A, uint64_t iID, int t, int _id) {
|
||||
kind = EntityKind::SIMPLE_NPC;
|
||||
type = t;
|
||||
hp = 400;
|
||||
angle = _A;
|
||||
id = _id;
|
||||
instanceID = iID;
|
||||
};
|
||||
|
||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||
|
||||
virtual sNPCAppearanceData getAppearanceData();
|
||||
};
|
||||
|
||||
struct CombatNPC : public BaseNPC, public ICombatant {
|
||||
int maxHealth = 0;
|
||||
int spawnX = 0;
|
||||
int spawnY = 0;
|
||||
int spawnZ = 0;
|
||||
int level = 0;
|
||||
int speed = 300;
|
||||
AIState state = AIState::INACTIVE;
|
||||
Group* group = nullptr;
|
||||
int playersInView = 0; // for optimizing away AI in empty chunks
|
||||
|
||||
std::map<AIState, void (*)(CombatNPC*, time_t)> stateHandlers;
|
||||
std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers;
|
||||
|
||||
std::unordered_map<int, Buff*> buffs = {};
|
||||
|
||||
CombatNPC(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, int id, int maxHP)
|
||||
: BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
|
||||
this->spawnX = spawnX;
|
||||
this->spawnY = spawnY;
|
||||
this->spawnZ = spawnZ;
|
||||
|
||||
kind = EntityKind::COMBAT_NPC;
|
||||
|
||||
stateHandlers[AIState::INACTIVE] = {};
|
||||
transitionHandlers[AIState::INACTIVE] = {};
|
||||
}
|
||||
|
||||
virtual sNPCAppearanceData getAppearanceData() override;
|
||||
|
||||
virtual bool isExtant() override { return hp > 0; }
|
||||
|
||||
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
||||
virtual Buff* getBuff(int buffId) override;
|
||||
virtual void removeBuff(int buffId) override;
|
||||
virtual void removeBuff(int buffId, BuffClass buffClass) override;
|
||||
virtual void clearBuffs(bool force) override;
|
||||
virtual bool hasBuff(int buffId) override;
|
||||
virtual int getCompositeCondition() override;
|
||||
virtual int takeDamage(EntityRef src, int amt) override;
|
||||
virtual int heal(EntityRef src, int amt) override;
|
||||
virtual bool isAlive() override;
|
||||
virtual int getCurrentHP() override;
|
||||
virtual int getMaxHP() override;
|
||||
virtual int getLevel() override;
|
||||
virtual std::vector<EntityRef> getGroupMembers() override;
|
||||
virtual int32_t getCharType() override;
|
||||
virtual int32_t getID() override;
|
||||
virtual EntityRef getRef() override;
|
||||
virtual void step(time_t currTime) override;
|
||||
|
||||
virtual void transition(AIState newState, EntityRef src);
|
||||
};
|
||||
|
||||
// Mob is in MobAI.hpp, Player is in Player.hpp
|
||||
|
||||
struct Egg : public BaseNPC {
|
||||
bool summoned = false;
|
||||
bool dead = false;
|
||||
time_t deadUntil;
|
||||
|
||||
Egg(uint64_t iID, int t, int32_t id, bool summon)
|
||||
: BaseNPC(0, iID, t, id) {
|
||||
summoned = summon;
|
||||
kind = EntityKind::EGG;
|
||||
}
|
||||
|
||||
virtual bool isExtant() override { return !dead; }
|
||||
|
||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||
};
|
||||
|
||||
struct Bus : public BaseNPC {
|
||||
Bus(int angle, uint64_t iID, int t, int id) :
|
||||
BaseNPC(angle, iID, t, id) {
|
||||
kind = EntityKind::BUS;
|
||||
loopingPath = true;
|
||||
}
|
||||
|
||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||
};
|
||||
56
src/EntityRef.hpp
Normal file
56
src/EntityRef.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
/* forward declaration(s) */
|
||||
struct Entity;
|
||||
|
||||
enum EntityKind {
|
||||
INVALID,
|
||||
PLAYER,
|
||||
SIMPLE_NPC,
|
||||
COMBAT_NPC,
|
||||
MOB,
|
||||
EGG,
|
||||
BUS
|
||||
};
|
||||
|
||||
struct EntityRef {
|
||||
EntityKind kind;
|
||||
union {
|
||||
CNSocket *sock;
|
||||
int32_t id;
|
||||
};
|
||||
|
||||
EntityRef(CNSocket *s);
|
||||
EntityRef(int32_t i);
|
||||
|
||||
bool isValid() const;
|
||||
Entity *getEntity() const;
|
||||
|
||||
bool operator==(const EntityRef& other) const {
|
||||
if (kind != other.kind)
|
||||
return false;
|
||||
|
||||
if (kind == EntityKind::PLAYER)
|
||||
return sock == other.sock;
|
||||
|
||||
return id == other.id;
|
||||
}
|
||||
|
||||
bool operator!=(const EntityRef& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
// arbitrary ordering
|
||||
bool operator<(const EntityRef& other) const {
|
||||
if (kind == other.kind) {
|
||||
if (kind == EntityKind::PLAYER)
|
||||
return sock < other.sock;
|
||||
else
|
||||
return id < other.id;
|
||||
}
|
||||
|
||||
return kind < other.kind;
|
||||
}
|
||||
};
|
||||
@@ -1,376 +0,0 @@
|
||||
#include "CNShardServer.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "ChatManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "GroupManager.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
void GroupManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_INVITE, requestGroup);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_INVITE_REFUSE, refuseGroup);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_JOIN, joinGroup);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_LEAVE, leaveGroup);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE, chatGroup);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE, menuChatGroup);
|
||||
}
|
||||
|
||||
void GroupManager::requestGroup(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_GROUP_INVITE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To);
|
||||
|
||||
if (plr == nullptr || otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
// fail if the group is full or the other player is already in a group
|
||||
if (plr->groupCnt >= 4 || otherPlr->groupCnt > 1) {
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp);
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(recv->iID_To);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE, resp);
|
||||
|
||||
resp.iHostID = plr->iIDGroup;
|
||||
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE, sizeof(sP_FE2CL_PC_GROUP_INVITE));
|
||||
}
|
||||
|
||||
void GroupManager::refuseGroup(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE*)data->buf;
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(recv->iID_From);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_REFUSE, resp);
|
||||
|
||||
resp.iID_To = plr->iID;
|
||||
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_REFUSE, sizeof(sP_FE2CL_PC_GROUP_INVITE_REFUSE));
|
||||
}
|
||||
|
||||
void GroupManager::joinGroup(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_GROUP_JOIN))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_GROUP_JOIN* recv = (sP_CL2FE_REQ_PC_GROUP_JOIN*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
|
||||
|
||||
if (plr == nullptr || otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
// fail if the group is full or the other player is already in a group
|
||||
if (plr->groupCnt > 1 || plr->iIDGroup != plr->iID || otherPlr->groupCnt >= 4) {
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_JOIN_FAIL, resp);
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), otherPlr->groupCnt + 1, sizeof(sPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
int bitFlagBefore = getGroupFlags(otherPlr);
|
||||
|
||||
plr->iIDGroup = otherPlr->iID;
|
||||
otherPlr->groupCnt += 1;
|
||||
otherPlr->groupIDs[otherPlr->groupCnt-1] = plr->iID;
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + otherPlr->groupCnt * sizeof(sPCGroupMemberInfo);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_PC_GROUP_JOIN *resp = (sP_FE2CL_PC_GROUP_JOIN*)respbuf;
|
||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_JOIN));
|
||||
|
||||
resp->iID_NewMember = plr->iID;
|
||||
resp->iMemberPCCnt = otherPlr->groupCnt;
|
||||
|
||||
int bitFlag = getGroupFlags(otherPlr);
|
||||
|
||||
for (int i = 0; i < otherPlr->groupCnt; i++) {
|
||||
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
|
||||
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
|
||||
|
||||
if (varPlr == nullptr || sockTo == nullptr)
|
||||
continue;
|
||||
|
||||
respdata[i].iPC_ID = varPlr->iID;
|
||||
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
|
||||
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
|
||||
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
||||
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
||||
respdata[i].iSpecialState = varPlr->iSpecialState;
|
||||
respdata[i].iLv = varPlr->level;
|
||||
respdata[i].iHP = varPlr->HP;
|
||||
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
||||
//respdata[i].iMapType = 0;
|
||||
//respdata[i].iMapNum = 0;
|
||||
respdata[i].iX = varPlr->x;
|
||||
respdata[i].iY = varPlr->y;
|
||||
respdata[i].iZ = varPlr->z;
|
||||
// client doesnt read nano data here
|
||||
|
||||
NanoManager::nanoChangeBuff(sockTo, varPlr, bitFlagBefore | varPlr->iConditionBitFlag, bitFlag | varPlr->iConditionBitFlag);
|
||||
}
|
||||
|
||||
sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_JOIN, resplen);
|
||||
}
|
||||
|
||||
void GroupManager::leaveGroup(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
groupKickPlayer(plr);
|
||||
}
|
||||
|
||||
void GroupManager::chatGroup(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (plr == nullptr || otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp);
|
||||
memcpy(resp.szFreeChat, chat->szFreeChat, sizeof(chat->szFreeChat));
|
||||
resp.iSendPCID = plr->iID;
|
||||
resp.iEmoteCode = chat->iEmoteCode;
|
||||
sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
|
||||
}
|
||||
|
||||
void GroupManager::menuChatGroup(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (plr == nullptr || otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp);
|
||||
memcpy(resp.szFreeChat, chat->szFreeChat, sizeof(chat->szFreeChat));
|
||||
resp.iSendPCID = plr->iID;
|
||||
resp.iEmoteCode = chat->iEmoteCode;
|
||||
sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
|
||||
}
|
||||
|
||||
void GroupManager::sendToGroup(Player* plr, void* buf, uint32_t type, size_t size) {
|
||||
for (int i = 0; i < plr->groupCnt; i++) {
|
||||
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[i]);
|
||||
|
||||
if (sock == nullptr)
|
||||
continue;
|
||||
|
||||
if (type == P_FE2CL_PC_GROUP_LEAVE_SUCC) {
|
||||
Player* leavingPlr = PlayerManager::getPlayer(sock);
|
||||
leavingPlr->iIDGroup = leavingPlr->iID;
|
||||
}
|
||||
|
||||
sock->sendPacket(buf, type, size);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupManager::groupTickInfo(Player* plr) {
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), plr->groupCnt, sizeof(sPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + plr->groupCnt * sizeof(sPCGroupMemberInfo);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_PC_GROUP_MEMBER_INFO *resp = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf;
|
||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO));
|
||||
|
||||
resp->iID = plr->iID;
|
||||
resp->iMemberPCCnt = plr->groupCnt;
|
||||
|
||||
for (int i = 0; i < plr->groupCnt; i++) {
|
||||
Player* varPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
|
||||
|
||||
if (varPlr == nullptr)
|
||||
continue;
|
||||
|
||||
respdata[i].iPC_ID = varPlr->iID;
|
||||
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
|
||||
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
|
||||
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
||||
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
||||
respdata[i].iSpecialState = varPlr->iSpecialState;
|
||||
respdata[i].iLv = varPlr->level;
|
||||
respdata[i].iHP = varPlr->HP;
|
||||
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
||||
//respdata[i].iMapType = 0;
|
||||
//respdata[i].iMapNum = 0;
|
||||
respdata[i].iX = varPlr->x;
|
||||
respdata[i].iY = varPlr->y;
|
||||
respdata[i].iZ = varPlr->z;
|
||||
if (varPlr->activeNano > 0) {
|
||||
respdata[i].bNano = 1;
|
||||
respdata[i].Nano = varPlr->Nanos[varPlr->activeNano];
|
||||
}
|
||||
}
|
||||
|
||||
sendToGroup(plr, (void*)&respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO, resplen);
|
||||
}
|
||||
|
||||
void GroupManager::groupKickPlayer(Player* plr) {
|
||||
// if you are the group leader, destroy your own group and kick everybody
|
||||
if (plr->iID == plr->iIDGroup) {
|
||||
groupUnbuff(plr);
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
|
||||
sendToGroup(plr, (void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
|
||||
plr->groupCnt = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), otherPlr->groupCnt - 1, sizeof(sPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_LEAVE) + (otherPlr->groupCnt - 1) * sizeof(sPCGroupMemberInfo);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_PC_GROUP_LEAVE *resp = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf;
|
||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_LEAVE));
|
||||
|
||||
resp->iID_LeaveMember = plr->iID;
|
||||
resp->iMemberPCCnt = otherPlr->groupCnt - 1;
|
||||
|
||||
int bitFlagBefore = getGroupFlags(otherPlr);
|
||||
int bitFlag = bitFlagBefore & ~plr->iGroupConditionBitFlag;
|
||||
int moveDown = 0;
|
||||
|
||||
for (int i = 0; i < otherPlr->groupCnt; i++) {
|
||||
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
|
||||
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
|
||||
|
||||
if (varPlr == nullptr || sockTo == nullptr)
|
||||
continue;
|
||||
|
||||
if (moveDown == 1)
|
||||
otherPlr->groupIDs[i-1] = otherPlr->groupIDs[i];
|
||||
|
||||
respdata[i-moveDown].iPC_ID = varPlr->iID;
|
||||
respdata[i-moveDown].iPCUID = varPlr->PCStyle.iPC_UID;
|
||||
respdata[i-moveDown].iNameCheck = varPlr->PCStyle.iNameCheck;
|
||||
memcpy(respdata[i-moveDown].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
||||
memcpy(respdata[i-moveDown].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
||||
respdata[i-moveDown].iSpecialState = varPlr->iSpecialState;
|
||||
respdata[i-moveDown].iLv = varPlr->level;
|
||||
respdata[i-moveDown].iHP = varPlr->HP;
|
||||
respdata[i-moveDown].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
||||
// respdata[i-moveDown]].iMapType = 0;
|
||||
// respdata[i-moveDown]].iMapNum = 0;
|
||||
respdata[i-moveDown].iX = varPlr->x;
|
||||
respdata[i-moveDown].iY = varPlr->y;
|
||||
respdata[i-moveDown].iZ = varPlr->z;
|
||||
// client doesnt read nano data here
|
||||
|
||||
if (varPlr == plr) {
|
||||
moveDown = 1;
|
||||
otherPlr->groupIDs[i] = 0;
|
||||
NanoManager::nanoChangeBuff(sockTo, varPlr, bitFlagBefore | varPlr->iConditionBitFlag, varPlr->iConditionBitFlag);
|
||||
} else
|
||||
NanoManager::nanoChangeBuff(sockTo, varPlr, bitFlagBefore | varPlr->iConditionBitFlag, bitFlag | varPlr->iConditionBitFlag);
|
||||
}
|
||||
|
||||
plr->iIDGroup = plr->iID;
|
||||
otherPlr->groupCnt -= 1;
|
||||
|
||||
sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_LEAVE, resplen);
|
||||
|
||||
CNSocket* sock = PlayerManager::getSockFromID(plr->iID);
|
||||
|
||||
if (sock == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
|
||||
sock->sendPacket((void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
|
||||
}
|
||||
|
||||
void GroupManager::groupUnbuff(Player* plr) {
|
||||
int bitFlag = getGroupFlags(plr);
|
||||
|
||||
for (int i = 0; i < plr->groupCnt; i++) {
|
||||
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[i]);
|
||||
|
||||
if (sock == nullptr)
|
||||
continue;
|
||||
|
||||
Player* otherPlr = PlayerManager::getPlayer(sock);
|
||||
NanoManager::nanoChangeBuff(sock, otherPlr, bitFlag | otherPlr->iConditionBitFlag, otherPlr->iConditionBitFlag);
|
||||
}
|
||||
}
|
||||
|
||||
int GroupManager::getGroupFlags(Player* plr) {
|
||||
int bitFlag = 0;
|
||||
|
||||
for (int i = 0; i < plr->groupCnt; i++) {
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
continue;
|
||||
|
||||
bitFlag |= otherPlr->iGroupConditionBitFlag;
|
||||
}
|
||||
|
||||
return bitFlag;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
namespace GroupManager {
|
||||
void init();
|
||||
|
||||
void requestGroup(CNSocket* sock, CNPacketData* data);
|
||||
void refuseGroup(CNSocket* sock, CNPacketData* data);
|
||||
void joinGroup(CNSocket* sock, CNPacketData* data);
|
||||
void leaveGroup(CNSocket* sock, CNPacketData* data);
|
||||
void chatGroup(CNSocket* sock, CNPacketData* data);
|
||||
void menuChatGroup(CNSocket* sock, CNPacketData* data);
|
||||
void sendToGroup(Player* plr, void* buf, uint32_t type, size_t size);
|
||||
void groupTickInfo(Player* plr);
|
||||
void groupKickPlayer(Player* plr);
|
||||
void groupUnbuff(Player* plr);
|
||||
int getGroupFlags(Player* plr);
|
||||
}
|
||||
311
src/Groups.cpp
Normal file
311
src/Groups.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
#include "Groups.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Entities.hpp"
|
||||
|
||||
/*
|
||||
* NOTE: Variadic response packets that list group members are technically
|
||||
* double-variadic, as they have two count members with trailing struct counts,
|
||||
* and are thus incompatible with the generic sendPacket() wrapper.
|
||||
* That means we still have to (carefully) use validOutVarPacket() in this
|
||||
* source file.
|
||||
*/
|
||||
|
||||
using namespace Groups;
|
||||
|
||||
Group::Group(EntityRef leader) {
|
||||
addToGroup(this, leader);
|
||||
}
|
||||
|
||||
static void attachGroupData(std::vector<EntityRef>& pcs, std::vector<EntityRef>& npcs, uint8_t* pivot) {
|
||||
for(EntityRef pcRef : pcs) {
|
||||
sPCGroupMemberInfo* info = (sPCGroupMemberInfo*)pivot;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(pcRef.sock);
|
||||
info->iPC_ID = plr->iID;
|
||||
info->iPCUID = plr->PCStyle.iPC_UID;
|
||||
info->iNameCheck = plr->PCStyle.iNameCheck;
|
||||
memcpy(info->szFirstName, plr->PCStyle.szFirstName, sizeof(plr->PCStyle.szFirstName));
|
||||
memcpy(info->szLastName, plr->PCStyle.szLastName, sizeof(plr->PCStyle.szLastName));
|
||||
info->iSpecialState = plr->iSpecialState;
|
||||
info->iLv = plr->level;
|
||||
info->iHP = plr->HP;
|
||||
info->iMaxHP = PC_MAXHEALTH(plr->level);
|
||||
// info->iMapType = 0;
|
||||
// info->iMapNum = 0;
|
||||
info->iX = plr->x;
|
||||
info->iY = plr->y;
|
||||
info->iZ = plr->z;
|
||||
if(plr->activeNano > 0) {
|
||||
info->Nano = *plr->getActiveNano();
|
||||
info->bNano = true;
|
||||
}
|
||||
|
||||
pivot = (uint8_t*)(info + 1);
|
||||
}
|
||||
for(EntityRef npcRef : npcs) {
|
||||
sNPCGroupMemberInfo* info = (sNPCGroupMemberInfo*)pivot;
|
||||
|
||||
// probably should not assume that the combatant is an
|
||||
// entity, but it works for now
|
||||
BaseNPC* npc = (BaseNPC*)npcRef.getEntity();
|
||||
info->iNPC_ID = npcRef.id;
|
||||
info->iNPC_Type = npc->type;
|
||||
info->iHP = npc->hp;
|
||||
info->iX = npc->x;
|
||||
info->iY = npc->y;
|
||||
info->iZ = npc->z;
|
||||
|
||||
pivot = (uint8_t*)(info + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void Groups::addToGroup(Group* group, EntityRef member) {
|
||||
if (member.kind == EntityKind::PLAYER) {
|
||||
Player* plr = PlayerManager::getPlayer(member.sock);
|
||||
plr->group = group;
|
||||
}
|
||||
else if (member.kind == EntityKind::COMBAT_NPC) {
|
||||
CombatNPC* npc = (CombatNPC*)member.getEntity();
|
||||
npc->group = group;
|
||||
}
|
||||
else {
|
||||
std::cout << "[WARN] Adding a weird entity type to a group" << std::endl;
|
||||
}
|
||||
|
||||
group->members.push_back(member);
|
||||
|
||||
if(member.kind == EntityKind::PLAYER) {
|
||||
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
|
||||
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
|
||||
size_t pcCount = pcs.size();
|
||||
size_t npcCount = npcs.size();
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
|
||||
sP_FE2CL_PC_GROUP_JOIN* pkt = (sP_FE2CL_PC_GROUP_JOIN*)respbuf;
|
||||
|
||||
pkt->iID_NewMember = PlayerManager::getPlayer(member.sock)->iID;
|
||||
pkt->iMemberPCCnt = (int32_t)pcCount;
|
||||
pkt->iMemberNPCCnt = (int32_t)npcCount;
|
||||
|
||||
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), pcCount, sizeof(sPCGroupMemberInfo))
|
||||
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size" << std::endl;
|
||||
} else {
|
||||
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||
attachGroupData(pcs, npcs, pivot);
|
||||
// PC_GROUP_JOIN_SUCC and PC_GROUP_JOIN carry identical payloads but have different IDs
|
||||
// (and the client does care!) so we need to send one to the new member
|
||||
// and the other to the rest
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo);
|
||||
member.sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_JOIN_SUCC, resplen);
|
||||
sendToGroup(group, member, respbuf, P_FE2CL_PC_GROUP_JOIN, resplen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Groups::removeFromGroup(Group* group, EntityRef member) {
|
||||
if (member.kind == EntityKind::PLAYER) {
|
||||
Player* plr = PlayerManager::getPlayer(member.sock);
|
||||
plr->group = nullptr; // no dangling pointers here muahaahahah
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, leavePkt);
|
||||
member.sock->sendPacket(leavePkt, P_FE2CL_PC_GROUP_LEAVE_SUCC);
|
||||
}
|
||||
else if (member.kind == EntityKind::COMBAT_NPC) {
|
||||
CombatNPC* npc = (CombatNPC*)member.getEntity();
|
||||
npc->group = nullptr;
|
||||
}
|
||||
else {
|
||||
std::cout << "[WARN] Removing a weird entity type from a group" << std::endl;
|
||||
}
|
||||
|
||||
auto it = std::find(group->members.begin(), group->members.end(), member);
|
||||
if (it == group->members.end()) {
|
||||
std::cout << "[WARN] Tried to remove a member that isn't in the group" << std::endl;
|
||||
} else {
|
||||
group->members.erase(it);
|
||||
}
|
||||
|
||||
if(member.kind == EntityKind::PLAYER) {
|
||||
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
|
||||
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
|
||||
size_t pcCount = pcs.size();
|
||||
size_t npcCount = npcs.size();
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
|
||||
sP_FE2CL_PC_GROUP_LEAVE* pkt = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf;
|
||||
|
||||
pkt->iID_LeaveMember = PlayerManager::getPlayer(member.sock)->iID;
|
||||
pkt->iMemberPCCnt = (int32_t)pcCount;
|
||||
pkt->iMemberNPCCnt = (int32_t)npcCount;
|
||||
|
||||
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), pcCount, sizeof(sPCGroupMemberInfo))
|
||||
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size" << std::endl;
|
||||
} else {
|
||||
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||
attachGroupData(pcs, npcs, pivot);
|
||||
sendToGroup(group, respbuf, P_FE2CL_PC_GROUP_LEAVE,
|
||||
sizeof(sP_FE2CL_PC_GROUP_LEAVE) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo));
|
||||
}
|
||||
}
|
||||
|
||||
if (group->members.size() == 1) {
|
||||
return removeFromGroup(group, group->members.back());
|
||||
}
|
||||
|
||||
if (group->members.empty()) {
|
||||
delete group; // cleanup memory
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Groups::disbandGroup(Group* group) {
|
||||
// remove everyone from the group!!
|
||||
bool done = false;
|
||||
while(!done) {
|
||||
EntityRef back = group->members.back();
|
||||
done = removeFromGroup(group, back);
|
||||
}
|
||||
}
|
||||
|
||||
static void requestGroup(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
// fail if the group is full or the other player is already in a group
|
||||
if ((plr->group != nullptr && plr->group->filter(EntityKind::PLAYER).size() >= 4) || otherPlr->group != nullptr) {
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp);
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(recv->iID_To);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE, resp);
|
||||
|
||||
resp.iHostID = plr->iID;
|
||||
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE, sizeof(sP_FE2CL_PC_GROUP_INVITE));
|
||||
}
|
||||
|
||||
static void refuseGroup(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE*)data->buf;
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(recv->iID_From);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_REFUSE, resp);
|
||||
|
||||
resp.iID_To = plr->iID;
|
||||
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_REFUSE, sizeof(sP_FE2CL_PC_GROUP_INVITE_REFUSE));
|
||||
}
|
||||
|
||||
static void joinGroup(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_GROUP_JOIN* recv = (sP_CL2FE_REQ_PC_GROUP_JOIN*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return; // disconnect or something
|
||||
|
||||
int size = otherPlr->group == nullptr ? 1 : otherPlr->group->filter(EntityKind::PLAYER).size();
|
||||
|
||||
// fail if the group is full or the other player is already in a group
|
||||
if (plr->group != nullptr || size + 1 > 4) {
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_JOIN_FAIL, resp);
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
if (otherPlr->group == nullptr) {
|
||||
// create group
|
||||
EntityRef otherPlrRef = PlayerManager::getSockFromID(recv->iID_From);
|
||||
otherPlr->group = new Group(otherPlrRef);
|
||||
}
|
||||
addToGroup(otherPlr->group, sock);
|
||||
}
|
||||
|
||||
static void leaveGroup(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
groupKick(plr->group, sock);
|
||||
}
|
||||
|
||||
void Groups::sendToGroup(Group* group, void* buf, uint32_t type, size_t size) {
|
||||
auto players = group->filter(EntityKind::PLAYER);
|
||||
for (EntityRef ref : players) {
|
||||
ref.sock->sendPacket(buf, type, size);
|
||||
}
|
||||
}
|
||||
|
||||
void Groups::sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size) {
|
||||
auto players = group->filter(EntityKind::PLAYER);
|
||||
for (EntityRef ref : players) {
|
||||
if(ref != excluded) ref.sock->sendPacket(buf, type, size);
|
||||
}
|
||||
}
|
||||
|
||||
void Groups::groupTickInfo(CNSocket* sock) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Group* group = plr->group;
|
||||
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
|
||||
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
|
||||
size_t pcCount = pcs.size();
|
||||
size_t npcCount = npcs.size();
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
|
||||
sP_FE2CL_PC_GROUP_MEMBER_INFO* pkt = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf;
|
||||
|
||||
pkt->iID = plr->iID;
|
||||
pkt->iMemberPCCnt = (int32_t)pcCount;
|
||||
pkt->iMemberNPCCnt = (int32_t)npcCount;
|
||||
|
||||
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), pcCount, sizeof(sPCGroupMemberInfo))
|
||||
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_MEMBER_INFO packet size" << std::endl;
|
||||
} else {
|
||||
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||
attachGroupData(pcs, npcs, pivot);
|
||||
sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO,
|
||||
sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo));
|
||||
}
|
||||
}
|
||||
|
||||
void Groups::groupKick(Group* group, EntityRef ref) {
|
||||
|
||||
// if you are the group leader, destroy your own group and kick everybody
|
||||
if (group->members[0] == ref) {
|
||||
disbandGroup(group);
|
||||
return;
|
||||
}
|
||||
|
||||
removeFromGroup(group, ref);
|
||||
}
|
||||
|
||||
void Groups::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_INVITE, requestGroup);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_INVITE_REFUSE, refuseGroup);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_JOIN, joinGroup);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_LEAVE, leaveGroup);
|
||||
}
|
||||
37
src/Groups.hpp
Normal file
37
src/Groups.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "EntityRef.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
struct Group {
|
||||
std::vector<EntityRef> members;
|
||||
|
||||
std::vector<EntityRef> filter(EntityKind kind) {
|
||||
std::vector<EntityRef> filtered;
|
||||
std::copy_if(members.begin(), members.end(), std::back_inserter(filtered), [kind](EntityRef e) {
|
||||
return e.kind == kind;
|
||||
});
|
||||
return filtered;
|
||||
}
|
||||
EntityRef getLeader() {
|
||||
assert(members.size() > 0);
|
||||
return members[0];
|
||||
}
|
||||
|
||||
Group(EntityRef leader);
|
||||
};
|
||||
|
||||
namespace Groups {
|
||||
void init();
|
||||
|
||||
void sendToGroup(Group* group, void* buf, uint32_t type, size_t size);
|
||||
void sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size);
|
||||
void groupTickInfo(CNSocket* sock);
|
||||
|
||||
void groupKick(Group* group, EntityRef ref);
|
||||
void addToGroup(Group* group, EntityRef member);
|
||||
bool removeFromGroup(Group* group, EntityRef member); // true iff group deleted
|
||||
void disbandGroup(Group* group);
|
||||
}
|
||||
1059
src/ItemManager.cpp
1059
src/ItemManager.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,67 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNShardServer.hpp"
|
||||
#include "Player.hpp"
|
||||
|
||||
struct Item {
|
||||
bool tradeable, sellable;
|
||||
int buyPrice, sellPrice, stackSize, level, rarity, pointDamage, groupDamage, defense, gender; // TODO: implement more as needed
|
||||
};
|
||||
struct VendorListing {
|
||||
int sort, type, iID;
|
||||
};
|
||||
struct CrocPotEntry {
|
||||
int multStats, multLooks;
|
||||
float base, rd0, rd1, rd2, rd3;
|
||||
};
|
||||
struct Crate {
|
||||
int rarityRatioId;
|
||||
std::vector<int> itemSets;
|
||||
};
|
||||
|
||||
namespace ItemManager {
|
||||
enum class SlotType {
|
||||
EQUIP = 0,
|
||||
INVENTORY = 1,
|
||||
BANK = 3
|
||||
};
|
||||
// hopefully this is fine since it's never modified after load
|
||||
extern std::map<std::pair<int32_t, int32_t>, Item> ItemData; // <id, type> -> data
|
||||
extern std::map<int32_t, std::vector<VendorListing>> VendorTables;
|
||||
extern std::map<int32_t, CrocPotEntry> CrocPotTable; // level gap -> entry
|
||||
extern std::map<int32_t, std::vector<int>> RarityRatios;
|
||||
extern std::map<int32_t, Crate> Crates;
|
||||
// pair <Itemset, Rarity> -> vector of pointers (map iterators) to records in ItemData (it looks a lot scarier than it is)
|
||||
extern std::map<std::pair<int32_t, int32_t>,
|
||||
std::vector<std::map<std::pair<int32_t, int32_t>, Item>::iterator>> CrateItems;
|
||||
|
||||
void init();
|
||||
|
||||
void itemMoveHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemDeleteHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemGMGiveHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemUseHandler(CNSocket* sock, CNPacketData* data);
|
||||
// Bank
|
||||
void itemBankOpenHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemTradeOfferHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemTradeOfferAcceptHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemTradeOfferRefusalHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemTradeConfirmHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemTradeConfirmCancelHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemTradeRegisterItemHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemTradeUnregisterItemHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemTradeRegisterCashHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemTradeChatHandler(CNSocket* sock, CNPacketData* data);
|
||||
void chestOpenHandler(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// crate opening logic with all helper functions
|
||||
int getItemSetId(Crate& crate, int crateId);
|
||||
int getRarity(Crate& crate, int itemSetId);
|
||||
int getCrateItem(sItemBase& reward, int itemSetId, int rarity, int playerGender);
|
||||
|
||||
int findFreeSlot(Player *plr);
|
||||
Item* getItemData(int32_t id, int32_t type);
|
||||
void checkItemExpire(CNSocket* sock, Player* player);
|
||||
void setItemStats(Player* plr);
|
||||
void updateEquips(CNSocket* sock, Player* plr);
|
||||
}
|
||||
862
src/Items.cpp
Normal file
862
src/Items.cpp
Normal file
@@ -0,0 +1,862 @@
|
||||
#include "Items.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "Eggs.hpp"
|
||||
#include "MobAI.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Buffs.hpp"
|
||||
|
||||
#include <string.h> // for memset()
|
||||
#include <assert.h>
|
||||
#include <numeric>
|
||||
|
||||
using namespace Items;
|
||||
|
||||
std::map<std::pair<int32_t, int32_t>, Items::Item> Items::ItemData;
|
||||
std::map<int32_t, CrocPotEntry> Items::CrocPotTable;
|
||||
std::map<int32_t, std::vector<int32_t>> Items::RarityWeights;
|
||||
std::map<int32_t, Crate> Items::Crates;
|
||||
std::map<int32_t, ItemReference> Items::ItemReferences;
|
||||
std::map<std::string, std::vector<std::pair<int32_t, int32_t>>> Items::CodeItems;
|
||||
|
||||
std::map<int32_t, CrateDropChance> Items::CrateDropChances;
|
||||
std::map<int32_t, std::vector<int32_t>> Items::CrateDropTypes;
|
||||
std::map<int32_t, MiscDropChance> Items::MiscDropChances;
|
||||
std::map<int32_t, MiscDropType> Items::MiscDropTypes;
|
||||
std::map<int32_t, MobDrop> Items::MobDrops;
|
||||
std::map<int32_t, int32_t> Items::EventToDropMap;
|
||||
std::map<int32_t, int32_t> Items::MobToDropMap;
|
||||
std::map<int32_t, ItemSet> Items::ItemSets;
|
||||
|
||||
#ifdef ACADEMY
|
||||
std::map<int32_t, int32_t> Items::NanoCapsules; // crate id -> nano id
|
||||
|
||||
static void nanoCapsuleHandler(CNSocket* sock, int slot, sItemBase *chest) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
int32_t nanoId = NanoCapsules[chest->iID];
|
||||
|
||||
// chest opening acknowledgement packet
|
||||
INITSTRUCT(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, resp);
|
||||
resp.iSlotNum = slot;
|
||||
|
||||
// in order to remove capsule form inventory, we have to send item reward packet with empty item
|
||||
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||
|
||||
// we know it's only one trailing struct, so we can skip full validation
|
||||
uint8_t respbuf[resplen]; // not a variable length array, don't worry
|
||||
sP_FE2CL_REP_REWARD_ITEM* reward = (sP_FE2CL_REP_REWARD_ITEM*)respbuf;
|
||||
sItemReward* item = (sItemReward*)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
|
||||
|
||||
// don't forget to zero the buffer!
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
// maintain stats
|
||||
reward->m_iCandy = plr->money;
|
||||
reward->m_iFusionMatter = plr->fusionmatter;
|
||||
reward->iFatigue = 100; // prevents warning message
|
||||
reward->iFatigue_Level = 1;
|
||||
reward->iItemCnt = 1; // remember to update resplen if you change this
|
||||
reward->m_iBatteryN = plr->batteryN;
|
||||
reward->m_iBatteryW = plr->batteryW;
|
||||
|
||||
item->iSlotNum = slot;
|
||||
item->eIL = 1;
|
||||
|
||||
// update player serverside
|
||||
plr->Inven[slot] = item->sItem;
|
||||
|
||||
// transmit item
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
||||
|
||||
// transmit chest opening acknowledgement packet
|
||||
sock->sendPacket(resp, P_FE2CL_REP_ITEM_CHEST_OPEN_SUCC);
|
||||
|
||||
// check if player doesn't already have this nano
|
||||
if (plr->Nanos[nanoId].iID != 0) {
|
||||
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
|
||||
msg.iDuringTime = 4;
|
||||
std::string text = "You have already acquired this nano!";
|
||||
U8toU16(text, msg.szAnnounceMsg, sizeof(text));
|
||||
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||
return;
|
||||
}
|
||||
Nanos::addNano(sock, nanoId, -1, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int choice(const std::vector<int>& weights, int rolled) {
|
||||
int total = std::accumulate(weights.begin(), weights.end(), 0);
|
||||
int randValue = rolled % total;
|
||||
int currentIndex = -1;
|
||||
|
||||
do {
|
||||
currentIndex++;
|
||||
randValue -= weights[currentIndex];
|
||||
} while (randValue >= 0);
|
||||
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
static int getRarity(int crateId, int itemSetId) {
|
||||
Crate& crate = Items::Crates[crateId];
|
||||
|
||||
// find rarity ratio
|
||||
if (Items::RarityWeights.find(crate.rarityWeightId) == Items::RarityWeights.end()) {
|
||||
std::cout << "[WARN] Rarity Weight " << crate.rarityWeightId << " not found!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<int>& rarityWeights = Items::RarityWeights[crate.rarityWeightId];
|
||||
ItemSet& itemSet = Items::ItemSets[itemSetId];
|
||||
|
||||
/*
|
||||
* First we have to check if specified item set contains items with all specified rarities,
|
||||
* and if not eliminate them from the draw
|
||||
* it is simpler to do here than to fix individually in the file
|
||||
*/
|
||||
|
||||
// remember that rarities start from 1!
|
||||
std::set<int> rarityIndices;
|
||||
|
||||
for (int itemReferenceId : itemSet.itemReferenceIds) {
|
||||
if (Items::ItemReferences.find(itemReferenceId) == Items::ItemReferences.end())
|
||||
continue;
|
||||
|
||||
// alter rarity
|
||||
int itemRarity = (itemSet.alterRarityMap.find(itemReferenceId) == itemSet.alterRarityMap.end())
|
||||
? Items::ItemReferences[itemReferenceId].rarity
|
||||
: itemSet.alterRarityMap[itemReferenceId];
|
||||
|
||||
rarityIndices.insert(itemRarity - 1);
|
||||
|
||||
// shortcut
|
||||
if (rarityIndices.size() == rarityWeights.size())
|
||||
break;
|
||||
}
|
||||
|
||||
if (rarityIndices.empty()) {
|
||||
std::cout << "[WARN] Item Set " << crate.itemSetId << " has no valid items assigned?!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// retain the weights of rarities that actually exist in the itemset
|
||||
std::vector<int> relevantWeights(rarityWeights.size(), 0);
|
||||
for (int index : rarityIndices) {
|
||||
// check for out of bounds and rarity 0 items
|
||||
if (index >= 0 && index < rarityWeights.size())
|
||||
relevantWeights[index] = rarityWeights[index];
|
||||
}
|
||||
|
||||
// now return a random rarity number (starting from 1)
|
||||
// if relevantWeights is empty or all zeros, we default to giving a common (1) item
|
||||
// rarity 0 items will appear in the drop pool regardless of this roll
|
||||
return Rand::randWeighted(relevantWeights) + 1;
|
||||
}
|
||||
|
||||
static int getCrateItem(sItemBase* result, int itemSetId, int rarity, int playerGender) {
|
||||
ItemSet& itemSet = Items::ItemSets[itemSetId];
|
||||
|
||||
// collect valid items that match the rarity and gender (if not ignored)
|
||||
std::vector<std::pair<int, ItemReference*>> validItems;
|
||||
|
||||
for (int itemReferenceId : itemSet.itemReferenceIds) {
|
||||
if (Items::ItemReferences.find(itemReferenceId) == Items::ItemReferences.end()) {
|
||||
std::cout << "[WARN] Item reference " << itemReferenceId << " in item set type "
|
||||
<< itemSetId << " was not found, skipping..." << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
ItemReference* item = &Items::ItemReferences[itemReferenceId];
|
||||
|
||||
// alter rarity
|
||||
int itemRarity = (itemSet.alterRarityMap.find(itemReferenceId) == itemSet.alterRarityMap.end())
|
||||
? item->rarity
|
||||
: itemSet.alterRarityMap[itemReferenceId];
|
||||
|
||||
// if rarity doesn't match the selected one, exclude item
|
||||
// rarity 0 bypasses this step for an individual item
|
||||
if (!itemSet.ignoreRarity && itemRarity != 0 && itemRarity != rarity)
|
||||
continue;
|
||||
|
||||
// alter rarity
|
||||
int itemGender = (itemSet.alterGenderMap.find(itemReferenceId) == itemSet.alterGenderMap.end())
|
||||
? item->gender
|
||||
: itemSet.alterGenderMap[itemReferenceId];
|
||||
|
||||
// if gender is incorrect, exclude item
|
||||
// gender 0 bypasses this step for an individual item
|
||||
if (!itemSet.ignoreGender && itemGender != 0 && itemGender != playerGender)
|
||||
continue;
|
||||
|
||||
validItems.push_back(std::make_pair(itemReferenceId, item));
|
||||
}
|
||||
|
||||
if (validItems.empty()) {
|
||||
std::cout << "[WARN] Set ID " << itemSetId << " Rarity " << rarity << " contains no valid items" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// initialize all weights as the default weight for all item slots
|
||||
std::vector<int> itemWeights(validItems.size(), itemSet.defaultItemWeight);
|
||||
|
||||
if (!itemSet.alterItemWeightMap.empty()) {
|
||||
for (int i = 0; i < validItems.size(); i++) {
|
||||
int itemReferenceId = validItems[i].first;
|
||||
|
||||
if (itemSet.alterItemWeightMap.find(itemReferenceId) == itemSet.alterItemWeightMap.end())
|
||||
continue;
|
||||
|
||||
int weight = itemSet.alterItemWeightMap[itemReferenceId];
|
||||
// allow 0 weights for convenience
|
||||
if (weight > -1)
|
||||
itemWeights[i] = weight;
|
||||
}
|
||||
}
|
||||
|
||||
int chosenIndex = Rand::randWeighted(itemWeights);
|
||||
ItemReference* item = validItems[chosenIndex].second;
|
||||
|
||||
result->iID = item->itemId;
|
||||
result->iType = item->type;
|
||||
result->iOpt = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int getValidCrateId(int crateId) {
|
||||
// find the crate
|
||||
if (Items::Crates.find(crateId) == Items::Crates.end()) {
|
||||
std::cout << "[WARN] Crate " << crateId << " not found!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return crateId;
|
||||
}
|
||||
|
||||
static int getValidItemSetId(int crateId) {
|
||||
Crate& crate = Items::Crates[crateId];
|
||||
|
||||
// find item set type
|
||||
if (Items::ItemSets.find(crate.itemSetId) == Items::ItemSets.end()) {
|
||||
std::cout << "[WARN] Crate " << crateId << " was assigned item set "
|
||||
<< crate.itemSetId << " which is invalid!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return crate.itemSetId;
|
||||
}
|
||||
|
||||
static void itemMoveHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto itemmove = (sP_CL2FE_REQ_ITEM_MOVE*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_PC_ITEM_MOVE_SUCC, resp);
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// sanity check
|
||||
if (itemmove->iToSlotNum < 0 || itemmove->iFromSlotNum < 0)
|
||||
return;
|
||||
// NOTE: sending a no-op, "move in-place" packet is not necessary
|
||||
|
||||
if (plr->isTrading) {
|
||||
std::cout << "[WARN] Player attempted to move item while trading" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// get the fromItem
|
||||
sItemBase *fromItem;
|
||||
switch ((SlotType)itemmove->eFrom) {
|
||||
case SlotType::EQUIP:
|
||||
if (itemmove->iFromSlotNum >= AEQUIP_COUNT)
|
||||
return;
|
||||
|
||||
fromItem = &plr->Equip[itemmove->iFromSlotNum];
|
||||
break;
|
||||
case SlotType::INVENTORY:
|
||||
if (itemmove->iFromSlotNum >= AINVEN_COUNT)
|
||||
return;
|
||||
|
||||
fromItem = &plr->Inven[itemmove->iFromSlotNum];
|
||||
break;
|
||||
case SlotType::BANK:
|
||||
if (itemmove->iFromSlotNum >= ABANK_COUNT)
|
||||
return;
|
||||
|
||||
fromItem = &plr->Bank[itemmove->iFromSlotNum];
|
||||
break;
|
||||
default:
|
||||
std::cout << "[WARN] MoveItem submitted unknown Item Type?! " << itemmove->eFrom << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// get the toItem
|
||||
sItemBase* toItem;
|
||||
switch ((SlotType)itemmove->eTo) {
|
||||
case SlotType::EQUIP:
|
||||
if (itemmove->iToSlotNum >= AEQUIP_COUNT)
|
||||
return;
|
||||
|
||||
toItem = &plr->Equip[itemmove->iToSlotNum];
|
||||
break;
|
||||
case SlotType::INVENTORY:
|
||||
if (itemmove->iToSlotNum >= AINVEN_COUNT)
|
||||
return;
|
||||
|
||||
toItem = &plr->Inven[itemmove->iToSlotNum];
|
||||
break;
|
||||
case SlotType::BANK:
|
||||
if (itemmove->iToSlotNum >= ABANK_COUNT)
|
||||
return;
|
||||
|
||||
toItem = &plr->Bank[itemmove->iToSlotNum];
|
||||
break;
|
||||
default:
|
||||
std::cout << "[WARN] MoveItem submitted unknown Item Type?! " << itemmove->eTo << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// if equipping an item, validate that it's of the correct type for the slot
|
||||
if ((SlotType)itemmove->eTo == SlotType::EQUIP) {
|
||||
if (fromItem->iType == 10 && itemmove->iToSlotNum != 8)
|
||||
return; // vehicle in wrong slot
|
||||
else if (fromItem->iType != 10
|
||||
&& !(fromItem->iType == 0 && itemmove->iToSlotNum == 7)
|
||||
&& fromItem->iType != itemmove->iToSlotNum)
|
||||
return; // something other than a vehicle or a weapon in a non-matching slot
|
||||
else if (itemmove->iToSlotNum >= AEQUIP_COUNT) // TODO: reject slots >= 9?
|
||||
return; // invalid slot
|
||||
}
|
||||
|
||||
// save items to response
|
||||
resp.eTo = itemmove->eFrom;
|
||||
resp.eFrom = itemmove->eTo;
|
||||
resp.ToSlotItem = *toItem;
|
||||
resp.FromSlotItem = *fromItem;
|
||||
|
||||
// swap/stack items in session
|
||||
Item* itemDat = getItemData(toItem->iID, toItem->iType);
|
||||
Item* itemDatFrom = getItemData(fromItem->iID, fromItem->iType);
|
||||
if (itemDat != nullptr && itemDatFrom != nullptr && itemDat->stackSize > 1 && itemDat == itemDatFrom && fromItem->iOpt < itemDat->stackSize && toItem->iOpt < itemDat->stackSize) {
|
||||
// items are stackable, identical, and not maxed, so run stacking logic
|
||||
|
||||
toItem->iOpt += fromItem->iOpt; // sum counts
|
||||
fromItem->iOpt = 0; // deplete from item
|
||||
if (toItem->iOpt > itemDat->stackSize) {
|
||||
// handle overflow
|
||||
fromItem->iOpt += (toItem->iOpt - itemDat->stackSize); // add overflow to fromItem
|
||||
toItem->iOpt = itemDat->stackSize; // set toItem count to max
|
||||
}
|
||||
|
||||
if (fromItem->iOpt == 0) { // from item count depleted
|
||||
// delete item
|
||||
fromItem->iID = 0;
|
||||
fromItem->iType = 0;
|
||||
fromItem->iTimeLimit = 0;
|
||||
}
|
||||
|
||||
resp.iFromSlotNum = itemmove->iFromSlotNum;
|
||||
resp.iToSlotNum = itemmove->iToSlotNum;
|
||||
resp.FromSlotItem = *fromItem;
|
||||
resp.ToSlotItem = *toItem;
|
||||
} else {
|
||||
// items not stackable; just swap them
|
||||
sItemBase temp = *toItem;
|
||||
*toItem = *fromItem;
|
||||
*fromItem = temp;
|
||||
resp.iFromSlotNum = itemmove->iToSlotNum;
|
||||
resp.iToSlotNum = itemmove->iFromSlotNum;
|
||||
}
|
||||
|
||||
// send equip change to viewable players
|
||||
if (itemmove->eFrom == (int)SlotType::EQUIP || itemmove->eTo == (int)SlotType::EQUIP) {
|
||||
INITSTRUCT(sP_FE2CL_PC_EQUIP_CHANGE, equipChange);
|
||||
|
||||
equipChange.iPC_ID = plr->iID;
|
||||
if (itemmove->eTo == (int)SlotType::EQUIP) {
|
||||
equipChange.iEquipSlotNum = itemmove->iToSlotNum;
|
||||
equipChange.EquipSlotItem = resp.FromSlotItem;
|
||||
} else {
|
||||
equipChange.iEquipSlotNum = itemmove->iFromSlotNum;
|
||||
equipChange.EquipSlotItem = resp.ToSlotItem;
|
||||
}
|
||||
|
||||
// unequip vehicle if equip slot 8 is 0
|
||||
if (plr->Equip[8].iID == 0 && plr->iPCState & 8) {
|
||||
INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response);
|
||||
sock->sendPacket(response, P_FE2CL_PC_VEHICLE_OFF_SUCC);
|
||||
|
||||
// send to other players
|
||||
plr->iPCState &= ~8;
|
||||
INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, response2);
|
||||
response2.iPC_ID = plr->iID;
|
||||
response2.iState = plr->iPCState;
|
||||
|
||||
PlayerManager::sendToViewable(sock, response2, P_FE2CL_PC_STATE_CHANGE);
|
||||
}
|
||||
|
||||
// send equip event to other players
|
||||
PlayerManager::sendToViewable(sock, equipChange, P_FE2CL_PC_EQUIP_CHANGE);
|
||||
|
||||
// set equipment stats serverside
|
||||
setItemStats(plr);
|
||||
}
|
||||
|
||||
// send response
|
||||
sock->sendPacket(resp, P_FE2CL_PC_ITEM_MOVE_SUCC);
|
||||
}
|
||||
|
||||
static void itemDeleteHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto itemdel = (sP_CL2FE_REQ_PC_ITEM_DELETE*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_DELETE_SUCC, resp);
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
resp.eIL = itemdel->eIL;
|
||||
resp.iSlotNum = itemdel->iSlotNum;
|
||||
|
||||
// so, im not sure what this eIL thing does since you always delete items in inventory and not equips
|
||||
plr->Inven[itemdel->iSlotNum].iID = 0;
|
||||
plr->Inven[itemdel->iSlotNum].iType = 0;
|
||||
plr->Inven[itemdel->iSlotNum].iOpt = 0;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_ITEM_DELETE_SUCC);
|
||||
}
|
||||
|
||||
static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto request = (sP_CL2FE_REQ_ITEM_USE*)data->buf;
|
||||
Player* player = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (request->iSlotNum < 0 || request->iSlotNum >= AINVEN_COUNT)
|
||||
return; // sanity check
|
||||
|
||||
// gumball can only be used from inventory, so we ignore eIL
|
||||
sItemBase gumball = player->Inven[request->iSlotNum];
|
||||
sNano nano = player->Nanos[player->equippedNanos[request->iNanoSlot]];
|
||||
|
||||
// sanity check, check if gumball exists
|
||||
if (!(gumball.iOpt > 0 && gumball.iType == 7 && gumball.iID>=119 && gumball.iID<=121)) {
|
||||
std::cout << "[WARN] Gumball not found" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, response);
|
||||
sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
// sanity check, check if gumball type matches nano style
|
||||
int nanoStyle = Nanos::nanoStyle(nano.iID);
|
||||
if (!((gumball.iID == 119 && nanoStyle == 0) ||
|
||||
( gumball.iID == 120 && nanoStyle == 1) ||
|
||||
( gumball.iID == 121 && nanoStyle == 2))) {
|
||||
std::cout << "[WARN] Gumball type doesn't match nano type" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, response);
|
||||
sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
gumball.iOpt -= 1;
|
||||
if (gumball.iOpt == 0)
|
||||
gumball = {};
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_REP_PC_ITEM_USE_SUCC) + sizeof(sSkillResult_Buff);
|
||||
|
||||
// validate response packet
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_REP_PC_ITEM_USE_SUCC), 1, sizeof(sSkillResult_Buff))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_REP_PC_ITEM_USE_SUCC packet size" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (gumball.iOpt == 0)
|
||||
gumball = {};
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_REP_PC_ITEM_USE_SUCC *resp = (sP_FE2CL_REP_PC_ITEM_USE_SUCC*)respbuf;
|
||||
sSkillResult_Buff *respdata = (sSkillResult_Buff*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC));
|
||||
resp->iPC_ID = player->iID;
|
||||
resp->eIL = 1;
|
||||
resp->iSlotNum = request->iSlotNum;
|
||||
resp->RemainItem = gumball;
|
||||
resp->iTargetCnt = 1;
|
||||
resp->eST = (int32_t)SkillType::NANOSTIMPAK;
|
||||
resp->iSkillID = 144;
|
||||
|
||||
int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
|
||||
|
||||
respdata->eCT = 1;
|
||||
respdata->iID = player->iID;
|
||||
respdata->iConditionBitFlag = CSB_FROM_ECSB(eCSB);
|
||||
|
||||
int durationMilliseconds = Abilities::SkillTable[144].durationTime[0] * 100;
|
||||
BuffStack gumballBuff = {
|
||||
durationMilliseconds / MS_PER_PLAYER_TICK,
|
||||
0,
|
||||
sock,
|
||||
BuffClass::CASH_ITEM // or BuffClass::ITEM?
|
||||
};
|
||||
player->addBuff(eCSB,
|
||||
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||
},
|
||||
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||
// no-op
|
||||
},
|
||||
&gumballBuff);
|
||||
|
||||
sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen);
|
||||
// update inventory serverside
|
||||
player->Inven[resp->iSlotNum] = resp->RemainItem;
|
||||
}
|
||||
|
||||
static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// just send bank inventory
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_BANK_OPEN_SUCC, resp);
|
||||
for (int i = 0; i < ABANK_COUNT; i++) {
|
||||
resp.aBank[i] = plr->Bank[i];
|
||||
}
|
||||
resp.iExtraBank = 1;
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_BANK_OPEN_SUCC);
|
||||
}
|
||||
|
||||
static void chestOpenHandler(CNSocket *sock, CNPacketData *data) {
|
||||
auto pkt = (sP_CL2FE_REQ_ITEM_CHEST_OPEN *)data->buf;
|
||||
|
||||
// sanity check
|
||||
if (pkt->eIL != 1 || pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT)
|
||||
return;
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
sItemBase *chest = &plr->Inven[pkt->iSlotNum];
|
||||
// we could reject the packet if the client thinks the item is different, but eh
|
||||
|
||||
if (chest->iType != 9) {
|
||||
std::cout << "[WARN] Player tried to open a crate with incorrect iType ?!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ACADEMY
|
||||
// check if chest isn't a nano capsule
|
||||
if (NanoCapsules.find(chest->iID) != NanoCapsules.end())
|
||||
return nanoCapsuleHandler(sock, pkt->iSlotNum, chest);
|
||||
#endif
|
||||
|
||||
// chest opening acknowledgement packet
|
||||
INITSTRUCT(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, resp);
|
||||
resp.iSlotNum = pkt->iSlotNum;
|
||||
|
||||
// item giving packet
|
||||
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||
// we know it's only one trailing struct, so we can skip full validation
|
||||
|
||||
uint8_t respbuf[resplen]; // not a variable length array, don't worry
|
||||
sP_FE2CL_REP_REWARD_ITEM *reward = (sP_FE2CL_REP_REWARD_ITEM *)respbuf;
|
||||
sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
|
||||
|
||||
// don't forget to zero the buffer!
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
// maintain stats
|
||||
reward->m_iCandy = plr->money;
|
||||
reward->m_iFusionMatter = plr->fusionmatter;
|
||||
reward->iFatigue = 100; // prevents warning message
|
||||
reward->iFatigue_Level = 1;
|
||||
reward->iItemCnt = 1; // remember to update resplen if you change this
|
||||
reward->m_iBatteryN = plr->batteryN;
|
||||
reward->m_iBatteryW = plr->batteryW;
|
||||
|
||||
item->iSlotNum = pkt->iSlotNum;
|
||||
item->eIL = 1;
|
||||
|
||||
int validItemSetId = -1, rarity = -1, ret = -1;
|
||||
|
||||
int validCrateId = getValidCrateId(chest->iID);
|
||||
bool failing = (validCrateId == -1);
|
||||
|
||||
if (!failing)
|
||||
validItemSetId = getValidItemSetId(validCrateId);
|
||||
failing = (validItemSetId == -1);
|
||||
|
||||
if (!failing)
|
||||
rarity = getRarity(validCrateId, validItemSetId);
|
||||
failing = (rarity == -1);
|
||||
|
||||
if (!failing)
|
||||
ret = getCrateItem(&item->sItem, validItemSetId, rarity, plr->PCStyle.iGender);
|
||||
failing = (ret == -1);
|
||||
|
||||
// if we failed to open a crate, at least give the player a gumball (suggested by Jade)
|
||||
if (failing) {
|
||||
item->sItem.iType = 7;
|
||||
item->sItem.iID = 119 + Rand::rand(3);
|
||||
item->sItem.iOpt = 1;
|
||||
|
||||
std::cout << "[WARN] Crate open failed, giving a Gumball..." << std::endl;
|
||||
}
|
||||
// update player
|
||||
plr->Inven[pkt->iSlotNum] = item->sItem;
|
||||
|
||||
// transmit item
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
||||
|
||||
// transmit chest opening acknowledgement packet
|
||||
std::cout << "opening chest..." << std::endl;
|
||||
sock->sendPacket(resp, P_FE2CL_REP_ITEM_CHEST_OPEN_SUCC);
|
||||
}
|
||||
|
||||
// TODO: use this in cleaned up Items
|
||||
int Items::findFreeSlot(Player *plr) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < AINVEN_COUNT; i++)
|
||||
if (plr->Inven[i].iType == 0 && plr->Inven[i].iID == 0 && plr->Inven[i].iOpt == 0)
|
||||
return i;
|
||||
|
||||
// not found
|
||||
return -1;
|
||||
}
|
||||
|
||||
Item* Items::getItemData(int32_t id, int32_t type) {
|
||||
if(ItemData.find(std::make_pair(id, type)) != ItemData.end())
|
||||
return &ItemData[std::make_pair(id, type)];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Items::checkItemExpire(CNSocket* sock, Player* player) {
|
||||
if (player->toRemoveVehicle.eIL == 0 && player->toRemoveVehicle.iSlotNum == 0)
|
||||
return;
|
||||
|
||||
/* prepare packet
|
||||
* yes, this is a varadic packet, however analyzing client behavior and code
|
||||
* it only checks takes the first item sent into account
|
||||
* yes, this is very stupid
|
||||
* therefore, we delete all but 1 expired vehicle while loading player
|
||||
* to delete the last one here so player gets a notification
|
||||
*/
|
||||
|
||||
const size_t resplen = sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||
// we know it's only one trailing struct, so we can skip full validation
|
||||
uint8_t respbuf[resplen]; // not a variable length array, don't worry
|
||||
auto packet = (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM*)respbuf;
|
||||
sTimeLimitItemDeleteInfo2CL* itemData = (sTimeLimitItemDeleteInfo2CL*)(respbuf + sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM));
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
packet->iItemListCount = 1;
|
||||
itemData->eIL = player->toRemoveVehicle.eIL;
|
||||
itemData->iSlotNum = player->toRemoveVehicle.iSlotNum;
|
||||
sock->sendPacket((void*)&respbuf, P_FE2CL_PC_DELETE_TIME_LIMIT_ITEM, resplen);
|
||||
|
||||
// delete serverside
|
||||
if (player->toRemoveVehicle.eIL == 0)
|
||||
memset(&player->Equip[8], 0, sizeof(sItemBase));
|
||||
else
|
||||
memset(&player->Inven[player->toRemoveVehicle.iSlotNum], 0, sizeof(sItemBase));
|
||||
|
||||
player->toRemoveVehicle.eIL = 0;
|
||||
player->toRemoveVehicle.iSlotNum = 0;
|
||||
}
|
||||
|
||||
void Items::setItemStats(Player* plr) {
|
||||
|
||||
plr->pointDamage = 8 + plr->level * 2;
|
||||
plr->groupDamage = 8 + plr->level * 2;
|
||||
plr->fireRate = 0;
|
||||
plr->defense = 16 + plr->level * 4;
|
||||
|
||||
Item* itemStatsDat;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
itemStatsDat = getItemData(plr->Equip[i].iID, plr->Equip[i].iType);
|
||||
if (itemStatsDat == nullptr) {
|
||||
std::cout << "[WARN] setItemStats(): getItemData() returned NULL" << std::endl;
|
||||
continue;
|
||||
}
|
||||
plr->pointDamage += itemStatsDat->pointDamage;
|
||||
plr->groupDamage += itemStatsDat->groupDamage;
|
||||
plr->fireRate += itemStatsDat->fireRate;
|
||||
plr->defense += itemStatsDat->defense;
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: work around the invisible weapon bug
|
||||
// TODO: I don't think this makes a difference at all? Check and remove, if necessary.
|
||||
void Items::updateEquips(CNSocket* sock, Player* plr) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
INITSTRUCT(sP_FE2CL_PC_EQUIP_CHANGE, resp);
|
||||
|
||||
resp.iPC_ID = plr->iID;
|
||||
resp.iEquipSlotNum = i;
|
||||
resp.EquipSlotItem = plr->Equip[i];
|
||||
|
||||
PlayerManager::sendToViewable(sock, resp, P_FE2CL_PC_EQUIP_CHANGE);
|
||||
}
|
||||
}
|
||||
|
||||
static void getMobDrop(sItemBase* reward, const std::vector<int>& weights, const std::vector<int>& crateIds, int rolled) {
|
||||
int chosenIndex = choice(weights, rolled);
|
||||
|
||||
reward->iType = 9;
|
||||
reward->iOpt = 1;
|
||||
reward->iID = crateIds[chosenIndex];
|
||||
}
|
||||
|
||||
static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRoll& rolled) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||
// we know it's only one trailing struct, so we can skip full validation
|
||||
|
||||
uint8_t respbuf[resplen]; // not a variable length array, don't worry
|
||||
sP_FE2CL_REP_REWARD_ITEM *reward = (sP_FE2CL_REP_REWARD_ITEM *)respbuf;
|
||||
sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
|
||||
|
||||
// don't forget to zero the buffer!
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
// sanity check
|
||||
if (Items::MobDrops.find(mobDropId) == Items::MobDrops.end()) {
|
||||
std::cout << "[WARN] Drop Type " << mobDropId << " was not found" << std::endl;
|
||||
return;
|
||||
}
|
||||
// find correct mob drop
|
||||
MobDrop& drop = Items::MobDrops[mobDropId];
|
||||
|
||||
// use the keys to fetch data from other maps
|
||||
// sanity check
|
||||
if (Items::CrateDropChances.find(drop.crateDropChanceId) == Items::CrateDropChances.end()) {
|
||||
std::cout << "[WARN] Crate Drop Chance Object " << drop.crateDropChanceId << " was not found" << std::endl;
|
||||
return;
|
||||
}
|
||||
CrateDropChance& crateDropChance = Items::CrateDropChances[drop.crateDropChanceId];
|
||||
|
||||
// sanity check
|
||||
if (Items::CrateDropTypes.find(drop.crateDropTypeId) == Items::CrateDropTypes.end()) {
|
||||
std::cout << "[WARN] Crate Drop Type Object " << drop.crateDropTypeId << " was not found" << std::endl;
|
||||
return;
|
||||
}
|
||||
std::vector<int>& crateDropType = Items::CrateDropTypes[drop.crateDropTypeId];
|
||||
|
||||
// sanity check
|
||||
if (Items::MiscDropChances.find(drop.miscDropChanceId) == Items::MiscDropChances.end()) {
|
||||
std::cout << "[WARN] Misc Drop Chance Object " << drop.miscDropChanceId << " was not found" << std::endl;
|
||||
return;
|
||||
}
|
||||
MiscDropChance& miscDropChance = Items::MiscDropChances[drop.miscDropChanceId];
|
||||
|
||||
// sanity check
|
||||
if (Items::MiscDropTypes.find(drop.miscDropTypeId) == Items::MiscDropTypes.end()) {
|
||||
std::cout << "[WARN] Misc Drop Type Object " << drop.miscDropTypeId << " was not found" << std::endl;
|
||||
return;
|
||||
}
|
||||
MiscDropType& miscDropType = Items::MiscDropTypes[drop.miscDropTypeId];
|
||||
|
||||
if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) {
|
||||
plr->money += miscDropType.taroAmount;
|
||||
// money nano boost
|
||||
if (plr->hasBuff(ECSB_REWARD_CASH)) {
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||
boost = 1;
|
||||
plr->money += miscDropType.taroAmount * (5 + boost) / 25;
|
||||
}
|
||||
}
|
||||
if (rolled.fm % miscDropChance.fmDropChanceTotal < miscDropChance.fmDropChance) {
|
||||
// formula for scaling FM with player/mob level difference
|
||||
// TODO: adjust this better
|
||||
int levelDifference = plr->level - mob->level;
|
||||
int fm = miscDropType.fmAmount;
|
||||
if (levelDifference > 0)
|
||||
fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0;
|
||||
// scavenger nano boost
|
||||
if (plr->hasBuff(ECSB_REWARD_BLOB)) {
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||
boost = 1;
|
||||
fm += fm * (5 + boost) / 25;
|
||||
}
|
||||
|
||||
Missions::updateFusionMatter(sock, fm);
|
||||
}
|
||||
|
||||
if (rolled.potions % miscDropChance.potionDropChanceTotal < miscDropChance.potionDropChance)
|
||||
plr->batteryN += miscDropType.potionAmount;
|
||||
if (rolled.boosts % miscDropChance.boostDropChanceTotal < miscDropChance.boostDropChance)
|
||||
plr->batteryW += miscDropType.boostAmount;
|
||||
|
||||
// caps
|
||||
if (plr->batteryW > 9999)
|
||||
plr->batteryW = 9999;
|
||||
if (plr->batteryN > 9999)
|
||||
plr->batteryN = 9999;
|
||||
|
||||
// simple rewards
|
||||
reward->m_iCandy = plr->money;
|
||||
reward->m_iFusionMatter = plr->fusionmatter;
|
||||
reward->m_iBatteryN = plr->batteryN;
|
||||
reward->m_iBatteryW = plr->batteryW;
|
||||
reward->iFatigue = 100; // prevents warning message
|
||||
reward->iFatigue_Level = 1;
|
||||
reward->iItemCnt = 1; // remember to update resplen if you change this
|
||||
|
||||
int slot = findFreeSlot(plr);
|
||||
|
||||
// no drop
|
||||
if (slot == -1 || rolled.crate % crateDropChance.dropChanceTotal >= crateDropChance.dropChance) {
|
||||
// no room for an item, but you still get FM and taros
|
||||
reward->iItemCnt = 0;
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, sizeof(sP_FE2CL_REP_REWARD_ITEM));
|
||||
} else {
|
||||
// item reward
|
||||
getMobDrop(&item->sItem, crateDropChance.crateTypeDropWeights, crateDropType, rolled.crateType);
|
||||
item->iSlotNum = slot;
|
||||
item->eIL = 1; // Inventory Location. 1 means player inventory.
|
||||
|
||||
// update player
|
||||
plr->Inven[slot] = item->sItem;
|
||||
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
||||
}
|
||||
}
|
||||
|
||||
void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) {
|
||||
// sanity check
|
||||
if (Items::MobToDropMap.find(mob->type) == Items::MobToDropMap.end()) {
|
||||
std::cout << "[WARN] Mob ID " << mob->type << " has no drops assigned" << std::endl;
|
||||
return;
|
||||
}
|
||||
// find mob drop id
|
||||
int mobDropId = Items::MobToDropMap[mob->type];
|
||||
|
||||
giveSingleDrop(sock, mob, mobDropId, rolled);
|
||||
|
||||
if (settings::EVENTMODE != 0) {
|
||||
// sanity check
|
||||
if (Items::EventToDropMap.find(settings::EVENTMODE) == Items::EventToDropMap.end()) {
|
||||
std::cout << "[WARN] Event " << settings::EVENTMODE << " has no mob drop assigned" << std::endl;
|
||||
return;
|
||||
}
|
||||
// find mob drop id
|
||||
int eventMobDropId = Items::EventToDropMap[settings::EVENTMODE];
|
||||
|
||||
giveSingleDrop(sock, mob, eventMobDropId, eventRolled);
|
||||
}
|
||||
}
|
||||
|
||||
void Items::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_MOVE, itemMoveHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_DELETE, itemDeleteHandler);
|
||||
// this one is for gumballs
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_USE, itemUseHandler);
|
||||
// Bank
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_BANK_OPEN, itemBankOpenHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_CHEST_OPEN, chestOpenHandler);
|
||||
}
|
||||
127
src/Items.hpp
Normal file
127
src/Items.hpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "Rand.hpp"
|
||||
#include "MobAI.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
struct CrocPotEntry {
|
||||
int multStats, multLooks;
|
||||
float base, rd0, rd1, rd2, rd3;
|
||||
};
|
||||
|
||||
struct Crate {
|
||||
int itemSetId;
|
||||
int rarityWeightId;
|
||||
};
|
||||
|
||||
struct CrateDropChance {
|
||||
int dropChance, dropChanceTotal;
|
||||
std::vector<int> crateTypeDropWeights;
|
||||
};
|
||||
|
||||
struct MiscDropChance {
|
||||
int potionDropChance, potionDropChanceTotal;
|
||||
int boostDropChance, boostDropChanceTotal;
|
||||
int taroDropChance, taroDropChanceTotal;
|
||||
int fmDropChance, fmDropChanceTotal;
|
||||
};
|
||||
|
||||
struct MiscDropType {
|
||||
int potionAmount;
|
||||
int boostAmount;
|
||||
int taroAmount;
|
||||
int fmAmount;
|
||||
};
|
||||
|
||||
struct MobDrop {
|
||||
int crateDropChanceId;
|
||||
int crateDropTypeId;
|
||||
int miscDropChanceId;
|
||||
int miscDropTypeId;
|
||||
};
|
||||
|
||||
struct ItemSet {
|
||||
// itemset-wise offswitch to rarity filtering, every crate drops every rarity (still based on rarity weights)
|
||||
bool ignoreRarity;
|
||||
// itemset-wise offswitch for gender filtering, every crate can now drop neutral/boys/girls items
|
||||
bool ignoreGender;
|
||||
// default weight of all items in the itemset
|
||||
int defaultItemWeight;
|
||||
// change the rarity class of items in the itemset here
|
||||
// rarity 0 bypasses the rarity filter for an individual item
|
||||
std::map<int, int> alterRarityMap;
|
||||
// change the gender class of items in the itemset here
|
||||
// gender 0 bypasses the gender filter for an individual item
|
||||
std::map<int, int> alterGenderMap;
|
||||
// change the item weghts items in the itemset here
|
||||
// only taken into account for chosen rarity, and if the item isn't filtered away due to gender
|
||||
std::map<int, int> alterItemWeightMap;
|
||||
std::vector<int> itemReferenceIds;
|
||||
};
|
||||
|
||||
struct ItemReference {
|
||||
int itemId;
|
||||
int type;
|
||||
int rarity;
|
||||
int gender;
|
||||
};
|
||||
|
||||
namespace Items {
|
||||
enum class SlotType {
|
||||
EQUIP = 0,
|
||||
INVENTORY = 1,
|
||||
BANK = 3
|
||||
};
|
||||
struct Item {
|
||||
bool tradeable, sellable;
|
||||
int buyPrice, sellPrice;
|
||||
int stackSize, level, rarity;
|
||||
int pointDamage, groupDamage, fireRate, defense, gender;
|
||||
int weaponType;
|
||||
// TODO: implement more as needed
|
||||
};
|
||||
struct DropRoll {
|
||||
int boosts, potions;
|
||||
int taros, fm;
|
||||
int crate, crateType;
|
||||
|
||||
DropRoll() : boosts(Rand::rand()), potions(Rand::rand()), taros(Rand::rand()), fm(Rand::rand()), crate(Rand::rand()), crateType(Rand::rand()) { }
|
||||
};
|
||||
// hopefully this is fine since it's never modified after load
|
||||
extern std::map<std::pair<int32_t, int32_t>, Item> ItemData; // <id, type> -> data
|
||||
extern std::map<int32_t, CrocPotEntry> CrocPotTable; // level gap -> entry
|
||||
extern std::map<int32_t, std::vector<int32_t>> RarityWeights;
|
||||
extern std::map<int32_t, Crate> Crates;
|
||||
extern std::map<int32_t, ItemReference> ItemReferences;
|
||||
extern std::map<std::string, std::vector<std::pair<int32_t, int32_t>>> CodeItems; // code -> vector of <id, type>
|
||||
|
||||
// mob drops
|
||||
extern std::map<int32_t, CrateDropChance> CrateDropChances;
|
||||
extern std::map<int32_t, std::vector<int32_t>> CrateDropTypes;
|
||||
extern std::map<int32_t, MiscDropChance> MiscDropChances;
|
||||
extern std::map<int32_t, MiscDropType> MiscDropTypes;
|
||||
extern std::map<int32_t, MobDrop> MobDrops;
|
||||
extern std::map<int32_t, int32_t> EventToDropMap;
|
||||
extern std::map<int32_t, int32_t> MobToDropMap;
|
||||
extern std::map<int32_t, ItemSet> ItemSets;
|
||||
|
||||
void init();
|
||||
|
||||
// mob drops
|
||||
void giveMobDrop(CNSocket *sock, Mob *mob, const DropRoll& rolled, const DropRoll& eventRolled);
|
||||
|
||||
int findFreeSlot(Player *plr);
|
||||
Item* getItemData(int32_t id, int32_t type);
|
||||
void checkItemExpire(CNSocket* sock, Player* player);
|
||||
void setItemStats(Player* plr);
|
||||
void updateEquips(CNSocket* sock, Player* plr);
|
||||
|
||||
#ifdef ACADEMY
|
||||
extern std::map<int32_t, int32_t> NanoCapsules; // crate id -> nano id
|
||||
#endif
|
||||
}
|
||||
@@ -1,282 +1,37 @@
|
||||
#include "CNShardServer.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
#include "Missions.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
#include "ItemManager.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Nanos.hpp"
|
||||
|
||||
#include "string.h"
|
||||
using namespace Missions;
|
||||
|
||||
std::map<int32_t, Reward*> MissionManager::Rewards;
|
||||
std::map<int32_t, TaskData*> MissionManager::Tasks;
|
||||
nlohmann::json MissionManager::AvatarGrowth[37];
|
||||
std::map<int32_t, Reward*> Missions::Rewards;
|
||||
std::map<int32_t, TaskData*> Missions::Tasks;
|
||||
nlohmann::json Missions::AvatarGrowth[37];
|
||||
|
||||
void MissionManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_START, taskStart);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_END, taskEnd);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID, setMission);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_STOP, quitMission);
|
||||
}
|
||||
|
||||
bool startTask(Player* plr, int TaskID, bool NanoMission) {
|
||||
if (MissionManager::Tasks.find(TaskID) == MissionManager::Tasks.end()) {
|
||||
std::cout << "[WARN] Player submitted unknown task!?" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// client freaks out if nano mission isn't sent first after reloging, so it's easiest to set it here
|
||||
if (NanoMission && plr->tasks[0] != 0) {
|
||||
// lets move task0 to different spot
|
||||
int moveToSlot = 1;
|
||||
for (; moveToSlot < ACTIVE_MISSION_COUNT; moveToSlot++)
|
||||
if (plr->tasks[moveToSlot] == 0)
|
||||
break;
|
||||
|
||||
plr->tasks[moveToSlot] = plr->tasks[0];
|
||||
plr->tasks[0] = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
plr->RemainingNPCCount[moveToSlot][i] = plr->RemainingNPCCount[0][i];
|
||||
plr->RemainingNPCCount[0][i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
TaskData& task = *MissionManager::Tasks[TaskID];
|
||||
int i;
|
||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == 0) {
|
||||
plr->tasks[i] = TaskID;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
plr->RemainingNPCCount[i][j] = (int)task["m_iCSUNumToKill"][j];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != TaskID) {
|
||||
std::cout << "[WARN] Player has more than 6 active missions!?" << std::endl;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MissionManager::taskStart(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_TASK_START))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_TASK_START* missionData = (sP_CL2FE_REQ_PC_TASK_START*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_START_SUCC, response);
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
if (!startTask(plr, missionData->iTaskNum, false)) {
|
||||
// TODO: TASK_FAIL?
|
||||
response.iTaskNum = missionData->iTaskNum;
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
||||
static void saveMission(Player* player, int missionId) {
|
||||
// sanity check missionID so we don't get exceptions
|
||||
if (missionId < 0 || missionId > 1023) {
|
||||
std::cout << "[WARN] Client submitted invalid missionId: " <<missionId<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
TaskData& task = *Tasks[missionData->iTaskNum];
|
||||
|
||||
response.iTaskNum = missionData->iTaskNum;
|
||||
response.iRemainTime = task["m_iSTGrantTimer"];
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
||||
|
||||
// HACK: auto-succeed escort task
|
||||
if (task["m_iHTaskType"] == 6) {
|
||||
std::cout << "Sending Eduardo success packet" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, response);
|
||||
|
||||
endTask(sock, missionData->iTaskNum);
|
||||
response.iTaskNum = missionData->iTaskNum;
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC));
|
||||
}
|
||||
|
||||
// Give player their delivery items at the start.
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (task["m_iSTItemID"][i] != 0 && task["m_iSTItemNumNeeded"][i] > 0)
|
||||
dropQuestItem(sock, missionData->iTaskNum, task["m_iSTItemNumNeeded"][i], task["m_iSTItemID"][i], 0);
|
||||
// Missions are stored in int64_t array
|
||||
int row = missionId / 64;
|
||||
int column = missionId % 64;
|
||||
player->aQuestFlag[row] |= (1ULL << column);
|
||||
}
|
||||
|
||||
void MissionManager::taskEnd(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_TASK_END))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_TASK_END* missionData = (sP_CL2FE_REQ_PC_TASK_END*)data->buf;
|
||||
|
||||
// failed timed missions give an iNPC_ID of 0
|
||||
if (missionData->iNPC_ID == 0) {
|
||||
TaskData* task = MissionManager::Tasks[missionData->iTaskNum];
|
||||
// double-checking
|
||||
if (task->task["m_iHTaskType"] == 3) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
int failTaskID = task->task["m_iFOutgoingTask"];
|
||||
if (failTaskID != 0) {
|
||||
MissionManager::quitTask(sock, missionData->iTaskNum, false);
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
if (plr->tasks[i] == missionData->iTaskNum)
|
||||
plr->tasks[i] = failTaskID;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, response);
|
||||
|
||||
response.iTaskNum = missionData->iTaskNum;
|
||||
|
||||
if (!endTask(sock, missionData->iTaskNum)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC));
|
||||
static bool isMissionCompleted(Player* player, int missionId) {
|
||||
int row = missionId / 64;
|
||||
int column = missionId % 64;
|
||||
return player->aQuestFlag[row] & (1ULL << column);
|
||||
}
|
||||
|
||||
bool MissionManager::endTask(CNSocket *sock, int32_t taskNum) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return false;
|
||||
|
||||
if (Tasks.find(taskNum) == Tasks.end())
|
||||
return false;
|
||||
|
||||
// ugly pointer/reference juggling for the sake of operator overloading...
|
||||
TaskData& task = *Tasks[taskNum];
|
||||
|
||||
// mission rewards
|
||||
if (Rewards.find(taskNum) != Rewards.end()) {
|
||||
if (giveMissionReward(sock, taskNum) == -1)
|
||||
return false; // we don't want to send anything
|
||||
}
|
||||
// don't take away quest items if we haven't finished the quest
|
||||
|
||||
/*
|
||||
* Give (or take away) quest items
|
||||
*
|
||||
* Some mission tasks give the player a quest item upon completion.
|
||||
* This is distinct from quest item mob drops.
|
||||
* They can be identified by a counter in the task indicator (ie. 1/1 Gravity Decelerator).
|
||||
* The server is responsible for dropping the correct item.
|
||||
* Yes, this is pretty stupid.
|
||||
*
|
||||
* iSUInstancename is the number of items to give. It is usually negative at the end of
|
||||
* a mission, to clean up its quest items.
|
||||
*/
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (task["m_iSUItem"][i] != 0)
|
||||
dropQuestItem(sock, taskNum, task["m_iSUInstancename"][i], task["m_iSUItem"][i], 0);
|
||||
|
||||
// update player
|
||||
int i;
|
||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == taskNum) {
|
||||
plr->tasks[i] = 0;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
plr->RemainingNPCCount[i][j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) {
|
||||
std::cout << "[WARN] Player completed non-active mission!?" << std::endl;
|
||||
}
|
||||
|
||||
// if it's the last task
|
||||
if (task["m_iSUOutgoingTask"] == 0) {
|
||||
// save completed mission on player
|
||||
saveMission(plr, (int)(task["m_iHMissionID"])-1);
|
||||
|
||||
// if it's a nano mission, reward the nano.
|
||||
if (task["m_iSTNanoID"] != 0)
|
||||
NanoManager::addNano(sock, task["m_iSTNanoID"], 0, true);
|
||||
|
||||
// remove current mission
|
||||
plr->CurrentMissionID = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MissionManager::setMission(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID))
|
||||
return; // malformed packet
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID* missionData = (sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, response);
|
||||
response.iCurrentMissionID = missionData->iCurrentMissionID;
|
||||
plr->CurrentMissionID = missionData->iCurrentMissionID;
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, sizeof(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID));
|
||||
}
|
||||
|
||||
void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_TASK_STOP))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_TASK_STOP* missionData = (sP_CL2FE_REQ_PC_TASK_STOP*)data->buf;
|
||||
quitTask(sock, missionData->iTaskNum, true);
|
||||
}
|
||||
|
||||
void MissionManager::quitTask(CNSocket* sock, int32_t taskNum, bool manual) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
// update player
|
||||
int i;
|
||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == taskNum) {
|
||||
plr->tasks[i] = 0;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
plr->RemainingNPCCount[i][j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) {
|
||||
std::cout << "[WARN] Player quit non-active mission!?" << std::endl;
|
||||
}
|
||||
// remove current mission
|
||||
plr->CurrentMissionID = 0;
|
||||
|
||||
TaskData& task = *Tasks[taskNum];
|
||||
|
||||
// clean up quest items
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (task["m_iSUItem"][i] == 0 && task["m_iCSUItemID"][i] == 0)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* It's ok to do this only server-side, because the server decides which
|
||||
* slot later items will be placed in.
|
||||
*/
|
||||
for (int j = 0; j < AQINVEN_COUNT; j++)
|
||||
if (plr->QInven[j].iID == task["m_iSUItem"][i] || plr->QInven[j].iID == task["m_iCSUItemID"][i])
|
||||
memset(&plr->QInven[j], 0, sizeof(sItemBase));
|
||||
}
|
||||
|
||||
if (!manual) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_FAIL, failResp);
|
||||
failResp.iErrorCode = 1;
|
||||
failResp.iTaskNum = taskNum;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_TASK_END_FAIL, sizeof(sP_FE2CL_REP_PC_TASK_END_FAIL));
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_STOP_SUCC, response);
|
||||
response.iTaskNum = taskNum;
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_STOP_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_STOP_SUCC));
|
||||
}
|
||||
|
||||
int MissionManager::findQSlot(Player *plr, int id) {
|
||||
int Missions::findQSlot(Player *plr, int id) {
|
||||
int i;
|
||||
|
||||
// two passes. we mustn't fail to find an existing stack.
|
||||
@@ -293,16 +48,27 @@ int MissionManager::findQSlot(Player *plr, int id) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
void MissionManager::dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid) {
|
||||
static bool isQuestItemFull(CNSocket* sock, int itemId, int itemCount) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
int slot = Missions::findQSlot(plr, itemId);
|
||||
if (slot == -1) {
|
||||
// this should never happen
|
||||
std::cout << "[WARN] Player has no room for quest item!?" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
return (itemCount == plr->QInven[slot].iOpt);
|
||||
}
|
||||
|
||||
static void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid) {
|
||||
std::cout << "Altered item id " << id << " by " << count << " for task id " << task << std::endl;
|
||||
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE);
|
||||
// we know it's only one trailing struct, so we can skip full validation
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
uint8_t respbuf[resplen]; // not a variable length array, don't worry
|
||||
sP_FE2CL_REP_REWARD_ITEM *reward = (sP_FE2CL_REP_REWARD_ITEM *)respbuf;
|
||||
sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
|
||||
@@ -311,7 +77,7 @@ void MissionManager::dropQuestItem(CNSocket *sock, int task, int count, int id,
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
// find free quest item slot
|
||||
int slot = findQSlot(plr, id);
|
||||
int slot = Missions::findQSlot(plr, id);
|
||||
if (slot == -1) {
|
||||
// this should never happen
|
||||
std::cout << "[WARN] Player has no room for quest item!?" << std::endl;
|
||||
@@ -350,22 +116,23 @@ void MissionManager::dropQuestItem(CNSocket *sock, int task, int count, int id,
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
||||
}
|
||||
|
||||
int MissionManager::giveMissionReward(CNSocket *sock, int task) {
|
||||
static int giveMissionReward(CNSocket *sock, int task, int choice=0) {
|
||||
Reward *reward = Rewards[task];
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return -1;
|
||||
|
||||
int nrewards = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (reward->itemIds[i] != 0)
|
||||
nrewards++;
|
||||
}
|
||||
|
||||
// this handles multiple choice rewards in the Academy's Mt. Neverest missions
|
||||
if (choice != 0)
|
||||
nrewards = 1;
|
||||
|
||||
int slots[4];
|
||||
for (int i = 0; i < nrewards; i++) {
|
||||
slots[i] = ItemManager::findFreeSlot(plr);
|
||||
slots[i] = Items::findFreeSlot(plr);
|
||||
if (slots[i] == -1) {
|
||||
std::cout << "Not enough room to complete task" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_FAIL, fail);
|
||||
@@ -374,8 +141,15 @@ int MissionManager::giveMissionReward(CNSocket *sock, int task) {
|
||||
fail.iErrorCode = 13; // inventory full
|
||||
|
||||
sock->sendPacket((void*)&fail, P_FE2CL_REP_PC_TASK_END_FAIL, sizeof(sP_FE2CL_REP_PC_TASK_END_FAIL));
|
||||
|
||||
// delete any temp items we might have set
|
||||
for (int j = 0; j < i; j++) {
|
||||
plr->Inven[slots[j]] = { 0, 0, 0, 0 }; // empty
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
plr->Inven[slots[i]] = { 999, 999, 999, 0 }; // temp item; overwritten later
|
||||
}
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
@@ -389,7 +163,20 @@ int MissionManager::giveMissionReward(CNSocket *sock, int task) {
|
||||
|
||||
// update player
|
||||
plr->money += reward->money;
|
||||
updateFusionMatter(sock, reward->fusionmatter);
|
||||
if (plr->hasBuff(ECSB_REWARD_CASH)) { // nano boost for taros
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||
boost = 1;
|
||||
plr->money += reward->money * (5 + boost) / 25;
|
||||
}
|
||||
|
||||
if (plr->hasBuff(ECSB_REWARD_BLOB)) { // nano boost for fm
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||
boost = 1;
|
||||
updateFusionMatter(sock, reward->fusionmatter * (30 + boost) / 25);
|
||||
} else
|
||||
updateFusionMatter(sock, reward->fusionmatter);
|
||||
|
||||
// simple rewards
|
||||
resp->m_iCandy = plr->money;
|
||||
@@ -400,14 +187,21 @@ int MissionManager::giveMissionReward(CNSocket *sock, int task) {
|
||||
resp->m_iBatteryN = plr->batteryN;
|
||||
resp->m_iBatteryW = plr->batteryW;
|
||||
|
||||
int offset = 0;
|
||||
|
||||
// choice is actually a bitfield
|
||||
if (choice != 0)
|
||||
offset = (int)log2((int)choice);
|
||||
|
||||
for (int i = 0; i < nrewards; i++) {
|
||||
item[i].sItem.iType = reward->itemTypes[i];
|
||||
item[i].sItem.iID = reward->itemIds[i];
|
||||
item[i].sItem.iType = reward->itemTypes[offset+i];
|
||||
item[i].sItem.iID = reward->itemIds[offset+i];
|
||||
item[i].sItem.iOpt = 1;
|
||||
item[i].iSlotNum = slots[i];
|
||||
item[i].eIL = 1;
|
||||
|
||||
// update player
|
||||
plr->Inven[slots[i]] = item->sItem;
|
||||
// update player inventory, overwriting temporary item
|
||||
plr->Inven[slots[i]] = item[i].sItem;
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
||||
@@ -415,28 +209,331 @@ int MissionManager::giveMissionReward(CNSocket *sock, int task) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MissionManager::updateFusionMatter(CNSocket* sock, int fusion) {
|
||||
static bool endTask(CNSocket *sock, int32_t taskNum, int choice=0) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
if (Tasks.find(taskNum) == Tasks.end())
|
||||
return false;
|
||||
|
||||
// ugly pointer/reference juggling for the sake of operator overloading...
|
||||
TaskData& task = *Tasks[taskNum];
|
||||
|
||||
// sanity check
|
||||
int i;
|
||||
bool found = false;
|
||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == taskNum) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return false;
|
||||
|
||||
// mission rewards
|
||||
if (Rewards.find(taskNum) != Rewards.end()) {
|
||||
if (giveMissionReward(sock, taskNum, choice) == -1)
|
||||
return false; // we don't want to send anything
|
||||
}
|
||||
// don't take away quest items if we haven't finished the quest
|
||||
|
||||
/*
|
||||
* Update player's active mission data.
|
||||
*
|
||||
* This must be done after all early returns have passed, otherwise we
|
||||
* risk introducing non-atomic changes. For example, failing to finish
|
||||
* a mission due to not having any inventory space could delete the
|
||||
* mission server-side; leading to a desync.
|
||||
*/
|
||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == taskNum) {
|
||||
plr->tasks[i] = 0;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
plr->RemainingNPCCount[i][j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Give (or take away) quest items
|
||||
*
|
||||
* Some mission tasks give the player a quest item upon completion.
|
||||
* This is distinct from quest item mob drops.
|
||||
* They can be identified by a counter in the task indicator (ie. 1/1 Gravity Decelerator).
|
||||
* The server is responsible for dropping the correct item.
|
||||
* Yes, this is pretty stupid.
|
||||
*
|
||||
* iSUInstancename is the number of items to give. It is usually negative at the end of
|
||||
* a mission, to clean up its quest items.
|
||||
*/
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (task["m_iSUItem"][i] != 0)
|
||||
dropQuestItem(sock, taskNum, task["m_iSUInstancename"][i], task["m_iSUItem"][i], 0);
|
||||
|
||||
// if it's the last task
|
||||
if (task["m_iSUOutgoingTask"] == 0) {
|
||||
// save completed mission on player
|
||||
saveMission(plr, (int)(task["m_iHMissionID"])-1);
|
||||
|
||||
// if it's a nano mission, reward the nano.
|
||||
if (task["m_iSTNanoID"] != 0)
|
||||
Nanos::addNano(sock, task["m_iSTNanoID"], 0, true);
|
||||
|
||||
// remove current mission
|
||||
plr->CurrentMissionID = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Missions::startTask(Player* plr, int TaskID) {
|
||||
if (Missions::Tasks.find(TaskID) == Missions::Tasks.end()) {
|
||||
std::cout << "[WARN] Player submitted unknown task!?" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
TaskData& task = *Missions::Tasks[TaskID];
|
||||
|
||||
if (task["m_iCTRReqLvMin"] > plr->level) {
|
||||
std::cout << "[WARN] Player tried to start a task above their level" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isMissionCompleted(plr, (int)(task["m_iHMissionID"]) - 1)) {
|
||||
std::cout << "[WARN] Player tried to start an already completed mission" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// client freaks out if nano mission isn't sent first after relogging, so it's easiest to set it here
|
||||
if (task["m_iSTNanoID"] != 0 && plr->tasks[0] != 0) {
|
||||
// lets move task0 to different spot
|
||||
int moveToSlot = 1;
|
||||
for (; moveToSlot < ACTIVE_MISSION_COUNT; moveToSlot++)
|
||||
if (plr->tasks[moveToSlot] == 0)
|
||||
break;
|
||||
|
||||
plr->tasks[moveToSlot] = plr->tasks[0];
|
||||
plr->tasks[0] = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
plr->RemainingNPCCount[moveToSlot][i] = plr->RemainingNPCCount[0][i];
|
||||
plr->RemainingNPCCount[0][i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == 0) {
|
||||
plr->tasks[i] = TaskID;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
plr->RemainingNPCCount[i][j] = (int)task["m_iCSUNumToKill"][j];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != TaskID) {
|
||||
std::cout << "[WARN] Player has more than 6 active missions!?" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void taskStart(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TASK_START* missionData = (sP_CL2FE_REQ_PC_TASK_START*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_START_SUCC, response);
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (!startTask(plr, missionData->iTaskNum)) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_START_FAIL, failresp);
|
||||
failresp.iTaskNum = missionData->iTaskNum;
|
||||
failresp.iErrorCode = 1; // unused in the client
|
||||
sock->sendPacket(failresp, P_FE2CL_REP_PC_TASK_START_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
TaskData& task = *Tasks[missionData->iTaskNum];
|
||||
|
||||
// Give player their delivery items at the start, or reset them to 0 at the start.
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (task["m_iSTItemID"][i] != 0)
|
||||
dropQuestItem(sock, missionData->iTaskNum, task["m_iSTItemNumNeeded"][i], task["m_iSTItemID"][i], 0);
|
||||
std::cout << "Mission requested task: " << missionData->iTaskNum << std::endl;
|
||||
response.iTaskNum = missionData->iTaskNum;
|
||||
response.iRemainTime = task["m_iSTGrantTimer"];
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
||||
|
||||
// if escort task, assign matching paths to all nearby NPCs
|
||||
if (task["m_iHTaskType"] == (int)eTaskTypeProperty::EscortDefence) {
|
||||
for (ChunkPos& chunkPos : Chunking::getChunksInMap(plr->instanceID)) { // check all NPCs in the instance
|
||||
Chunk* chunk = Chunking::chunks[chunkPos];
|
||||
for (EntityRef ref : chunk->entities) {
|
||||
if (ref.kind != EntityKind::PLAYER) {
|
||||
BaseNPC* npc = (BaseNPC*)ref.getEntity();
|
||||
NPCPath* path = Transport::findApplicablePath(npc->id, npc->type, missionData->iTaskNum);
|
||||
if (path != nullptr) {
|
||||
Transport::constructPathNPC(npc->id, path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void taskEnd(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TASK_END* missionData = (sP_CL2FE_REQ_PC_TASK_END*)data->buf;
|
||||
|
||||
TaskData* task = Missions::Tasks[missionData->iTaskNum];
|
||||
|
||||
// handle timed mission failure
|
||||
if (task->task["m_iSTGrantTimer"] > 0 && missionData->iNPC_ID == 0) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
/*
|
||||
* Enemy killing missions
|
||||
* this is gross and should be cleaned up later
|
||||
* once we comb over mission logic more throughly
|
||||
*/
|
||||
bool mobsAreKilled = false;
|
||||
if (task->task["m_iHTaskType"] == (int)eTaskTypeProperty::Defeat) {
|
||||
mobsAreKilled = true;
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == missionData->iTaskNum) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (plr->RemainingNPCCount[i][j] > 0) {
|
||||
mobsAreKilled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mobsAreKilled) {
|
||||
int failTaskID = task->task["m_iFOutgoingTask"];
|
||||
if (failTaskID != 0) {
|
||||
Missions::quitTask(sock, missionData->iTaskNum, false);
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
if (plr->tasks[i] == missionData->iTaskNum)
|
||||
plr->tasks[i] = failTaskID;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, response);
|
||||
|
||||
response.iTaskNum = missionData->iTaskNum;
|
||||
|
||||
if (!endTask(sock, missionData->iTaskNum, missionData->iBox1Choice)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC));
|
||||
}
|
||||
|
||||
static void setMission(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID* missionData = (sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, response);
|
||||
response.iCurrentMissionID = missionData->iCurrentMissionID;
|
||||
plr->CurrentMissionID = missionData->iCurrentMissionID;
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, sizeof(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID));
|
||||
}
|
||||
|
||||
static void quitMission(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TASK_STOP* missionData = (sP_CL2FE_REQ_PC_TASK_STOP*)data->buf;
|
||||
quitTask(sock, missionData->iTaskNum, true);
|
||||
}
|
||||
|
||||
void Missions::quitTask(CNSocket* sock, int32_t taskNum, bool manual) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (Tasks.find(taskNum) == Tasks.end())
|
||||
return; // sanity check
|
||||
|
||||
// update player
|
||||
int i;
|
||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == taskNum) {
|
||||
plr->tasks[i] = 0;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
plr->RemainingNPCCount[i][j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) {
|
||||
std::cout << "[WARN] Player quit non-active mission!?" << std::endl;
|
||||
}
|
||||
// remove current mission
|
||||
plr->CurrentMissionID = 0;
|
||||
|
||||
TaskData& task = *Tasks[taskNum];
|
||||
|
||||
// clean up quest items
|
||||
if (manual) {
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (task["m_iSUItem"][i] == 0 && task["m_iCSUItemID"][i] == 0)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* It's ok to do this only server-side, because the server decides which
|
||||
* slot later items will be placed in.
|
||||
*/
|
||||
for (int j = 0; j < AQINVEN_COUNT; j++)
|
||||
if (plr->QInven[j].iID == task["m_iSUItem"][i] || plr->QInven[j].iID == task["m_iCSUItemID"][i] || plr->QInven[j].iID == task["m_iSTItemID"][i])
|
||||
memset(&plr->QInven[j], 0, sizeof(sItemBase));
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (task["m_iFItemID"][i] == 0)
|
||||
continue;
|
||||
dropQuestItem(sock, taskNum, task["m_iFItemNumNeeded"][i], task["m_iFItemID"][i], 0);
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_FAIL, failResp);
|
||||
failResp.iErrorCode = 1;
|
||||
failResp.iTaskNum = taskNum;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_TASK_END_FAIL, sizeof(sP_FE2CL_REP_PC_TASK_END_FAIL));
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_STOP_SUCC, response);
|
||||
response.iTaskNum = taskNum;
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_STOP_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_STOP_SUCC));
|
||||
}
|
||||
|
||||
void Missions::updateFusionMatter(CNSocket* sock, int fusion) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
plr->fusionmatter += fusion;
|
||||
|
||||
// there's a much lower FM cap in the Future
|
||||
if (plr->fusionmatter > AvatarGrowth[plr->level]["m_iFMLimit"])
|
||||
plr->fusionmatter = AvatarGrowth[plr->level]["m_iFMLimit"];
|
||||
int fmCap = AvatarGrowth[plr->level]["m_iFMLimit"];
|
||||
if (plr->fusionmatter > fmCap)
|
||||
plr->fusionmatter = fmCap;
|
||||
else if (plr->fusionmatter < 0) // if somehow lowered too far
|
||||
plr->fusionmatter = 0;
|
||||
|
||||
// check if it is enough for the nano mission
|
||||
if (plr->fusionmatter <= AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"])
|
||||
// don't run nano mission logic at level 36
|
||||
if (plr->level >= 36)
|
||||
return;
|
||||
|
||||
// don't give the Blossom nano mission until the player's in the Past
|
||||
if (plr->level == 4 && plr->PCStyle2.iPayzoneFlag == 0)
|
||||
return;
|
||||
|
||||
// check if it is enough for the nano mission
|
||||
int fmNano = AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"];
|
||||
if (plr->fusionmatter < fmNano)
|
||||
return;
|
||||
|
||||
#ifndef ACADEMY
|
||||
// check if the nano task is already started
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
TaskData& task = *Tasks[plr->tasks[i]];
|
||||
@@ -445,19 +542,33 @@ void MissionManager::updateFusionMatter(CNSocket* sock, int fusion) {
|
||||
}
|
||||
|
||||
// start the nano mission
|
||||
startTask(plr, AvatarGrowth[plr->level]["m_iNanoQuestTaskID"], true);
|
||||
startTask(plr, AvatarGrowth[plr->level]["m_iNanoQuestTaskID"]);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_START_SUCC, response);
|
||||
response.iTaskNum = AvatarGrowth[plr->level]["m_iNanoQuestTaskID"];
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
||||
#else
|
||||
plr->fusionmatter -= (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"];
|
||||
plr->level++;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_LEVEL_SUCC, response);
|
||||
|
||||
response.iFusionMatter = plr->fusionmatter;
|
||||
response.iLevel = plr->level;
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_CHANGE_LEVEL_SUCC, sizeof(sP_FE2CL_REP_PC_CHANGE_LEVEL_SUCC));
|
||||
#endif
|
||||
|
||||
// play the beam animation for other players
|
||||
INITSTRUCT(sP_FE2CL_PC_EVENT, bcast);
|
||||
bcast.iEventID = 1; // beam effect
|
||||
bcast.iPC_ID = plr->iID;
|
||||
PlayerManager::sendToViewable(sock, (void*)&bcast, P_FE2CL_PC_EVENT, sizeof(sP_FE2CL_PC_EVENT));
|
||||
}
|
||||
|
||||
void MissionManager::mobKilled(CNSocket *sock, int mobid) {
|
||||
void Missions::mobKilled(CNSocket *sock, int mobid, std::map<int, int>& rolls) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
bool missionmob = false;
|
||||
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
@@ -474,19 +585,33 @@ void MissionManager::mobKilled(CNSocket *sock, int mobid) {
|
||||
// acknowledge killing of mission mob...
|
||||
if (task["m_iCSUNumToKill"][j] != 0) {
|
||||
missionmob = true;
|
||||
// sanity check
|
||||
if (plr->RemainingNPCCount[i][j] == 0) {
|
||||
std::cout << "[WARN] RemainingNPCCount tries to go below 0?!" << std::endl;
|
||||
} else {
|
||||
if (plr->RemainingNPCCount[i][j] > 0) {
|
||||
plr->RemainingNPCCount[i][j]--;
|
||||
}
|
||||
}
|
||||
|
||||
// drop quest item
|
||||
if (task["m_iCSUItemNumNeeded"][j] != 0 && !isQuestItemFull(sock, task["m_iCSUItemID"][j], task["m_iCSUItemNumNeeded"][j]) ) {
|
||||
bool drop = rand() % 100 < task["m_iSTItemDropRate"][j];
|
||||
bool drop = rolls[plr->tasks[i]] % 100 < task["m_iSTItemDropRate"][j];
|
||||
if (drop) {
|
||||
// XXX: are CSUItemID and CSTItemID the same?
|
||||
dropQuestItem(sock, plr->tasks[i], 1, task["m_iCSUItemID"][j], mobid);
|
||||
|
||||
/*
|
||||
* Workaround: The client has a bug where it only sends a TASK_END request
|
||||
* for the first task of multiple that met their quest item requirements
|
||||
* at the same time. We deal with this by sending TASK_END response packets
|
||||
* proactively and then silently ignoring the extra TASK_END requests it
|
||||
* sends afterwards.
|
||||
*/
|
||||
if (isQuestItemFull(sock, task["m_iCSUItemID"][j], task["m_iCSUItemNumNeeded"][j])) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, end);
|
||||
end.iTaskNum = plr->tasks[i];
|
||||
|
||||
if (!endTask(sock, plr->tasks[i]))
|
||||
continue;
|
||||
|
||||
sock->sendPacket(end, P_FE2CL_REP_PC_TASK_END_SUCC);
|
||||
}
|
||||
} else {
|
||||
// fail to drop (itemID == 0)
|
||||
dropQuestItem(sock, plr->tasks[i], 1, 0, mobid);
|
||||
@@ -506,50 +631,28 @@ void MissionManager::mobKilled(CNSocket *sock, int mobid) {
|
||||
}
|
||||
}
|
||||
|
||||
void MissionManager::saveMission(Player* player, int missionId) {
|
||||
// sanity check missionID so we don't get exceptions
|
||||
if (missionId < 0 || missionId>1023) {
|
||||
std::cout << "[WARN] Client submitted invalid missionId: " <<missionId<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Missions are stored in int64_t array
|
||||
int row = missionId / 64;
|
||||
int column = missionId % 64;
|
||||
player->aQuestFlag[row] |= (1ULL << column);
|
||||
}
|
||||
|
||||
bool MissionManager::isQuestItemFull(CNSocket* sock, int itemId, int itemCount) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return true;
|
||||
|
||||
int slot = findQSlot(plr, itemId);
|
||||
if (slot == -1) {
|
||||
// this should never happen
|
||||
std::cout << "[WARN] Player has no room for quest item!?" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
return (itemCount == plr->QInven[slot].iOpt);
|
||||
}
|
||||
|
||||
void MissionManager::failInstancedMissions(CNSocket* sock) {
|
||||
void Missions::failInstancedMissions(CNSocket* sock) {
|
||||
// loop through all tasks; if the required instance is being left, "fail" the task
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int taskNum = plr->tasks[i];
|
||||
if (MissionManager::Tasks.find(taskNum) == MissionManager::Tasks.end())
|
||||
if (Missions::Tasks.find(taskNum) == Missions::Tasks.end())
|
||||
continue; // sanity check
|
||||
|
||||
TaskData* task = MissionManager::Tasks[taskNum];
|
||||
TaskData* task = Missions::Tasks[taskNum];
|
||||
if (task->task["m_iRequireInstanceID"] != 0) { // mission is instanced
|
||||
int failTaskID = task->task["m_iFOutgoingTask"];
|
||||
if (failTaskID != 0) {
|
||||
MissionManager::quitTask(sock, taskNum, false);
|
||||
plr->tasks[i] = failTaskID;
|
||||
Missions::quitTask(sock, taskNum, false);
|
||||
//plr->tasks[i] = failTaskID; // this causes the client to freak out and send a dupe task
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Missions::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_START, taskStart);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_END, taskEnd);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID, setMission);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_STOP, quitMission);
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNShardServer.hpp"
|
||||
#include "core/Core.hpp"
|
||||
#include "JSON.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
|
||||
#include "contrib/JSON.hpp"
|
||||
#include <map>
|
||||
|
||||
struct Reward {
|
||||
int32_t id;
|
||||
@@ -35,28 +37,20 @@ struct TaskData {
|
||||
auto operator[](std::string s) { return task[s]; }
|
||||
};
|
||||
|
||||
namespace MissionManager {
|
||||
namespace Missions {
|
||||
extern std::map<int32_t, Reward*> Rewards;
|
||||
extern std::map<int32_t, TaskData*> Tasks;
|
||||
extern nlohmann::json AvatarGrowth[37];
|
||||
void init();
|
||||
|
||||
void taskStart(CNSocket* sock, CNPacketData* data);
|
||||
void taskEnd(CNSocket* sock, CNPacketData* data);
|
||||
void setMission(CNSocket* sock, CNPacketData* data);
|
||||
void quitMission(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
int findQSlot(Player *plr, int id);
|
||||
void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid);
|
||||
|
||||
bool startTask(Player* plr, int TaskID);
|
||||
|
||||
// checks if player doesn't have n/n quest items
|
||||
bool isQuestItemFull(CNSocket* sock, int itemId, int itemCount);
|
||||
int giveMissionReward(CNSocket *sock, int task);
|
||||
void updateFusionMatter(CNSocket* sock, int fusion);
|
||||
|
||||
void mobKilled(CNSocket *sock, int mobid);
|
||||
void mobKilled(CNSocket *sock, int mobid, std::map<int, int>& rolls);
|
||||
|
||||
bool endTask(CNSocket *sock, int32_t taskNum);
|
||||
void saveMission(Player* player, int missionId);
|
||||
void quitTask(CNSocket* sock, int32_t taskNum, bool manual);
|
||||
|
||||
void failInstancedMissions(CNSocket* sock);
|
||||
854
src/MobAI.cpp
Normal file
854
src/MobAI.cpp
Normal file
@@ -0,0 +1,854 @@
|
||||
#include "MobAI.hpp"
|
||||
|
||||
#include "Chunking.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "Entities.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Racing.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "Combat.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Rand.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <limits.h>
|
||||
|
||||
using namespace MobAI;
|
||||
|
||||
bool MobAI::simulateMobs = settings::SIMULATEMOBS;
|
||||
|
||||
void Mob::step(time_t currTime) {
|
||||
if (playersInView < 0)
|
||||
std::cout << "[WARN] Weird playerview value " << playersInView << std::endl;
|
||||
|
||||
// skip movement and combat if disabled or not in view
|
||||
if ((!MobAI::simulateMobs || playersInView == 0) && state != AIState::DEAD
|
||||
&& state != AIState::RETREAT)
|
||||
return;
|
||||
|
||||
// call superclass step
|
||||
CombatNPC::step(currTime);
|
||||
}
|
||||
|
||||
int Mob::takeDamage(EntityRef src, int amt) {
|
||||
|
||||
// cannot kill mobs multiple times; cannot harm retreating mobs
|
||||
if (state != AIState::ROAMING && state != AIState::COMBAT) {
|
||||
return 0; // no damage
|
||||
}
|
||||
|
||||
if (skillStyle >= 0)
|
||||
return 0; // don't hurt a mob casting corruption
|
||||
|
||||
if (state == AIState::ROAMING) {
|
||||
assert(target == nullptr && src.kind == EntityKind::PLAYER); // TODO: players only for now
|
||||
transition(AIState::COMBAT, src);
|
||||
|
||||
if (groupLeader != 0)
|
||||
MobAI::followToCombat(this);
|
||||
}
|
||||
|
||||
// wake up sleeping monster
|
||||
if (hasBuff(ECSB_MEZ)) {
|
||||
removeBuff(ECSB_MEZ);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||
pkt1.eCT = 2;
|
||||
pkt1.iID = id;
|
||||
pkt1.iConditionBitFlag = getCompositeCondition();
|
||||
NPCManager::sendToViewable(this, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||
}
|
||||
|
||||
// call superclass takeDamage
|
||||
return CombatNPC::takeDamage(src, amt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dynamic lerp; distinct from Transport::lerp(). This one doesn't care about height and
|
||||
* only returns the first step, since the rest will need to be recalculated anyway if chasing player.
|
||||
*/
|
||||
static std::pair<int,int> lerp(int x1, int y1, int x2, int y2, int speed) {
|
||||
std::pair<int,int> ret = {x1, y1};
|
||||
|
||||
if (speed == 0)
|
||||
return ret;
|
||||
|
||||
int distance = hypot(x1 - x2, y1 - y2);
|
||||
|
||||
if (distance > speed) {
|
||||
|
||||
int lerps = distance / speed;
|
||||
|
||||
// interpolate only the first point
|
||||
float frac = 1.0f / lerps;
|
||||
|
||||
ret.first = (x1 + (x2 - x1) * frac);
|
||||
ret.second = (y1 + (y2 - y1) * frac);
|
||||
} else {
|
||||
ret.first = x2;
|
||||
ret.second = y2;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MobAI::clearDebuff(Mob *mob) {
|
||||
mob->skillStyle = -1;
|
||||
mob->clearBuffs(false);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||
pkt1.eCT = 2;
|
||||
pkt1.iID = mob->id;
|
||||
pkt1.iConditionBitFlag = mob->getCompositeCondition();
|
||||
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||
}
|
||||
|
||||
void MobAI::followToCombat(Mob *mob) {
|
||||
if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->kind == EntityKind::MOB) {
|
||||
Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (leadMob->groupMember[i] == 0)
|
||||
break;
|
||||
|
||||
if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityKind::MOB) {
|
||||
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]];
|
||||
|
||||
if (followerMob->state != AIState::ROAMING) // only roaming mobs should transition to combat
|
||||
continue;
|
||||
|
||||
followerMob->transition(AIState::COMBAT, mob->target);
|
||||
}
|
||||
|
||||
if (leadMob->state != AIState::ROAMING)
|
||||
return;
|
||||
|
||||
leadMob->transition(AIState::COMBAT, mob->target);
|
||||
}
|
||||
}
|
||||
|
||||
void MobAI::groupRetreat(Mob *mob) {
|
||||
if (NPCManager::NPCs.find(mob->groupLeader) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupLeader]->kind != EntityKind::MOB)
|
||||
return;
|
||||
|
||||
Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (leadMob->groupMember[i] == 0)
|
||||
break;
|
||||
|
||||
if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityKind::MOB) {
|
||||
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]];
|
||||
|
||||
if (followerMob->state != AIState::COMBAT)
|
||||
continue;
|
||||
|
||||
followerMob->target = nullptr;
|
||||
followerMob->state = AIState::RETREAT;
|
||||
clearDebuff(followerMob);
|
||||
}
|
||||
|
||||
if (leadMob->state != AIState::COMBAT)
|
||||
return;
|
||||
|
||||
leadMob->target = nullptr;
|
||||
leadMob->state = AIState::RETREAT;
|
||||
clearDebuff(leadMob);
|
||||
}
|
||||
|
||||
/*
|
||||
* Aggro on nearby players.
|
||||
* Even if they're in range, we can't assume they're all in the same one chunk
|
||||
* as the mob, since it might be near a chunk boundary.
|
||||
*/
|
||||
bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
|
||||
CNSocket *closest = nullptr;
|
||||
int closestDistance = INT_MAX;
|
||||
|
||||
for (auto it = mob->viewableChunks.begin(); it != mob->viewableChunks.end(); it++) {
|
||||
Chunk* chunk = *it;
|
||||
for (const EntityRef& ref : chunk->entities) {
|
||||
// TODO: support targetting other CombatNPCs
|
||||
if (ref.kind != EntityKind::PLAYER)
|
||||
continue;
|
||||
|
||||
CNSocket *s = ref.sock;
|
||||
Player *plr = PlayerManager::getPlayer(s);
|
||||
|
||||
if (plr->HP <= 0 || plr->onMonkey)
|
||||
continue;
|
||||
|
||||
int mobRange = mob->sightRange;
|
||||
|
||||
if (plr->hasBuff(ECSB_UP_STEALTH)
|
||||
|| Racing::EPRaces.find(s) != Racing::EPRaces.end())
|
||||
mobRange /= 3;
|
||||
|
||||
// 0.33x - 1.66x the range
|
||||
int levelDifference = plr->level - mob->level;
|
||||
if (levelDifference > -10)
|
||||
mobRange = levelDifference < 10 ? mobRange - (levelDifference * mobRange / 15) : mobRange / 3;
|
||||
|
||||
if (mob->state != AIState::ROAMING && plr->inCombat) // freshly out of aggro mobs
|
||||
mobRange = mob->sightRange * 2; // should not be impacted by the above
|
||||
|
||||
if (plr->iSpecialState & (CN_SPECIAL_STATE_FLAG__INVISIBLE|CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
||||
mobRange = -1;
|
||||
|
||||
// height is relevant for aggro distance because of platforming
|
||||
int xyDistance = hypot(mob->x - plr->x, mob->y - plr->y);
|
||||
int distance = hypot(xyDistance, (mob->z - plr->z) * 2); // difference in Z counts twice
|
||||
|
||||
if (distance > mobRange || distance > closestDistance)
|
||||
continue;
|
||||
|
||||
// found a player
|
||||
closest = s;
|
||||
closestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if (closest != nullptr) {
|
||||
// found closest player. engage.
|
||||
mob->transition(AIState::COMBAT, closest);
|
||||
|
||||
if (mob->groupLeader != 0)
|
||||
followToCombat(mob);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, int mobStyle) {
|
||||
Player *plr = PlayerManager::getPlayer(mob->target);
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT) + targetData[0] * sizeof(sCAttackResult);
|
||||
|
||||
// validate response packet
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT), targetData[0], sizeof(sCAttackResult))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_NPC_SKILL_CORRUPTION_HIT packet size" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_NPC_SKILL_CORRUPTION_HIT *resp = (sP_FE2CL_NPC_SKILL_CORRUPTION_HIT*)respbuf;
|
||||
sCAttackResult *respdata = (sCAttackResult*)(respbuf+sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT));
|
||||
|
||||
resp->iNPC_ID = mob->id;
|
||||
resp->iSkillID = skillID;
|
||||
resp->iStyle = mobStyle;
|
||||
resp->iValue1 = plr->x;
|
||||
resp->iValue2 = plr->y;
|
||||
resp->iValue3 = plr->z;
|
||||
resp->iTargetCnt = targetData[0];
|
||||
|
||||
for (int i = 0; i < targetData[0]; i++) {
|
||||
CNSocket *sock = nullptr;
|
||||
Player *plr = nullptr;
|
||||
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
if (pair.second->iID == targetData[i+1]) {
|
||||
sock = pair.first;
|
||||
plr = pair.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// player not found
|
||||
if (plr == nullptr) {
|
||||
std::cout << "[WARN] dealCorruption: player ID not found" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
respdata[i].eCT = 1;
|
||||
respdata[i].iID = plr->iID;
|
||||
respdata[i].bProtected = 0;
|
||||
|
||||
respdata[i].iActiveNanoSlotNum = -1;
|
||||
for (int n = 0; n < 3; n++)
|
||||
if (plr->activeNano == plr->equippedNanos[n])
|
||||
respdata[i].iActiveNanoSlotNum = n;
|
||||
respdata[i].iNanoID = plr->activeNano;
|
||||
|
||||
int nanoStyle = Nanos::nanoStyle(plr->activeNano);
|
||||
if (nanoStyle == -1) { // no nano
|
||||
respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
|
||||
respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
||||
} else if (mobStyle == nanoStyle) {
|
||||
respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
|
||||
respdata[i].iDamage = 0;
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
|
||||
} else if (mobStyle - nanoStyle == 1 || nanoStyle - mobStyle == 2) {
|
||||
respdata[i].iHitFlag = HF_BIT_STYLE_WIN;
|
||||
respdata[i].iDamage = 0;
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina += 45;
|
||||
if (plr->Nanos[plr->activeNano].iStamina > 150)
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150;
|
||||
// fire damage power disguised as a corruption attack back at the enemy
|
||||
SkillData skill = {
|
||||
SkillType::DAMAGE, // skillType
|
||||
SkillEffectTarget::POINT, // effectTarget
|
||||
1, // effectType
|
||||
SkillTargetType::MOBS, // targetType
|
||||
SkillDrainType::ACTIVE, // drainType
|
||||
0, // effectArea
|
||||
{0, 0, 0, 0}, // batteryUse
|
||||
{0, 0, 0, 0}, // durationTime
|
||||
{0, 0, 0}, // valueTypes (unused)
|
||||
{
|
||||
{200, 200, 200, 200},
|
||||
{200, 200, 200, 200},
|
||||
{200, 200, 200, 200},
|
||||
}
|
||||
};
|
||||
Abilities::useNanoSkill(sock, &skill, *plr->getActiveNano(), { mob });
|
||||
} else {
|
||||
respdata[i].iHitFlag = HF_BIT_STYLE_LOSE;
|
||||
respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina -= 90;
|
||||
if (plr->Nanos[plr->activeNano].iStamina < 0) {
|
||||
respdata[i].bNanoDeactive = 1;
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
||||
plr->HP -= respdata[i].iDamage;
|
||||
|
||||
respdata[i].iHP = plr->HP;
|
||||
respdata[i].iConditionBitFlag = plr->getCompositeCondition();
|
||||
|
||||
if (plr->HP <= 0) {
|
||||
if (!MobAI::aggroCheck(mob, getTime()))
|
||||
mob->transition(AIState::RETREAT, mob->target);
|
||||
}
|
||||
}
|
||||
|
||||
NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_NPC_SKILL_CORRUPTION_HIT, resplen);
|
||||
}
|
||||
|
||||
static void useAbilities(Mob *mob, time_t currTime) {
|
||||
Player *plr = PlayerManager::getPlayer(mob->target);
|
||||
|
||||
if (mob->skillStyle >= 0) { // corruption hit
|
||||
int skillID = (int)mob->data["m_iCorruptionType"];
|
||||
std::vector<int> targetData = {1, plr->iID, 0, 0, 0};
|
||||
int temp = mob->skillStyle;
|
||||
mob->skillStyle = -3; // corruption cooldown
|
||||
mob->nextAttack = currTime + 1000;
|
||||
dealCorruption(mob, targetData, skillID, temp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mob->skillStyle == -2) { // eruption hit
|
||||
int skillID = (int)mob->data["m_iMegaType"];
|
||||
std::vector<ICombatant*> targets{};
|
||||
|
||||
// find the players within range of eruption
|
||||
for (auto it = mob->viewableChunks.begin(); it != mob->viewableChunks.end(); it++) {
|
||||
Chunk* chunk = *it;
|
||||
for (const EntityRef& ref : chunk->entities) {
|
||||
// TODO: see aggroCheck()
|
||||
if (ref.kind != EntityKind::PLAYER)
|
||||
continue;
|
||||
|
||||
CNSocket *s = ref.sock;
|
||||
Player *plr = PlayerManager::getPlayer(s);
|
||||
|
||||
if (!plr->isAlive())
|
||||
continue;
|
||||
|
||||
int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y);
|
||||
if (distance < Abilities::SkillTable[skillID].effectArea) {
|
||||
targets.push_back(plr);
|
||||
if (targets.size() > 3) // make sure not to have more than 4
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Abilities::useNPCSkill(mob->id, skillID, targets);
|
||||
mob->skillStyle = -3; // eruption cooldown
|
||||
mob->nextAttack = currTime + 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mob->skillStyle == -3) { // cooldown expires
|
||||
mob->skillStyle = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
int random = Rand::rand(2000) * 1000;
|
||||
int prob1 = (int)mob->data["m_iActiveSkill1Prob"]; // active skill probability
|
||||
int prob2 = (int)mob->data["m_iCorruptionTypeProb"]; // corruption probability
|
||||
int prob3 = (int)mob->data["m_iMegaTypeProb"]; // eruption probability
|
||||
|
||||
if (random < prob1) { // active skill hit
|
||||
int skillID = (int)mob->data["m_iActiveSkill1"];
|
||||
SkillData* skill = &Abilities::SkillTable[skillID];
|
||||
int debuffID = Abilities::getCSTBFromST(skill->skillType);
|
||||
if(plr->hasBuff(debuffID))
|
||||
return; // prevent debuffing a player twice
|
||||
Abilities::useNPCSkill(mob->getRef(), skillID, { plr });
|
||||
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
|
||||
return;
|
||||
}
|
||||
|
||||
if (random < prob1 + prob2) { // corruption windup
|
||||
int skillID = (int)mob->data["m_iCorruptionType"];
|
||||
INITSTRUCT(sP_FE2CL_NPC_SKILL_CORRUPTION_READY, pkt);
|
||||
pkt.iNPC_ID = mob->id;
|
||||
pkt.iSkillID = skillID;
|
||||
pkt.iValue1 = plr->x;
|
||||
pkt.iValue2 = plr->y;
|
||||
pkt.iValue3 = plr->z;
|
||||
mob->skillStyle = Nanos::nanoStyle(plr->activeNano) - 1;
|
||||
if (mob->skillStyle == -1)
|
||||
mob->skillStyle = 2;
|
||||
if (mob->skillStyle == -2)
|
||||
mob->skillStyle = Rand::rand(3);
|
||||
pkt.iStyle = mob->skillStyle;
|
||||
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_SKILL_CORRUPTION_READY, sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_READY));
|
||||
mob->nextAttack = currTime + 1800;
|
||||
return;
|
||||
}
|
||||
|
||||
if (random < prob1 + prob2 + prob3) { // eruption windup
|
||||
int skillID = (int)mob->data["m_iMegaType"];
|
||||
INITSTRUCT(sP_FE2CL_NPC_SKILL_READY, pkt);
|
||||
pkt.iNPC_ID = mob->id;
|
||||
pkt.iSkillID = skillID;
|
||||
pkt.iValue1 = mob->hitX = plr->x;
|
||||
pkt.iValue2 = mob->hitY = plr->y;
|
||||
pkt.iValue3 = mob->hitZ = plr->z;
|
||||
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_SKILL_READY, sizeof(sP_FE2CL_NPC_SKILL_READY));
|
||||
mob->nextAttack = currTime + 1800;
|
||||
mob->skillStyle = -2;
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void MobAI::incNextMovement(Mob* mob, time_t currTime) {
|
||||
if (currTime == 0)
|
||||
currTime = getTime();
|
||||
|
||||
int delay = (int)mob->data["m_iDelayTime"] * 1000;
|
||||
mob->nextMovement = currTime + delay / 2 + Rand::rand(delay / 2);
|
||||
}
|
||||
|
||||
void MobAI::deadStep(CombatNPC* npc, time_t currTime) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
// despawn the mob after a short delay
|
||||
if (self->killedTime != 0 && !self->despawned && currTime - self->killedTime > 2000) {
|
||||
self->despawned = true;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
|
||||
|
||||
pkt.iNPC_ID = self->id;
|
||||
|
||||
NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
|
||||
|
||||
// if it was summoned, mark it for removal
|
||||
if (self->summoned) {
|
||||
std::cout << "[INFO] Queueing killed summoned mob for removal" << std::endl;
|
||||
NPCManager::queueNPCRemoval(self->id);
|
||||
return;
|
||||
}
|
||||
|
||||
// pre-set spawn coordinates if not marked for removal
|
||||
self->x = self->spawnX;
|
||||
self->y = self->spawnY;
|
||||
self->z = self->spawnZ;
|
||||
}
|
||||
|
||||
// to guide their groupmates, group leaders still need to move despite being dead
|
||||
if (self->groupLeader == self->id)
|
||||
roamingStep(self, currTime);
|
||||
|
||||
if (self->killedTime != 0 && currTime - self->killedTime < self->regenTime * 100)
|
||||
return;
|
||||
|
||||
std::cout << "respawning mob " << self->id << " with HP = " << self->maxHealth << std::endl;
|
||||
|
||||
self->transition(AIState::ROAMING, self->id);
|
||||
|
||||
// if mob is a group leader/follower, spawn where the group is.
|
||||
if (self->groupLeader != 0) {
|
||||
if (NPCManager::NPCs.find(self->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[self->groupLeader]->kind == EntityKind::MOB) {
|
||||
Mob* leaderMob = (Mob*)NPCManager::NPCs[self->groupLeader];
|
||||
self->x = leaderMob->x + self->offsetX;
|
||||
self->y = leaderMob->y + self->offsetY;
|
||||
self->z = leaderMob->z;
|
||||
} else {
|
||||
std::cout << "[WARN] deadStep: mob cannot find it's leader!" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_NPC_NEW, pkt);
|
||||
|
||||
pkt.NPCAppearanceData = self->getAppearanceData();
|
||||
|
||||
// notify all nearby players
|
||||
NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW));
|
||||
}
|
||||
|
||||
void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
||||
Mob* self = (Mob*)npc;
|
||||
assert(self->target != nullptr);
|
||||
|
||||
// lose aggro if the player lost connection
|
||||
if (PlayerManager::players.find(self->target) == PlayerManager::players.end()) {
|
||||
if (!MobAI::aggroCheck(self, getTime()))
|
||||
self->transition(AIState::RETREAT, self->target);
|
||||
return;
|
||||
}
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(self->target);
|
||||
|
||||
// lose aggro if the player became invulnerable or died
|
||||
if (plr->HP <= 0
|
||||
|| (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) {
|
||||
if (!MobAI::aggroCheck(self, getTime()))
|
||||
self->transition(AIState::RETREAT, self->target);
|
||||
return;
|
||||
}
|
||||
|
||||
// tick buffs
|
||||
auto it = npc->buffs.begin();
|
||||
while(it != npc->buffs.end()) {
|
||||
Buff* buff = (*it).second;
|
||||
buff->combatTick(currTime);
|
||||
|
||||
// if mob state changed, end the step
|
||||
if(self->state != AIState::COMBAT)
|
||||
return;
|
||||
|
||||
buff->tick(currTime);
|
||||
if(buff->isStale()) {
|
||||
// garbage collect
|
||||
it = npc->buffs.erase(it);
|
||||
delete buff;
|
||||
}
|
||||
else it++;
|
||||
}
|
||||
|
||||
// skip attack if stunned or asleep
|
||||
if (self->hasBuff(ECSB_STUN) || self->hasBuff(ECSB_MEZ)) {
|
||||
self->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up.
|
||||
return;
|
||||
}
|
||||
|
||||
int distance = hypot(plr->x - self->x, plr->y - self->y);
|
||||
int mobRange = (int)self->data["m_iAtkRange"] + (int)self->data["m_iRadius"];
|
||||
|
||||
if (currTime >= self->nextAttack) {
|
||||
if (self->skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance.
|
||||
useAbilities(self, currTime);
|
||||
if (self->target == nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
int distanceToTravel = INT_MAX;
|
||||
int speed = self->speed;
|
||||
// movement logic: move when out of range but don't move while casting a skill
|
||||
if (distance > mobRange && self->skillStyle == -1) {
|
||||
if (self->nextMovement != 0 && currTime < self->nextMovement)
|
||||
return;
|
||||
self->nextMovement = currTime + 400;
|
||||
if (currTime >= self->nextAttack)
|
||||
self->nextAttack = 0;
|
||||
|
||||
// halve movement speed if snared
|
||||
if (self->hasBuff(ECSB_DN_MOVE_SPEED))
|
||||
speed /= 2;
|
||||
|
||||
int targetX = plr->x;
|
||||
int targetY = plr->y;
|
||||
if (self->groupLeader != 0) {
|
||||
targetX += self->offsetX*distance/(self->idleRange + 1);
|
||||
targetY += self->offsetY*distance/(self->idleRange + 1);
|
||||
}
|
||||
|
||||
distanceToTravel = std::min(distance-mobRange+1, speed*2/5);
|
||||
auto targ = lerp(self->x, self->y, targetX, targetY, distanceToTravel);
|
||||
if (distanceToTravel < speed*2/5 && currTime >= self->nextAttack)
|
||||
self->nextAttack = 0;
|
||||
|
||||
NPCManager::updateNPCPosition(self->id, targ.first, targ.second, self->z, self->instanceID, self->angle);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
|
||||
|
||||
pkt.iNPC_ID = self->id;
|
||||
pkt.iSpeed = speed;
|
||||
pkt.iToX = self->x = targ.first;
|
||||
pkt.iToY = self->y = targ.second;
|
||||
pkt.iToZ = plr->z;
|
||||
pkt.iMoveStyle = 1;
|
||||
|
||||
// notify all nearby players
|
||||
NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
|
||||
}
|
||||
|
||||
/* attack logic
|
||||
* 2/5 represents 400 ms which is the time interval mobs use per movement logic step
|
||||
* if the mob is one move interval away, we should just start attacking anyways.
|
||||
*/
|
||||
if (distance <= mobRange || distanceToTravel < self->speed*2/5) {
|
||||
if (self->nextAttack == 0 || currTime >= self->nextAttack) {
|
||||
self->nextAttack = currTime + (int)self->data["m_iDelayTime"] * 100;
|
||||
Combat::npcAttackPc(self, currTime);
|
||||
}
|
||||
}
|
||||
|
||||
// retreat if the player leaves combat range
|
||||
int xyDistance = hypot(plr->x - self->roamX, plr->y - self->roamY);
|
||||
distance = hypot(xyDistance, plr->z - self->roamZ);
|
||||
if (distance >= self->data["m_iCombatRange"]) {
|
||||
self->transition(AIState::RETREAT, self->target);
|
||||
}
|
||||
}
|
||||
|
||||
void MobAI::roamingStep(CombatNPC* npc, time_t currTime) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
/*
|
||||
* We reuse nextAttack to avoid scanning for players all the time, but to still
|
||||
* do so more often than if we waited for nextMovement (which is way too slow).
|
||||
* In the case of group leaders, this step will be called by dead mobs, so disable attack.
|
||||
*/
|
||||
if (self->state != AIState::DEAD && (self->nextAttack == 0 || currTime >= self->nextAttack)) {
|
||||
self->nextAttack = currTime + 500;
|
||||
if (aggroCheck(self, currTime))
|
||||
return;
|
||||
}
|
||||
|
||||
// no random roaming if the mob already has a set path
|
||||
if (self->staticPath)
|
||||
return;
|
||||
|
||||
if (self->groupLeader != 0 && self->groupLeader != self->id) // don't roam by yourself without group leader
|
||||
return;
|
||||
|
||||
/*
|
||||
* mob->nextMovement is also updated whenever the path queue is traversed in
|
||||
* Transport::stepNPCPathing() (which ticks at a higher frequency than nextMovement),
|
||||
* so we don't have to check if there's already entries in the queue since we know there won't be.
|
||||
*/
|
||||
if (self->nextMovement != 0 && currTime < self->nextMovement)
|
||||
return;
|
||||
incNextMovement(self, currTime);
|
||||
|
||||
int xStart = self->spawnX - self->idleRange/2;
|
||||
int yStart = self->spawnY - self->idleRange/2;
|
||||
|
||||
// some mobs don't move (and we mustn't divide/modulus by zero)
|
||||
if (self->idleRange == 0 || self->speed == 0)
|
||||
return;
|
||||
|
||||
int farX, farY, distance;
|
||||
int minDistance = self->idleRange / 2;
|
||||
|
||||
// pick a random destination
|
||||
farX = xStart + Rand::rand(self->idleRange);
|
||||
farY = yStart + Rand::rand(self->idleRange);
|
||||
|
||||
distance = std::abs(std::max(farX - self->x, farY - self->y));
|
||||
if (distance == 0)
|
||||
distance += 1; // hack to avoid FPE
|
||||
|
||||
// if it's too short a walk, go further in that direction
|
||||
farX = self->x + (farX - self->x) * minDistance / distance;
|
||||
farY = self->y + (farY - self->y) * minDistance / distance;
|
||||
|
||||
// but don't got out of bounds
|
||||
farX = std::clamp(farX, xStart, xStart + self->idleRange);
|
||||
farY = std::clamp(farY, yStart, yStart + self->idleRange);
|
||||
|
||||
// halve movement speed if snared
|
||||
if (self->hasBuff(ECSB_DN_MOVE_SPEED))
|
||||
self->speed /= 2;
|
||||
|
||||
std::queue<Vec3> queue;
|
||||
Vec3 from = { self->x, self->y, self->z };
|
||||
Vec3 to = { farX, farY, self->z };
|
||||
|
||||
// add a route to the queue; to be processed in Transport::stepNPCPathing()
|
||||
Transport::lerp(&queue, from, to, self->speed);
|
||||
Transport::NPCQueues[self->id] = queue;
|
||||
|
||||
if (self->groupLeader != 0 && self->groupLeader == self->id) {
|
||||
// make followers follow this npc.
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (self->groupMember[i] == 0)
|
||||
break;
|
||||
|
||||
if (NPCManager::NPCs.find(self->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[self->groupMember[i]]->kind != EntityKind::MOB) {
|
||||
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::queue<Vec3> queue2;
|
||||
Mob* followerMob = (Mob*)NPCManager::NPCs[self->groupMember[i]];
|
||||
from = { followerMob->x, followerMob->y, followerMob->z };
|
||||
to = { farX + followerMob->offsetX, farY + followerMob->offsetY, followerMob->z };
|
||||
Transport::lerp(&queue2, from, to, self->speed);
|
||||
Transport::NPCQueues[followerMob->id] = queue2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MobAI::retreatStep(CombatNPC* npc, time_t currTime) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
if (self->nextMovement != 0 && currTime < self->nextMovement)
|
||||
return;
|
||||
|
||||
self->nextMovement = currTime + 400;
|
||||
|
||||
// distance between spawn point and current location
|
||||
int distance = hypot(self->x - self->roamX, self->y - self->roamY);
|
||||
|
||||
//if (distance > mob->data["m_iIdleRange"]) {
|
||||
if (distance > 10) {
|
||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
|
||||
|
||||
auto targ = lerp(self->x, self->y, self->roamX, self->roamY, (int)self->speed*4/5);
|
||||
|
||||
pkt.iNPC_ID = self->id;
|
||||
pkt.iSpeed = (int)self->speed * 2;
|
||||
pkt.iToX = self->x = targ.first;
|
||||
pkt.iToY = self->y = targ.second;
|
||||
pkt.iToZ = self->z = self->spawnZ;
|
||||
pkt.iMoveStyle = 1;
|
||||
|
||||
// notify all nearby players
|
||||
NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
|
||||
}
|
||||
|
||||
// if we got there
|
||||
//if (distance <= mob->data["m_iIdleRange"]) {
|
||||
if (distance <= 10) { // retreat back to the spawn point
|
||||
self->transition(AIState::ROAMING, self->id);
|
||||
}
|
||||
}
|
||||
|
||||
void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
self->hp = self->maxHealth;
|
||||
self->killedTime = 0;
|
||||
self->nextAttack = 0;
|
||||
|
||||
// cast a return home heal spell, this is the right way(tm)
|
||||
Abilities::useNPCSkill(npc->getRef(), 110, { npc });
|
||||
|
||||
// clear outlying debuffs
|
||||
clearDebuff(self);
|
||||
}
|
||||
|
||||
void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
assert(src.kind == EntityKind::PLAYER);
|
||||
self->target = src.sock;
|
||||
self->nextMovement = getTime();
|
||||
self->nextAttack = 0;
|
||||
|
||||
self->roamX = self->x;
|
||||
self->roamY = self->y;
|
||||
self->roamZ = self->z;
|
||||
|
||||
int skillID = (int)self->data["m_iPassiveBuff"];
|
||||
if(skillID != 0) // cast passive
|
||||
Abilities::useNPCSkill(npc->getRef(), skillID, { npc });
|
||||
}
|
||||
|
||||
void MobAI::onRetreat(CombatNPC* npc, EntityRef src) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
self->target = nullptr;
|
||||
MobAI::clearDebuff(self);
|
||||
if (self->groupLeader != 0)
|
||||
MobAI::groupRetreat(self);
|
||||
}
|
||||
|
||||
void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
self->target = nullptr;
|
||||
self->skillStyle = -1;
|
||||
self->clearBuffs(true);
|
||||
self->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step?
|
||||
|
||||
// check for the edge case where hitting the mob did not aggro it
|
||||
if (src.kind == EntityKind::PLAYER && src.isValid()) {
|
||||
Player* plr = PlayerManager::getPlayer(src.sock);
|
||||
|
||||
Items::DropRoll rolled;
|
||||
Items::DropRoll eventRolled;
|
||||
std::map<int, int> qitemRolls;
|
||||
std::vector<Player*> playerRefs;
|
||||
|
||||
if (plr->group == nullptr) {
|
||||
playerRefs.push_back(plr);
|
||||
Combat::genQItemRolls(playerRefs, qitemRolls);
|
||||
Items::giveMobDrop(src.sock, self, rolled, eventRolled);
|
||||
Missions::mobKilled(src.sock, self->type, qitemRolls);
|
||||
}
|
||||
else {
|
||||
auto players = plr->group->filter(EntityKind::PLAYER);
|
||||
for (EntityRef pRef : players) playerRefs.push_back(PlayerManager::getPlayer(pRef.sock));
|
||||
Combat::genQItemRolls(playerRefs, qitemRolls);
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
CNSocket* sockTo = players[i].sock;
|
||||
Player* otherPlr = PlayerManager::getPlayer(sockTo);
|
||||
|
||||
// only contribute to group members' kills if they're close enough
|
||||
int dist = std::hypot(plr->x - otherPlr->x + 1, plr->y - otherPlr->y + 1);
|
||||
if (dist > 5000)
|
||||
continue;
|
||||
|
||||
Items::giveMobDrop(sockTo, self, rolled, eventRolled);
|
||||
Missions::mobKilled(sockTo, self->type, qitemRolls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delay the despawn animation
|
||||
self->despawned = false;
|
||||
|
||||
auto it = Transport::NPCQueues.find(self->id);
|
||||
if (it == Transport::NPCQueues.end() || it->second.empty())
|
||||
return;
|
||||
|
||||
// rewind or empty the movement queue
|
||||
if (self->staticPath) {
|
||||
/*
|
||||
* This is inelegant, but we wind forward in the path until we find the point that
|
||||
* corresponds with the Mob's spawn point.
|
||||
*
|
||||
* IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever.
|
||||
*/
|
||||
auto& queue = it->second;
|
||||
for (auto point = queue.front(); point.x != self->spawnX || point.y != self->spawnY; point = queue.front()) {
|
||||
queue.pop();
|
||||
queue.push(point);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Transport::NPCQueues.erase(self->id);
|
||||
}
|
||||
}
|
||||
114
src/MobAI.hpp
Normal file
114
src/MobAI.hpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "JSON.hpp"
|
||||
|
||||
#include "Entities.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
namespace MobAI {
|
||||
void deadStep(CombatNPC* self, time_t currTime);
|
||||
void combatStep(CombatNPC* self, time_t currTime);
|
||||
void roamingStep(CombatNPC* self, time_t currTime);
|
||||
void retreatStep(CombatNPC* self, time_t currTime);
|
||||
|
||||
void onRoamStart(CombatNPC* self, EntityRef src);
|
||||
void onCombatStart(CombatNPC* self, EntityRef src);
|
||||
void onRetreat(CombatNPC* self, EntityRef src);
|
||||
void onDeath(CombatNPC* self, EntityRef src);
|
||||
}
|
||||
|
||||
struct Mob : public CombatNPC {
|
||||
|
||||
// dead
|
||||
time_t killedTime = 0;
|
||||
time_t regenTime = 0;
|
||||
bool summoned = false;
|
||||
bool despawned = false; // for the sake of death animations
|
||||
|
||||
// roaming
|
||||
int idleRange = 0;
|
||||
const int sightRange = 0;
|
||||
time_t nextMovement = 0;
|
||||
bool staticPath = false;
|
||||
int roamX = 0, roamY = 0, roamZ = 0;
|
||||
|
||||
// combat
|
||||
CNSocket *target = nullptr;
|
||||
time_t nextAttack = 0;
|
||||
time_t lastDrainTime = 0;
|
||||
int skillStyle = -1; // -1 for nothing, 0-2 for corruption, -2 for eruption
|
||||
int hitX = 0, hitY = 0, hitZ = 0; // for use in ability targeting
|
||||
|
||||
// group
|
||||
int groupLeader = 0;
|
||||
int offsetX = 0, offsetY = 0;
|
||||
int groupMember[4] = {};
|
||||
|
||||
// temporary; until we're sure what's what
|
||||
nlohmann::json data = {};
|
||||
|
||||
Mob(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
|
||||
: CombatNPC(spawnX, spawnY, spawnZ, angle, iID, t, id, d["m_iHP"]),
|
||||
sightRange(d["m_iSightRange"]) {
|
||||
state = AIState::ROAMING;
|
||||
|
||||
data = d;
|
||||
|
||||
speed = data["m_iRunSpeed"];
|
||||
regenTime = data["m_iRegenTime"];
|
||||
idleRange = (int)data["m_iIdleRange"];
|
||||
level = data["m_iNpcLevel"];
|
||||
|
||||
roamX = spawnX;
|
||||
roamY = spawnY;
|
||||
roamZ = spawnZ;
|
||||
|
||||
offsetX = 0;
|
||||
offsetY = 0;
|
||||
|
||||
// NOTE: there appear to be discrepancies in the dump
|
||||
hp = maxHealth;
|
||||
|
||||
kind = EntityKind::MOB;
|
||||
|
||||
// AI
|
||||
stateHandlers[AIState::DEAD] = MobAI::deadStep;
|
||||
stateHandlers[AIState::COMBAT] = MobAI::combatStep;
|
||||
stateHandlers[AIState::ROAMING] = MobAI::roamingStep;
|
||||
stateHandlers[AIState::RETREAT] = MobAI::retreatStep;
|
||||
|
||||
transitionHandlers[AIState::DEAD] = MobAI::onDeath;
|
||||
transitionHandlers[AIState::COMBAT] = MobAI::onCombatStart;
|
||||
transitionHandlers[AIState::ROAMING] = MobAI::onRoamStart;
|
||||
transitionHandlers[AIState::RETREAT] = MobAI::onRetreat;
|
||||
}
|
||||
|
||||
// constructor for /summon
|
||||
Mob(int x, int y, int z, uint64_t iID, int t, nlohmann::json d, int32_t id)
|
||||
: Mob(x, y, z, 0, iID, t, d, id) {
|
||||
summoned = true; // will be despawned and deallocated when killed
|
||||
}
|
||||
|
||||
~Mob() {}
|
||||
|
||||
virtual int takeDamage(EntityRef src, int amt) override;
|
||||
virtual void step(time_t currTime) override;
|
||||
|
||||
auto operator[](std::string s) {
|
||||
return data[s];
|
||||
}
|
||||
};
|
||||
|
||||
namespace MobAI {
|
||||
extern bool simulateMobs;
|
||||
|
||||
// TODO: make this internal later
|
||||
void incNextMovement(Mob *mob, time_t currTime=0);
|
||||
bool aggroCheck(Mob *mob, time_t currTime);
|
||||
void clearDebuff(Mob *mob);
|
||||
void followToCombat(Mob *mob);
|
||||
void groupRetreat(Mob *mob);
|
||||
}
|
||||
1139
src/MobManager.cpp
1139
src/MobManager.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,145 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNShared.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
#include "NPC.hpp"
|
||||
|
||||
#include "contrib/JSON.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
|
||||
enum class MobState {
|
||||
INACTIVE,
|
||||
ROAMING,
|
||||
COMBAT,
|
||||
RETREAT,
|
||||
DEAD
|
||||
};
|
||||
|
||||
struct Mob : public BaseNPC {
|
||||
// general
|
||||
MobState state;
|
||||
int maxHealth;
|
||||
int spawnX;
|
||||
int spawnY;
|
||||
int spawnZ;
|
||||
int level;
|
||||
|
||||
std::unordered_map<int32_t,time_t> unbuffTimes;
|
||||
|
||||
// dead
|
||||
time_t killedTime = 0;
|
||||
time_t regenTime;
|
||||
bool summoned = false;
|
||||
bool despawned = false; // for the sake of death animations
|
||||
|
||||
// roaming
|
||||
int idleRange;
|
||||
const int sightRange;
|
||||
time_t nextMovement = 0;
|
||||
bool staticPath = false;
|
||||
int roamX, roamY, roamZ;
|
||||
|
||||
// combat
|
||||
CNSocket *target = nullptr;
|
||||
time_t nextAttack = 0;
|
||||
|
||||
|
||||
// drop
|
||||
int dropType;
|
||||
|
||||
// temporary; until we're sure what's what
|
||||
nlohmann::json data;
|
||||
|
||||
Mob(int x, int y, int z, int angle, uint64_t iID, int type, int hp, nlohmann::json d, int32_t id)
|
||||
: BaseNPC(x, y, z, angle, iID, type, id),
|
||||
maxHealth(hp),
|
||||
sightRange(d["m_iSightRange"]) {
|
||||
state = MobState::ROAMING;
|
||||
|
||||
data = d;
|
||||
|
||||
regenTime = data["m_iRegenTime"];
|
||||
idleRange = (int)data["m_iIdleRange"] * 2; // TODO: tuning?
|
||||
dropType = data["m_iDropType"];
|
||||
level = data["m_iNpcLevel"];
|
||||
|
||||
roamX = spawnX = appearanceData.iX;
|
||||
roamY = spawnY = appearanceData.iY;
|
||||
roamZ = spawnZ = appearanceData.iZ;
|
||||
|
||||
appearanceData.iConditionBitFlag = 0;
|
||||
|
||||
// NOTE: there appear to be discrepancies in the dump
|
||||
appearanceData.iHP = maxHealth;
|
||||
|
||||
npcClass = NPC_MOB;
|
||||
}
|
||||
|
||||
// constructor for /summon
|
||||
Mob(int x, int y, int z, uint64_t iID, int type, nlohmann::json d, int32_t id)
|
||||
: Mob(x, y, z, 0, iID, type, 0, d, id) {
|
||||
summoned = true; // will be despawned and deallocated when killed
|
||||
appearanceData.iHP = maxHealth = d["m_iHP"];
|
||||
}
|
||||
|
||||
~Mob() {}
|
||||
|
||||
auto operator[](std::string s) {
|
||||
return data[s];
|
||||
}
|
||||
};
|
||||
|
||||
struct MobDropChance {
|
||||
int dropChance;
|
||||
std::vector<int> cratesRatio;
|
||||
};
|
||||
|
||||
struct MobDrop {
|
||||
std::vector<int> crateIDs;
|
||||
int dropChanceType;
|
||||
int taros;
|
||||
int fm;
|
||||
int boosts;
|
||||
};
|
||||
|
||||
namespace MobManager {
|
||||
extern std::map<int32_t, Mob*> Mobs;
|
||||
extern std::queue<int32_t> RemovalQueue;
|
||||
extern std::map<int32_t, MobDropChance> MobDropChances;
|
||||
extern std::map<int32_t, MobDrop> MobDrops;
|
||||
extern bool simulateMobs;
|
||||
|
||||
void init();
|
||||
void step(CNServer*, time_t);
|
||||
void playerTick(CNServer*, time_t);
|
||||
|
||||
void deadStep(Mob*, time_t);
|
||||
void combatStep(Mob*, time_t);
|
||||
void retreatStep(Mob*, time_t);
|
||||
void roamingStep(Mob*, time_t);
|
||||
|
||||
void pcAttackNpcs(CNSocket *sock, CNPacketData *data);
|
||||
void combatBegin(CNSocket *sock, CNPacketData *data);
|
||||
void combatEnd(CNSocket *sock, CNPacketData *data);
|
||||
void dotDamageOnOff(CNSocket *sock, CNPacketData *data);
|
||||
void dealGooDamage(CNSocket *sock, int amount);
|
||||
|
||||
void npcAttackPc(Mob *mob, time_t currTime);
|
||||
int hitMob(CNSocket *sock, Mob *mob, int damage);
|
||||
void killMob(CNSocket *sock, Mob *mob);
|
||||
void giveReward(CNSocket *sock, Mob *mob);
|
||||
void getReward(sItemBase *reward, MobDrop *drop, MobDropChance *chance);
|
||||
void giveEventReward(CNSocket* sock, Player* player);
|
||||
|
||||
std::pair<int,int> lerp(int, int, int, int, int);
|
||||
std::pair<int,int> getDamage(int, int, bool, bool, int, int, int);
|
||||
|
||||
void pcAttackChars(CNSocket *sock, CNPacketData *data);
|
||||
void drainMobHP(Mob *mob, int amount);
|
||||
void incNextMovement(Mob *mob, time_t currTime=0);
|
||||
bool aggroCheck(Mob *mob, time_t currTime);
|
||||
}
|
||||
33
src/NPC.hpp
33
src/NPC.hpp
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNStructs.hpp"
|
||||
#include "ChunkManager.hpp"
|
||||
|
||||
class BaseNPC {
|
||||
public:
|
||||
sNPCAppearanceData appearanceData;
|
||||
NPCClass npcClass;
|
||||
uint64_t instanceID;
|
||||
std::tuple<int, int, uint64_t> chunkPos;
|
||||
std::vector<Chunk*> currentChunks;
|
||||
|
||||
BaseNPC() {};
|
||||
BaseNPC(int x, int y, int z, int angle, uint64_t iID, int type, int id) {
|
||||
appearanceData.iX = x;
|
||||
appearanceData.iY = y;
|
||||
appearanceData.iZ = z;
|
||||
appearanceData.iNPCType = type;
|
||||
appearanceData.iHP = 400;
|
||||
appearanceData.iAngle = angle;
|
||||
appearanceData.iConditionBitFlag = 0;
|
||||
appearanceData.iBarkerType = 0;
|
||||
appearanceData.iNPC_ID = id;
|
||||
|
||||
instanceID = iID;
|
||||
|
||||
chunkPos = std::make_tuple(0, 0, instanceID);
|
||||
};
|
||||
BaseNPC(int x, int y, int z, int angle, uint64_t iID, int type, int id, NPCClass classType) : BaseNPC(x, y, z, angle, iID, type, id) {
|
||||
npcClass = classType;
|
||||
}
|
||||
};
|
||||
@@ -1,9 +1,20 @@
|
||||
#include "NPCManager.hpp"
|
||||
#include "ItemManager.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
#include "ChunkManager.hpp"
|
||||
#include "Combat.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Chunking.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "TableData.hpp"
|
||||
#include "Groups.hpp"
|
||||
#include "Racing.hpp"
|
||||
#include "Vendors.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "Rand.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
@@ -13,13 +24,15 @@
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "contrib/JSON.hpp"
|
||||
using namespace NPCManager;
|
||||
|
||||
std::map<int32_t, BaseNPC*> NPCManager::NPCs;
|
||||
std::unordered_map<int32_t, BaseNPC*> NPCManager::NPCs;
|
||||
std::map<int32_t, WarpLocation> NPCManager::Warps;
|
||||
std::vector<WarpLocation> NPCManager::RespawnPoints;
|
||||
nlohmann::json NPCManager::NPCData;
|
||||
|
||||
static std::queue<int32_t> RemovalQueue;
|
||||
|
||||
/*
|
||||
* Initialized at the end of TableData::init().
|
||||
* This allows us to summon and kill mobs in arbitrary order without
|
||||
@@ -27,83 +40,6 @@ nlohmann::json NPCManager::NPCData;
|
||||
*/
|
||||
int32_t NPCManager::nextId;
|
||||
|
||||
void NPCManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_WARP_USE_NPC, npcWarpHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TIME_TO_GO_WARP, npcWarpTimeMachine);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_SUMMON, npcSummonHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_START, npcVendorStart);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE, npcVendorTable);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_BUY, npcVendorBuy);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_SELL, npcVendorSell);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY, npcVendorBuyback);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_BATTERY_BUY, npcVendorBuyBattery);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_COMBINATION, npcCombineItems);
|
||||
}
|
||||
|
||||
void NPCManager::removeNPC(std::vector<Chunk*> viewableChunks, int32_t id) {
|
||||
BaseNPC* npc = NPCs[id];
|
||||
|
||||
switch (npc->npcClass) {
|
||||
case NPC_BUS:
|
||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, exitBusData);
|
||||
exitBusData.eTT = 3;
|
||||
exitBusData.iT_ID = id;
|
||||
|
||||
for (Chunk* chunk : viewableChunks) {
|
||||
for (CNSocket* sock : chunk->players) {
|
||||
// send to socket
|
||||
sock->sendPacket((void*)&exitBusData, P_FE2CL_TRANSPORTATION_EXIT, sizeof(sP_FE2CL_TRANSPORTATION_EXIT));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// create struct
|
||||
INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData);
|
||||
exitData.iNPC_ID = id;
|
||||
|
||||
// remove it from the clients
|
||||
for (Chunk* chunk : viewableChunks) {
|
||||
for (CNSocket* sock : chunk->players) {
|
||||
// send to socket
|
||||
sock->sendPacket((void*)&exitData, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NPCManager::addNPC(std::vector<Chunk*> viewableChunks, int32_t id) {
|
||||
BaseNPC* npc = NPCs[id];
|
||||
|
||||
switch (npc->npcClass) {
|
||||
case NPC_BUS:
|
||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, enterBusData);
|
||||
enterBusData.AppearanceData = { 3, npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ };
|
||||
|
||||
for (Chunk* chunk : viewableChunks) {
|
||||
for (CNSocket* sock : chunk->players) {
|
||||
// send to socket
|
||||
sock->sendPacket((void*)&enterBusData, P_FE2CL_TRANSPORTATION_ENTER, sizeof(sP_FE2CL_TRANSPORTATION_ENTER));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// create struct
|
||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData);
|
||||
enterData.NPCAppearanceData = npc->appearanceData;
|
||||
|
||||
for (Chunk* chunk : viewableChunks) {
|
||||
for (CNSocket* sock : chunk->players) {
|
||||
// send to socket
|
||||
sock->sendPacket((void*)&enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NPCManager::destroyNPC(int32_t id) {
|
||||
// sanity check
|
||||
if (NPCs.find(id) == NPCs.end()) {
|
||||
@@ -114,534 +50,240 @@ void NPCManager::destroyNPC(int32_t id) {
|
||||
BaseNPC* entity = NPCs[id];
|
||||
|
||||
// sanity check
|
||||
if (ChunkManager::chunks.find(entity->chunkPos) == ChunkManager::chunks.end()) {
|
||||
if (!Chunking::chunkExists(entity->chunkPos)) {
|
||||
std::cout << "chunk not found!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// remove NPC from the chunk
|
||||
Chunk* chunk = ChunkManager::chunks[entity->chunkPos];
|
||||
chunk->NPCs.erase(id);
|
||||
EntityRef ref = {id};
|
||||
Chunking::untrackEntity(entity->chunkPos, ref);
|
||||
|
||||
// remove from viewable chunks
|
||||
removeNPC(entity->currentChunks, id);
|
||||
|
||||
// remove from mob manager
|
||||
if (MobManager::Mobs.find(id) != MobManager::Mobs.end())
|
||||
MobManager::Mobs.erase(id);
|
||||
Chunking::removeEntityFromChunks(Chunking::getViewableChunks(entity->chunkPos), ref);
|
||||
|
||||
// finally, remove it from the map and free it
|
||||
NPCs.erase(id);
|
||||
delete entity;
|
||||
}
|
||||
|
||||
void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, int angle) {
|
||||
NPCs[id]->appearanceData.iAngle = angle;
|
||||
updateNPCPosition(id, X, Y, Z);
|
||||
}
|
||||
|
||||
void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z) {
|
||||
void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I, int angle) {
|
||||
BaseNPC* npc = NPCs[id];
|
||||
|
||||
npc->appearanceData.iX = X;
|
||||
npc->appearanceData.iY = Y;
|
||||
npc->appearanceData.iZ = Z;
|
||||
std::tuple<int, int, uint64_t> newPos = ChunkManager::grabChunk(X, Y, npc->instanceID);
|
||||
|
||||
// nothing to be done (but we should also update currentChunks to add/remove stale chunks)
|
||||
if (newPos == npc->chunkPos) {
|
||||
npc->currentChunks = ChunkManager::grabChunks(newPos);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Chunk*> allChunks = ChunkManager::grabChunks(newPos);
|
||||
|
||||
// send npc exit to stale chunks
|
||||
removeNPC(ChunkManager::getDeltaChunks(npc->currentChunks, allChunks), id);
|
||||
|
||||
// send npc enter to new chunks
|
||||
addNPC(ChunkManager::getDeltaChunks(allChunks, npc->currentChunks), id);
|
||||
|
||||
Chunk *chunk = nullptr;
|
||||
if (ChunkManager::checkChunk(npc->chunkPos))
|
||||
chunk = ChunkManager::chunks[npc->chunkPos];
|
||||
|
||||
if (ChunkManager::removeNPC(npc->chunkPos, id)) {
|
||||
// if the old chunk was deallocated, remove it
|
||||
allChunks.erase(std::remove(allChunks.begin(), allChunks.end(), chunk), allChunks.end());
|
||||
}
|
||||
|
||||
ChunkManager::addNPC(X, Y, npc->instanceID, id);
|
||||
|
||||
npc->chunkPos = newPos;
|
||||
npc->currentChunks = allChunks;
|
||||
npc->angle = angle;
|
||||
ChunkPos oldChunk = npc->chunkPos;
|
||||
ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I);
|
||||
npc->x = X;
|
||||
npc->y = Y;
|
||||
npc->z = Z;
|
||||
npc->instanceID = I;
|
||||
if (oldChunk == newChunk)
|
||||
return; // didn't change chunks
|
||||
Chunking::updateEntityChunk({id}, oldChunk, newChunk);
|
||||
}
|
||||
|
||||
void NPCManager::updateNPCInstance(int32_t npcID, uint64_t instanceID) {
|
||||
BaseNPC* npc = NPCs[npcID];
|
||||
npc->instanceID = instanceID;
|
||||
updateNPCPosition(npcID, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ);
|
||||
}
|
||||
|
||||
void NPCManager::sendToViewable(BaseNPC *npc, void *buf, uint32_t type, size_t size) {
|
||||
for (Chunk *chunk : npc->currentChunks) {
|
||||
for (CNSocket *s : chunk->players) {
|
||||
s->sendPacket(buf, type, size);
|
||||
void NPCManager::sendToViewable(Entity *npc, void *buf, uint32_t type, size_t size) {
|
||||
for (auto it = npc->viewableChunks.begin(); it != npc->viewableChunks.end(); it++) {
|
||||
Chunk* chunk = *it;
|
||||
for (const EntityRef& ref : chunk->entities) {
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
ref.sock->sendPacket(buf, type, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY))
|
||||
return; // malformed packet
|
||||
static void npcBarkHandler(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_BARKER* req = (sP_CL2FE_REQ_BARKER*)data->buf;
|
||||
|
||||
sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
Item* item = ItemManager::getItemData(req->Item.iID, req->Item.iType);
|
||||
|
||||
if (item == nullptr) {
|
||||
std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (buy)" << std::endl;
|
||||
// NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side.
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL));
|
||||
return;
|
||||
// get bark IDs from task data
|
||||
TaskData* td = Missions::Tasks[req->iMissionTaskID];
|
||||
std::vector<int> barks;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (td->task["m_iHBarkerTextID"][i] != 0) // non-zeroes only
|
||||
barks.push_back(td->task["m_iHBarkerTextID"][i]);
|
||||
}
|
||||
|
||||
int itemCost = item->buyPrice * (item->stackSize > 1 ? req->Item.iOpt : 1);
|
||||
int slot = ItemManager::findFreeSlot(plr);
|
||||
if (itemCost > plr->money || slot == -1) {
|
||||
// NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side.
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL));
|
||||
return;
|
||||
}
|
||||
// if vehicle
|
||||
if (req->Item.iType == 10)
|
||||
// set time limit: current time + 7days
|
||||
req->Item.iTimeLimit = getTimestamp() + 604800;
|
||||
|
||||
if (slot != req->iInvenSlotNum) {
|
||||
// possible item stacking?
|
||||
std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, resp);
|
||||
|
||||
plr->money = plr->money - itemCost;
|
||||
plr->Inven[slot] = req->Item;
|
||||
|
||||
resp.iCandy = plr->money;
|
||||
resp.iInvenSlotNum = slot;
|
||||
resp.Item = req->Item;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC));
|
||||
}
|
||||
|
||||
void NPCManager::npcVendorSell(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
if (req->iInvenSlotNum < 0 || req->iInvenSlotNum >= AINVEN_COUNT || req->iItemCnt < 0) {
|
||||
std::cout << "[WARN] Client failed to sell item in slot " << req->iInvenSlotNum << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
sItemBase* item = &plr->Inven[req->iInvenSlotNum];
|
||||
Item* itemData = ItemManager::getItemData(item->iID, item->iType);
|
||||
|
||||
if (itemData == nullptr || !itemData->sellable) { // sanity + sellable check
|
||||
std::cout << "[WARN] Item id " << item->iID << " with type " << item->iType << " not found (sell)" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
sItemBase original;
|
||||
memcpy(&original, item, sizeof(sItemBase));
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, resp);
|
||||
|
||||
int sellValue = itemData->sellPrice * req->iItemCnt;
|
||||
|
||||
// increment taros
|
||||
plr->money = plr->money + sellValue;
|
||||
|
||||
// modify item
|
||||
if (plr->Inven[req->iInvenSlotNum].iOpt - req->iItemCnt > 0) { // selling part of a stack
|
||||
item->iOpt -= req->iItemCnt;
|
||||
original.iOpt = req->iItemCnt;
|
||||
} else { // selling entire slot
|
||||
item->iID = 0;
|
||||
item->iOpt = 0;
|
||||
item->iType = 0;
|
||||
item->iTimeLimit = 0;
|
||||
}
|
||||
|
||||
// response parameters
|
||||
resp.iInvenSlotNum = req->iInvenSlotNum;
|
||||
resp.iCandy = plr->money;
|
||||
resp.Item = original; // the item that gets sent to buyback
|
||||
resp.ItemStay = *item; // the void item that gets put in the slot
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC));
|
||||
}
|
||||
|
||||
void NPCManager::npcVendorBuyback(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
Item* item = ItemManager::getItemData(req->Item.iID, req->Item.iType);
|
||||
|
||||
if (item == nullptr) {
|
||||
std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (rebuy)" << std::endl;
|
||||
// NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side.
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
// sell price is used on rebuy. ternary identifies stacked items
|
||||
int itemCost = item->sellPrice * (item->stackSize > 1 ? req->Item.iOpt : 1);
|
||||
int slot = ItemManager::findFreeSlot(plr);
|
||||
if (itemCost > plr->money || slot == -1) {
|
||||
// NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side.
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
if (slot != req->iInvenSlotNum) {
|
||||
// possible item stacking?
|
||||
std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl;
|
||||
}
|
||||
|
||||
plr->money = plr->money - itemCost;
|
||||
plr->Inven[slot] = req->Item;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, resp);
|
||||
// response parameters
|
||||
resp.iCandy = plr->money;
|
||||
resp.iInvenSlotNum = slot;
|
||||
resp.Item = req->Item;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC));
|
||||
}
|
||||
|
||||
void NPCManager::npcVendorTable(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE* req = (sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE*)data->buf;
|
||||
|
||||
if (req->iVendorID != req->iNPC_ID || ItemManager::VendorTables.find(req->iNPC_ID) == ItemManager::VendorTables.end())
|
||||
return;
|
||||
|
||||
std::vector<VendorListing> listings = ItemManager::VendorTables[req->iNPC_ID]; // maybe use iVendorID instead...?
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp);
|
||||
|
||||
for (int i = 0; i < (int)listings.size() && i < 20; i++) { // 20 is the max
|
||||
sItemBase base;
|
||||
base.iID = listings[i].iID;
|
||||
base.iOpt = 0;
|
||||
base.iTimeLimit = 0;
|
||||
base.iType = listings[i].type;
|
||||
|
||||
sItemVendor vItem;
|
||||
vItem.item = base;
|
||||
vItem.iSortNum = listings[i].sort;
|
||||
vItem.iVendorID = req->iVendorID;
|
||||
//vItem.fBuyCost = listings[i].price; // this value is not actually the one that is used
|
||||
|
||||
resp.item[i] = vItem;
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC));
|
||||
}
|
||||
|
||||
void NPCManager::npcVendorStart(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_START))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_VENDOR_START* req = (sP_CL2FE_REQ_PC_VENDOR_START*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_START_SUCC, resp);
|
||||
if (barks.empty())
|
||||
return; // no barks
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_BARKER, resp);
|
||||
resp.iNPC_ID = req->iNPC_ID;
|
||||
resp.iVendorID = req->iVendorID;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_START_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_START_SUCC));
|
||||
resp.iMissionStringID = barks[Rand::rand(barks.size())];
|
||||
sock->sendPacket(resp, P_FE2CL_REP_BARKER);
|
||||
}
|
||||
|
||||
void NPCManager::npcVendorBuyBattery(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY*)data->buf;
|
||||
static void npcUnsummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
int cost = req->Item.iOpt * 10;
|
||||
if ((req->Item.iID == 3 ? (plr->batteryW >= 9999) : (plr->batteryN >= 9999)) || plr->money < cost) { // sanity check
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL));
|
||||
}
|
||||
|
||||
plr->money -= cost;
|
||||
plr->batteryW += req->Item.iID == 3 ? req->Item.iOpt * 10 : 0;
|
||||
plr->batteryN += req->Item.iID == 4 ? req->Item.iOpt * 10 : 0;
|
||||
|
||||
// caps
|
||||
if (plr->batteryW > 9999)
|
||||
plr->batteryW = 9999;
|
||||
if (plr->batteryN > 9999)
|
||||
plr->batteryN = 9999;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, resp);
|
||||
|
||||
resp.iCandy = plr->money;
|
||||
resp.iBatteryW = plr->batteryW;
|
||||
resp.iBatteryN = plr->batteryN;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC));
|
||||
}
|
||||
|
||||
void NPCManager::npcCombineItems(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_ITEM_COMBINATION))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_ITEM_COMBINATION* req = (sP_CL2FE_REQ_PC_ITEM_COMBINATION*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
if (req->iCostumeItemSlot < 0 || req->iCostumeItemSlot >= AINVEN_COUNT || req->iStatItemSlot < 0 || req->iStatItemSlot >= AINVEN_COUNT) { // sanity check 1
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp);
|
||||
failResp.iCostumeItemSlot = req->iCostumeItemSlot;
|
||||
failResp.iStatItemSlot = req->iStatItemSlot;
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL));
|
||||
std::cout << "[WARN] Inventory slot(s) out of range (" << req->iStatItemSlot << " and " << req->iCostumeItemSlot << ")" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
sItemBase* itemStats = &plr->Inven[req->iStatItemSlot];
|
||||
sItemBase* itemLooks = &plr->Inven[req->iCostumeItemSlot];
|
||||
Item* itemStatsDat = ItemManager::getItemData(itemStats->iID, itemStats->iType);
|
||||
Item* itemLooksDat = ItemManager::getItemData(itemLooks->iID, itemLooks->iType);
|
||||
|
||||
if (itemStatsDat == nullptr || itemLooksDat == nullptr
|
||||
|| ItemManager::CrocPotTable.find(abs(itemStatsDat->level - itemLooksDat->level)) == ItemManager::CrocPotTable.end()) { // sanity check 2
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp);
|
||||
failResp.iCostumeItemSlot = req->iCostumeItemSlot;
|
||||
failResp.iStatItemSlot = req->iStatItemSlot;
|
||||
failResp.iErrorCode = 0;
|
||||
std::cout << "[WARN] Either item ids or croc pot value set not found" << std::endl;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
CrocPotEntry* recipe = &ItemManager::CrocPotTable[abs(itemStatsDat->level - itemLooksDat->level)];
|
||||
int cost = itemStatsDat->buyPrice * recipe->multStats + itemLooksDat->buyPrice * recipe->multLooks;
|
||||
float successChance = recipe->base / 100.0f; // base success chance
|
||||
|
||||
// rarity gap multiplier
|
||||
switch(abs(itemStatsDat->rarity - itemLooksDat->rarity)) {
|
||||
case 0:
|
||||
successChance *= recipe->rd0;
|
||||
break;
|
||||
case 1:
|
||||
successChance *= recipe->rd1;
|
||||
break;
|
||||
case 2:
|
||||
successChance *= recipe->rd2;
|
||||
break;
|
||||
case 3:
|
||||
successChance *= recipe->rd3;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
float rolled = (rand() * 1.0f / RAND_MAX) * 100.0f; // success chance out of 100
|
||||
//std::cout << rolled << " vs " << successChance << std::endl;
|
||||
plr->money -= cost;
|
||||
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, resp);
|
||||
if (rolled < successChance) {
|
||||
// success
|
||||
resp.iSuccessFlag = 1;
|
||||
|
||||
// modify the looks item with the new stats and set the appearance through iOpt
|
||||
itemLooks->iOpt = (int32_t)((itemLooks->iOpt) >> 16 > 0 ? (itemLooks->iOpt >> 16) : itemLooks->iID) << 16;
|
||||
itemLooks->iID = itemStats->iID;
|
||||
|
||||
// delete stats item
|
||||
itemStats->iID = 0;
|
||||
itemStats->iOpt = 0;
|
||||
itemStats->iTimeLimit = 0;
|
||||
itemStats->iType = 0;
|
||||
} else {
|
||||
// failure; don't do anything?
|
||||
resp.iSuccessFlag = 0;
|
||||
}
|
||||
resp.iCandy = plr->money;
|
||||
resp.iNewItemSlot = req->iCostumeItemSlot;
|
||||
resp.iStatItemSlot = req->iStatItemSlot;
|
||||
resp.sNewItem = *itemLooks;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC));
|
||||
}
|
||||
|
||||
void NPCManager::npcBarkHandler(CNSocket* sock, CNPacketData* data) {} // stubbed for now
|
||||
|
||||
void NPCManager::npcUnsummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_NPC_UNSUMMON))
|
||||
return; // malformed packet
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr || plr->accountLevel > 30)
|
||||
if (plr->accountLevel > 30)
|
||||
return;
|
||||
|
||||
sP_CL2FE_REQ_NPC_UNSUMMON* req = (sP_CL2FE_REQ_NPC_UNSUMMON*)data->buf;
|
||||
NPCManager::destroyNPC(req->iNPC_ID);
|
||||
}
|
||||
|
||||
void NPCManager::npcSummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_NPC_SUMMON))
|
||||
return; // malformed packet
|
||||
// type must already be checked and updateNPCPosition() must be called on the result
|
||||
BaseNPC *NPCManager::summonNPC(int spawnX, int spawnY, int spawnZ, uint64_t instance, int type, bool respawn, bool baseInstance) {
|
||||
uint64_t inst = baseInstance ? MAPNUM(instance) : instance;
|
||||
|
||||
sP_CL2FE_REQ_NPC_SUMMON* req = (sP_CL2FE_REQ_NPC_SUMMON*)data->buf;
|
||||
int id = nextId--;
|
||||
int team = NPCData[type]["m_iTeam"];
|
||||
BaseNPC *npc = nullptr;
|
||||
|
||||
if (team == 2) {
|
||||
npc = new Mob(spawnX, spawnY, spawnZ, inst, type, NPCData[type], id);
|
||||
|
||||
// re-enable respawning, if desired
|
||||
((Mob*)npc)->summoned = !respawn;
|
||||
} else
|
||||
npc = new BaseNPC(0, inst, type, id);
|
||||
|
||||
NPCs[id] = npc;
|
||||
|
||||
return npc;
|
||||
}
|
||||
|
||||
static void npcSummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto req = (sP_CL2FE_REQ_NPC_SUMMON*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
int limit = NPCData.back()["m_iNpcNumber"];
|
||||
|
||||
// permission & sanity check
|
||||
if (plr == nullptr || plr->accountLevel > 30 || req->iNPCType >= 3314 || req->iNPCCnt > 100)
|
||||
if (plr->accountLevel > 30 || req->iNPCType > limit || req->iNPCCnt > 100)
|
||||
return;
|
||||
|
||||
int team = NPCData[req->iNPCType]["m_iTeam"];
|
||||
|
||||
for (int i = 0; i < req->iNPCCnt; i++) {
|
||||
assert(nextId < INT32_MAX);
|
||||
int id = nextId++;
|
||||
|
||||
if (team == 2) {
|
||||
NPCs[id] = new Mob(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType, NPCData[req->iNPCType], id);
|
||||
MobManager::Mobs[id] = (Mob*)NPCs[id];
|
||||
} else
|
||||
NPCs[id] = new BaseNPC(plr->x, plr->y, plr->z, 0, plr->instanceID, req->iNPCType, id);
|
||||
|
||||
updateNPCPosition(id, plr->x, plr->y, plr->z);
|
||||
BaseNPC *npc = summonNPC(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType);
|
||||
updateNPCPosition(npc->id, plr->x, plr->y, plr->z, plr->instanceID, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void NPCManager::npcWarpHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_WARP_USE_NPC))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_WARP_USE_NPC* warpNpc = (sP_CL2FE_REQ_PC_WARP_USE_NPC*)data->buf;
|
||||
handleWarp(sock, warpNpc->iWarpID);
|
||||
}
|
||||
|
||||
void NPCManager::npcWarpTimeMachine(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_TIME_TO_GO_WARP))
|
||||
return; // malformed packet
|
||||
// this is just a warp request
|
||||
handleWarp(sock, 28);
|
||||
}
|
||||
|
||||
void NPCManager::handleWarp(CNSocket* sock, int32_t warpId) {
|
||||
PlayerView& plrv = PlayerManager::players[sock];
|
||||
static void handleWarp(CNSocket* sock, int32_t warpId) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
// sanity check
|
||||
if (Warps.find(warpId) == Warps.end())
|
||||
return;
|
||||
|
||||
uint64_t fromInstance = plrv.plr->instanceID; // saved for post-warp
|
||||
if (plr->iPCState & 8) {
|
||||
// remove the player's vehicle
|
||||
plr->iPCState &= ~8;
|
||||
|
||||
// send to self
|
||||
INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, off);
|
||||
sock->sendPacket(off, P_FE2CL_PC_VEHICLE_OFF_SUCC);
|
||||
|
||||
// send to others
|
||||
INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, chg);
|
||||
chg.iPC_ID = plr->iID;
|
||||
chg.iState = plr->iPCState;
|
||||
PlayerManager::sendToViewable(sock, chg, P_FE2CL_PC_STATE_CHANGE);
|
||||
}
|
||||
|
||||
// std::cerr << "Warped to Map Num:" << Warps[warpId].instanceID << " NPC ID " << Warps[warpId].npcID << std::endl;
|
||||
if (Warps[warpId].isInstance) {
|
||||
uint64_t instanceID = Warps[warpId].instanceID;
|
||||
|
||||
Player* leader = plr;
|
||||
if (plr->group != nullptr) leader = PlayerManager::getPlayer(plr->group->filter(EntityKind::PLAYER)[0].sock);
|
||||
|
||||
// if warp requires you to be on a mission, it's gotta be a unique instance
|
||||
if (Warps[warpId].limitTaskID != 0 || instanceID == 14) { // 14 is a special case for the Time Lab
|
||||
instanceID += ((uint64_t)plrv.plr->iIDGroup << 32); // upper 32 bits are leader ID
|
||||
ChunkManager::createInstance(instanceID);
|
||||
instanceID += ((uint64_t)leader->iID << 32); // upper 32 bits are leader ID
|
||||
Chunking::createInstance(instanceID);
|
||||
|
||||
// save Lair entrance coords as a pseudo-Resurrect 'Em
|
||||
plr->recallX = Warps[warpId].x;
|
||||
plr->recallY = Warps[warpId].y;
|
||||
plr->recallZ = Warps[warpId].z + RESURRECT_HEIGHT;
|
||||
plr->recallInstance = instanceID;
|
||||
}
|
||||
|
||||
if (plrv.plr->iID == plrv.plr->iIDGroup && plrv.plr->groupCnt == 1)
|
||||
if (plr->group == nullptr)
|
||||
PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID);
|
||||
else {
|
||||
Player* leaderPlr = PlayerManager::getPlayerFromID(plrv.plr->iIDGroup);
|
||||
|
||||
for (int i = 0; i < leaderPlr->groupCnt; i++) {
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(leaderPlr->groupIDs[i]);
|
||||
CNSocket* sockTo = PlayerManager::getSockFromID(leaderPlr->groupIDs[i]);
|
||||
auto players = plr->group->filter(EntityKind::PLAYER);
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
CNSocket* sockTo = players[i].sock;
|
||||
Player* otherPlr = PlayerManager::getPlayer(sockTo);
|
||||
|
||||
if (otherPlr == nullptr || sockTo == nullptr)
|
||||
continue;
|
||||
|
||||
// save Lair entrance coords for everyone else as well
|
||||
otherPlr->recallX = Warps[warpId].x;
|
||||
otherPlr->recallY = Warps[warpId].y;
|
||||
otherPlr->recallZ = Warps[warpId].z + RESURRECT_HEIGHT;
|
||||
otherPlr->recallInstance = instanceID;
|
||||
|
||||
// remove their vehicle if they're on one
|
||||
if (otherPlr->iPCState & 8) {
|
||||
// remove the player's vehicle
|
||||
otherPlr->iPCState &= ~8;
|
||||
|
||||
// send to self
|
||||
INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, off);
|
||||
sockTo->sendPacket(off, P_FE2CL_PC_VEHICLE_OFF_SUCC);
|
||||
|
||||
// send to others
|
||||
INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, chg);
|
||||
chg.iPC_ID = otherPlr->iID;
|
||||
chg.iState = otherPlr->iPCState;
|
||||
PlayerManager::sendToViewable(sockTo, chg, P_FE2CL_PC_STATE_CHANGE);
|
||||
}
|
||||
|
||||
PlayerManager::sendPlayerTo(sockTo, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC, resp); //Can only be used for exiting instances because it sets the instance flag to false
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC, resp); // Can only be used for exiting instances because it sets the instance flag to false
|
||||
resp.iX = Warps[warpId].x;
|
||||
resp.iY = Warps[warpId].y;
|
||||
resp.iZ = Warps[warpId].z;
|
||||
resp.iCandy = plrv.plr->money;
|
||||
resp.iCandy = plr->money;
|
||||
resp.eIL = 4; // do not take away any items
|
||||
PlayerManager::removePlayerFromChunks(plrv.currentChunks, sock);
|
||||
plrv.currentChunks.clear();
|
||||
plrv.plr->instanceID = INSTANCE_OVERWORLD;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC, sizeof(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC));
|
||||
}
|
||||
uint64_t fromInstance = plr->instanceID; // pre-warp instance, saved for post-warp
|
||||
plr->instanceID = INSTANCE_OVERWORLD;
|
||||
Missions::failInstancedMissions(sock); // fail any instanced missions
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC);
|
||||
|
||||
// post-warp: check if the source instance has no more players in it and delete it if so
|
||||
ChunkManager::destroyInstanceIfEmpty(fromInstance);
|
||||
PlayerManager::updatePlayerPositionForWarp(sock, resp.iX, resp.iY, resp.iZ, INSTANCE_OVERWORLD);
|
||||
|
||||
// remove the player's ongoing race, if any
|
||||
if (Racing::EPRaces.find(sock) != Racing::EPRaces.end())
|
||||
Racing::EPRaces.erase(sock);
|
||||
|
||||
// post-warp: check if the source instance has no more players in it and delete it if so
|
||||
Chunking::destroyInstanceIfEmpty(fromInstance);
|
||||
}
|
||||
}
|
||||
|
||||
static void npcWarpHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto warpNpc = (sP_CL2FE_REQ_PC_WARP_USE_NPC*)data->buf;
|
||||
handleWarp(sock, warpNpc->iWarpID);
|
||||
}
|
||||
|
||||
static void npcWarpTimeMachine(CNSocket* sock, CNPacketData* data) {
|
||||
// this is just a warp request
|
||||
handleWarp(sock, 28);
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function to get NPC closest to coordinates in specified chunks
|
||||
*/
|
||||
BaseNPC* NPCManager::getNearestNPC(std::vector<Chunk*> chunks, int X, int Y, int Z) {
|
||||
BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z) {
|
||||
BaseNPC* npc = nullptr;
|
||||
int lastDist = INT_MAX;
|
||||
for (auto c = chunks.begin(); c != chunks.end(); c++) { // haha get it
|
||||
for (auto c = chunks->begin(); c != chunks->end(); c++) { // haha get it
|
||||
Chunk* chunk = *c;
|
||||
for (auto _npc = chunk->NPCs.begin(); _npc != chunk->NPCs.end(); _npc++) {
|
||||
BaseNPC* npcTemp = NPCs[*_npc];
|
||||
int distXY = std::hypot(X - npcTemp->appearanceData.iX, Y - npcTemp->appearanceData.iY);
|
||||
int dist = std::hypot(distXY, Z - npcTemp->appearanceData.iZ);
|
||||
for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) {
|
||||
if (ent->kind == EntityKind::PLAYER)
|
||||
continue;
|
||||
|
||||
BaseNPC* npcTemp = (BaseNPC*)ent->getEntity();
|
||||
int distXY = std::hypot(X - npcTemp->x, Y - npcTemp->y);
|
||||
int dist = std::hypot(distXY, Z - npcTemp->z);
|
||||
if (dist < lastDist) {
|
||||
npc = npcTemp;
|
||||
lastDist = dist;
|
||||
@@ -650,3 +292,86 @@ BaseNPC* NPCManager::getNearestNPC(std::vector<Chunk*> chunks, int X, int Y, int
|
||||
}
|
||||
return npc;
|
||||
}
|
||||
|
||||
// TODO: Move this to separate file in ai/ subdir when implementing more events
|
||||
#pragma region NPCEvents
|
||||
|
||||
// summon right arm and stage 2 body
|
||||
static void lordFuseStageTwo(CombatNPC *npc) {
|
||||
Mob *oldbody = (Mob*)npc; // adaptium, stun
|
||||
|
||||
std::cout << "Lord Fuse stage two" << std::endl;
|
||||
|
||||
// Fuse doesn't move
|
||||
// Blastons, Heal
|
||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, oldbody->instanceID, 2467);
|
||||
|
||||
newbody->angle = oldbody->angle;
|
||||
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
||||
oldbody->instanceID, oldbody->angle);
|
||||
|
||||
// right arm, Adaptium, Stun
|
||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, oldbody->instanceID, 2469);
|
||||
|
||||
arm->angle = oldbody->angle;
|
||||
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ,
|
||||
oldbody->instanceID, oldbody->angle);
|
||||
}
|
||||
|
||||
// summon left arm and stage 3 body
|
||||
static void lordFuseStageThree(CombatNPC *npc) {
|
||||
Mob *oldbody = (Mob*)npc;
|
||||
|
||||
std::cout << "Lord Fuse stage three" << std::endl;
|
||||
|
||||
// Cosmix, Damage Point
|
||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, oldbody->instanceID, 2468);
|
||||
|
||||
newbody->angle = oldbody->angle;
|
||||
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
||||
newbody->instanceID, oldbody->angle);
|
||||
|
||||
// Blastons, Heal
|
||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, oldbody->instanceID, 2470);
|
||||
|
||||
arm->angle = oldbody->angle;
|
||||
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ,
|
||||
arm->instanceID, oldbody->angle);
|
||||
}
|
||||
|
||||
std::vector<NPCEvent> NPCManager::NPCEvents = {
|
||||
NPCEvent(2466, AIState::DEAD, lordFuseStageTwo),
|
||||
NPCEvent(2467, AIState::DEAD, lordFuseStageThree),
|
||||
};
|
||||
|
||||
#pragma endregion NPCEvents
|
||||
|
||||
void NPCManager::queueNPCRemoval(int32_t id) {
|
||||
RemovalQueue.push(id);
|
||||
}
|
||||
|
||||
static void step(CNServer *serv, time_t currTime) {
|
||||
for (auto& pair : NPCs) {
|
||||
if (pair.second->kind != EntityKind::COMBAT_NPC && pair.second->kind != EntityKind::MOB)
|
||||
continue;
|
||||
auto npc = (CombatNPC*)pair.second;
|
||||
|
||||
npc->step(currTime);
|
||||
}
|
||||
|
||||
// deallocate all NPCs queued for removal
|
||||
while (RemovalQueue.size() > 0) {
|
||||
NPCManager::destroyNPC(RemovalQueue.front());
|
||||
RemovalQueue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void NPCManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_WARP_USE_NPC, npcWarpHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TIME_TO_GO_WARP, npcWarpTimeMachine);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_SUMMON, npcSummonHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
|
||||
|
||||
REGISTER_SHARD_TIMER(step, MS_PER_COMBAT_TICK);
|
||||
}
|
||||
|
||||
@@ -1,53 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNProtocol.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPC.hpp"
|
||||
#include "core/Core.hpp"
|
||||
#include "JSON.hpp"
|
||||
|
||||
#include "contrib/JSON.hpp"
|
||||
#include "Transport.hpp"
|
||||
#include "Chunking.hpp"
|
||||
#include "Entities.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#define RESURRECT_HEIGHT 400
|
||||
|
||||
// this should really be called vec3 or something...
|
||||
struct WarpLocation {
|
||||
int x, y, z, instanceID, isInstance, limitTaskID, npcID;
|
||||
typedef void (*NPCEventHandler)(CombatNPC*);
|
||||
|
||||
struct NPCEvent {
|
||||
int32_t npcType;
|
||||
AIState triggerState;
|
||||
NPCEventHandler handler;
|
||||
|
||||
NPCEvent(int32_t t, AIState tr, NPCEventHandler hndlr)
|
||||
: npcType(t), triggerState(tr), handler(hndlr) {}
|
||||
};
|
||||
|
||||
namespace NPCManager {
|
||||
extern std::map<int32_t, BaseNPC*> NPCs;
|
||||
extern std::unordered_map<int32_t, BaseNPC*> NPCs;
|
||||
extern std::map<int32_t, WarpLocation> Warps;
|
||||
extern std::vector<WarpLocation> RespawnPoints;
|
||||
extern std::vector<NPCEvent> NPCEvents;
|
||||
extern nlohmann::json NPCData;
|
||||
extern int32_t nextId;
|
||||
void init();
|
||||
|
||||
void addNPC(std::vector<Chunk*> viewableChunks, int32_t id);
|
||||
void removeNPC(std::vector<Chunk*> viewableChunks, int32_t id);
|
||||
void queueNPCRemoval(int32_t);
|
||||
void destroyNPC(int32_t);
|
||||
void updateNPCPosition(int32_t, int X, int Y, int Z, int angle);
|
||||
void updateNPCPosition(int32_t, int X, int Y, int Z);
|
||||
void updateNPCInstance(int32_t, uint64_t instanceID);
|
||||
void updateNPCPosition(int32_t, int X, int Y, int Z, uint64_t I, int angle);
|
||||
|
||||
void sendToViewable(BaseNPC* npc, void* buf, uint32_t type, size_t size);
|
||||
void sendToViewable(Entity* npc, void* buf, uint32_t type, size_t size);
|
||||
|
||||
void npcBarkHandler(CNSocket* sock, CNPacketData* data);
|
||||
void npcSummonHandler(CNSocket* sock, CNPacketData* data);
|
||||
void npcUnsummonHandler(CNSocket* sock, CNPacketData* data);
|
||||
void npcWarpHandler(CNSocket* sock, CNPacketData* data);
|
||||
void npcWarpTimeMachine(CNSocket* sock, CNPacketData* data);
|
||||
BaseNPC *summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn=false, bool baseInstance=false);
|
||||
|
||||
void npcVendorStart(CNSocket* sock, CNPacketData* data);
|
||||
void npcVendorTable(CNSocket* sock, CNPacketData* data);
|
||||
void npcVendorBuy(CNSocket* sock, CNPacketData* data);
|
||||
void npcVendorSell(CNSocket* sock, CNPacketData* data);
|
||||
void npcVendorBuyback(CNSocket* sock, CNPacketData* data);
|
||||
void npcVendorBuyBattery(CNSocket* sock, CNPacketData* data);
|
||||
void npcCombineItems(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
void handleWarp(CNSocket* sock, int32_t warpId);
|
||||
|
||||
BaseNPC* getNearestNPC(std::vector<Chunk*> chunks, int X, int Y, int Z);
|
||||
BaseNPC* getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z);
|
||||
}
|
||||
|
||||
@@ -1,973 +0,0 @@
|
||||
#include "CNShardServer.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
#include "GroupManager.hpp"
|
||||
|
||||
namespace NanoManager {
|
||||
|
||||
// active powers
|
||||
std::set<int> StunPowers = {1, 13, 42, 59, 78, 103};
|
||||
std::set<int> HealPowers = {7, 12, 38, 53, 92, 98};
|
||||
std::set<int> GroupHealPowers = {2, 61, 82};
|
||||
std::set<int> RecallPowers = {5, 25, 66, 69, 75, 87};
|
||||
std::set<int> DrainPowers = {10, 34, 37, 56, 93, 97};
|
||||
std::set<int> SnarePowers = {17, 18, 27, 41, 43, 47, 90, 96, 106};
|
||||
std::set<int> DamagePowers = {19, 21, 33, 45, 46, 52, 101, 105, 108};
|
||||
std::set<int> GroupRevivePowers = {20, 63, 91};
|
||||
std::set<int> LeechPowers = {24, 51, 89};
|
||||
std::set<int> SleepPowers = {28, 30, 32, 49, 70, 71, 81, 85, 94};
|
||||
|
||||
// passive powers
|
||||
std::set<int> ScavengePowers = {3, 50, 99};
|
||||
std::set<int> RunPowers = {4, 68, 86};
|
||||
std::set<int> GroupRunPowers = {8, 62, 73};
|
||||
std::set<int> BonusPowers = {6, 54, 104};
|
||||
std::set<int> GuardPowers = {9, 57, 76};
|
||||
std::set<int> RadarPowers = {11, 67, 95};
|
||||
std::set<int> AntidotePowers = {14, 58, 102};
|
||||
std::set<int> FreedomPowers = {31, 39, 107};
|
||||
std::set<int> GroupFreedomPowers = {15, 55, 77};
|
||||
std::set<int> JumpPowers = {16, 44, 88};
|
||||
std::set<int> GroupJumpPowers = {35, 60, 100};
|
||||
std::set<int> SelfRevivePowers = {22, 48, 84};
|
||||
std::set<int> SneakPowers = {29, 72, 80};
|
||||
std::set<int> GroupSneakPowers = {23, 65, 83};
|
||||
std::set<int> TreasureFinderPowers = {26, 40, 74};
|
||||
|
||||
/*
|
||||
* The active nano power table is down below activePower<>() and its
|
||||
* worker functions so we don't have to have unsightly function declarations.
|
||||
*/
|
||||
|
||||
}; // namespace
|
||||
|
||||
std::map<int32_t, NanoData> NanoManager::NanoTable;
|
||||
std::map<int32_t, NanoTuning> NanoManager::NanoTunings;
|
||||
|
||||
void NanoManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_ACTIVE, nanoSummonHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_EQUIP, nanoEquipHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_UNEQUIP, nanoUnEquipHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GIVE_NANO, nanoGMGiveHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_TUNE, nanoSkillSetHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GIVE_NANO_SKILL, nanoSkillSetGMHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_SKILL_USE, nanoSkillUseHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_WARP_USE_RECALL, nanoRecallHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_CHARGE_NANO_STAMINA, nanoPotionHandler);
|
||||
}
|
||||
|
||||
void NanoManager::nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_NANO_EQUIP))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_NANO_EQUIP* nano = (sP_CL2FE_REQ_NANO_EQUIP*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_EQUIP_SUCC, resp);
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// sanity checks
|
||||
if (plr == nullptr || nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
|
||||
return;
|
||||
|
||||
resp.iNanoID = nano->iNanoID;
|
||||
resp.iNanoSlotNum = nano->iNanoSlotNum;
|
||||
|
||||
// Update player
|
||||
plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID;
|
||||
|
||||
// unsummon nano if replaced
|
||||
if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum])
|
||||
summonNano(sock, -1);
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_NANO_EQUIP_SUCC, sizeof(sP_FE2CL_REP_NANO_EQUIP_SUCC));
|
||||
}
|
||||
|
||||
void NanoManager::nanoUnEquipHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_NANO_UNEQUIP))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_NANO_UNEQUIP* nano = (sP_CL2FE_REQ_NANO_UNEQUIP*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_UNEQUIP_SUCC, resp);
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// sanity check
|
||||
if (plr == nullptr || nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
|
||||
return;
|
||||
|
||||
resp.iNanoSlotNum = nano->iNanoSlotNum;
|
||||
|
||||
// unsummon nano if removed
|
||||
if (plr->equippedNanos[nano->iNanoSlotNum] == plr->activeNano)
|
||||
summonNano(sock, -1);
|
||||
|
||||
// update player
|
||||
plr->equippedNanos[nano->iNanoSlotNum] = 0;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_NANO_UNEQUIP_SUCC, sizeof(sP_FE2CL_REP_NANO_UNEQUIP_SUCC));
|
||||
}
|
||||
|
||||
void NanoManager::nanoGMGiveHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_GIVE_NANO))
|
||||
return; // ignore the malformed packet
|
||||
|
||||
// Cmd: /nano <nanoId>
|
||||
sP_CL2FE_REQ_PC_GIVE_NANO* nano = (sP_CL2FE_REQ_PC_GIVE_NANO*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
// Add nano to player
|
||||
addNano(sock, nano->iNanoID, 0);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << U16toU8(plr->PCStyle.szFirstName) << U16toU8(plr->PCStyle.szLastName) << " requested to add nano id: " << nano->iNanoID << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
void NanoManager::nanoSummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_NANO_ACTIVE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_NANO_ACTIVE* pkt = (sP_CL2FE_REQ_NANO_ACTIVE*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
summonNano(sock, pkt->iNanoSlotNum);
|
||||
|
||||
// Send to client
|
||||
DEBUGLOG(
|
||||
std::cout << U16toU8(plr->PCStyle.szFirstName) << U16toU8(plr->PCStyle.szLastName) << " requested to summon nano slot: " << pkt->iNanoSlotNum << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
void NanoManager::nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
int16_t nanoId = plr->activeNano;
|
||||
int16_t skillId = plr->Nanos[nanoId].iSkillID;
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << U16toU8(plr->PCStyle.szFirstName) << U16toU8(plr->PCStyle.szLastName) << " requested to summon nano skill " << std::endl;
|
||||
)
|
||||
|
||||
for (auto& pwr : ActivePowers)
|
||||
if (pwr.powers.count(skillId)) // std::set's contains method is C++20 only...
|
||||
pwr.handle(sock, data, nanoId, skillId);
|
||||
|
||||
// Group Revive is handled separately (XXX: move into table?)
|
||||
if (GroupRevivePowers.find(skillId) == GroupRevivePowers.end())
|
||||
return;
|
||||
|
||||
Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (leader == nullptr)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < leader->groupCnt; i++) {
|
||||
Player* varPlr = PlayerManager::getPlayerFromID(leader->groupIDs[i]);
|
||||
|
||||
if (varPlr == nullptr)
|
||||
return;
|
||||
|
||||
if (varPlr->HP <= 0)
|
||||
revivePlayer(varPlr);
|
||||
}
|
||||
}
|
||||
|
||||
void NanoManager::nanoSkillSetHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_NANO_TUNE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_NANO_TUNE* skill = (sP_CL2FE_REQ_NANO_TUNE*)data->buf;
|
||||
setNanoSkill(sock, skill);
|
||||
}
|
||||
|
||||
void NanoManager::nanoSkillSetGMHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_GIVE_NANO_SKILL))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_NANO_TUNE* skillGM = (sP_CL2FE_REQ_NANO_TUNE*)data->buf;
|
||||
setNanoSkill(sock, skillGM);
|
||||
}
|
||||
|
||||
void NanoManager::nanoRecallHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_WARP_USE_RECALL))
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_WARP_USE_RECALL_FAIL, resp);
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_WARP_USE_RECALL_FAIL, sizeof(sP_FE2CL_REP_WARP_USE_RECALL_FAIL));
|
||||
// stubbed for now
|
||||
}
|
||||
|
||||
void NanoManager::nanoPotionHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_CHARGE_NANO_STAMINA))
|
||||
return;
|
||||
|
||||
Player* player = PlayerManager::getPlayer(sock);
|
||||
|
||||
// sanity checks
|
||||
if (player == nullptr || player->activeNano == -1 || player->batteryN == 0)
|
||||
return;
|
||||
|
||||
sNano nano = player->Nanos[player->activeNano];
|
||||
int difference = 150 - nano.iStamina;
|
||||
if (player->batteryN < difference)
|
||||
difference = player->batteryN;
|
||||
|
||||
if (difference == 0)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_CHARGE_NANO_STAMINA, response);
|
||||
response.iNanoID = nano.iID;
|
||||
response.iNanoStamina = nano.iStamina + difference;
|
||||
response.iBatteryN = player->batteryN - difference;
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_CHARGE_NANO_STAMINA, sizeof(sP_FE2CL_REP_CHARGE_NANO_STAMINA));
|
||||
// now update serverside
|
||||
player->batteryN -= difference;
|
||||
player->Nanos[nano.iID].iStamina += difference;
|
||||
|
||||
}
|
||||
|
||||
#pragma region Helper methods
|
||||
void NanoManager::addNano(CNSocket* sock, int16_t nanoId, int16_t slot, bool spendfm) {
|
||||
if (nanoId > 36)
|
||||
return;
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
int level = nanoId < plr->level ? plr->level : nanoId;
|
||||
|
||||
/*
|
||||
* Spend the necessary Fusion Matter.
|
||||
* Note the use of the not-yet-incremented plr->level as opposed to level.
|
||||
* Doing it the other way always leaves the FM at 0. Jade totally called it.
|
||||
*/
|
||||
plr->level = level;
|
||||
|
||||
if (spendfm)
|
||||
MissionManager::updateFusionMatter(sock, -(int)MissionManager::AvatarGrowth[plr->level-1]["m_iReqBlob_NanoCreate"]);
|
||||
|
||||
// Send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_NANO_CREATE_SUCC, resp);
|
||||
resp.Nano.iID = nanoId;
|
||||
resp.Nano.iStamina = 150;
|
||||
resp.iQuestItemSlotNum = slot;
|
||||
resp.iPC_Level = level;
|
||||
resp.iPC_FusionMatter = plr->fusionmatter;
|
||||
|
||||
if (plr->activeNano > 0 && plr->activeNano == nanoId)
|
||||
summonNano(sock, -1); // just unsummon the nano to prevent infinite buffs
|
||||
|
||||
// Update player
|
||||
plr->Nanos[nanoId] = resp.Nano;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_NANO_CREATE_SUCC, sizeof(sP_FE2CL_REP_PC_NANO_CREATE_SUCC));
|
||||
|
||||
/*
|
||||
* iPC_Level in NANO_CREATE_SUCC sets the player's level.
|
||||
* Other players must be notified of the change as well. Both P_FE2CL_REP_PC_NANO_CREATE and
|
||||
* P_FE2CL_REP_PC_CHANGE_LEVEL appear to play the same animation, but only the latter affects
|
||||
* the other player's displayed level.
|
||||
*/
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_LEVEL, resp2);
|
||||
|
||||
resp2.iPC_ID = plr->iID;
|
||||
resp2.iPC_Level = level;
|
||||
|
||||
// Update other players' perception of the player's level
|
||||
PlayerManager::sendToViewable(sock, (void*)&resp2, P_FE2CL_REP_PC_CHANGE_LEVEL, sizeof(sP_FE2CL_REP_PC_CHANGE_LEVEL));
|
||||
}
|
||||
|
||||
void NanoManager::summonNano(CNSocket *sock, int slot) {
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_ACTIVE_SUCC, resp);
|
||||
resp.iActiveNanoSlotNum = slot;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr || slot > 2 || slot < -1)
|
||||
return; // sanity check
|
||||
|
||||
int16_t nanoId = slot == -1 ? -1 : plr->equippedNanos[slot];
|
||||
|
||||
if (nanoId > 36 || nanoId < -1)
|
||||
return; // sanity check
|
||||
|
||||
int16_t skillId = 0;
|
||||
|
||||
if (plr->activeNano > 0)
|
||||
for (auto& pwr : PassivePowers)
|
||||
if (pwr.powers.count(plr->Nanos[plr->activeNano].iSkillID)) { // std::set's contains method is C++20 only...
|
||||
nanoUnbuff(sock, pwr.iCBFlag, pwr.eCharStatusTimeBuffID, pwr.iValue, pwr.groupPower);
|
||||
plr->passiveNanoOut = false;
|
||||
}
|
||||
|
||||
sNano nano = plr->Nanos[nanoId];
|
||||
skillId = nano.iSkillID;
|
||||
|
||||
if (slot > -1) {
|
||||
plr->activeNano = nanoId;
|
||||
|
||||
for (auto& pwr : PassivePowers)
|
||||
if (pwr.powers.count(skillId)) { // std::set's contains method is C++20 only...
|
||||
resp.eCSTB___Add = 1;
|
||||
nanoBuff(sock, nanoId, skillId, pwr.eSkillType, pwr.iCBFlag, pwr.eCharStatusTimeBuffID, pwr.iValue, pwr.groupPower);
|
||||
plr->passiveNanoOut = true;
|
||||
}
|
||||
} else
|
||||
plr->activeNano = 0;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_NANO_ACTIVE_SUCC, sizeof(sP_FE2CL_REP_NANO_ACTIVE_SUCC));
|
||||
|
||||
// Send to other players
|
||||
INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1);
|
||||
|
||||
pkt1.iPC_ID = plr->iID;
|
||||
|
||||
if (nanoId == -1)
|
||||
memset(&pkt1.Nano, 0, sizeof(pkt1.Nano));
|
||||
else
|
||||
pkt1.Nano = plr->Nanos[nanoId];
|
||||
|
||||
PlayerManager::sendToViewable(sock, (void*)&pkt1, P_FE2CL_NANO_ACTIVE, sizeof(sP_FE2CL_NANO_ACTIVE));
|
||||
|
||||
// update player
|
||||
plr->activeNano = nanoId;
|
||||
}
|
||||
|
||||
void NanoManager::setNanoSkill(CNSocket* sock, sP_CL2FE_REQ_NANO_TUNE* skill) {
|
||||
if (skill->iNanoID > 36)
|
||||
return;
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
if (plr->activeNano > 0 && plr->activeNano == skill->iNanoID)
|
||||
summonNano(sock, -1); // just unsummon the nano to prevent infinite buffs
|
||||
|
||||
sNano nano = plr->Nanos[skill->iNanoID];
|
||||
nano.iSkillID = skill->iTuneID;
|
||||
plr->Nanos[skill->iNanoID] = nano;
|
||||
|
||||
// Send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_TUNE_SUCC, resp);
|
||||
resp.iNanoID = skill->iNanoID;
|
||||
resp.iSkillID = skill->iTuneID;
|
||||
resp.iPC_FusionMatter = plr->fusionmatter;
|
||||
resp.aItem[9] = plr->Inven[0]; // quick fix to make sure item in slot 0 doesn't get yeeted by default
|
||||
|
||||
|
||||
// check if there's any garbage in the item slot array (this'll happen when a nano station isn't used)
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (skill->aiNeedItemSlotNum[i] < 0 || skill->aiNeedItemSlotNum[i] >= AINVEN_COUNT) {
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_NANO_TUNE_SUCC, sizeof(sP_FE2CL_REP_NANO_TUNE_SUCC));
|
||||
return; // stop execution, don't run consumption logic
|
||||
}
|
||||
}
|
||||
|
||||
if (plr->fusionmatter < (int)MissionManager::AvatarGrowth[plr->level]["m_iReqBlob_NanoTune"]) // sanity check
|
||||
return;
|
||||
|
||||
plr->fusionmatter -= (int)MissionManager::AvatarGrowth[plr->level]["m_iReqBlob_NanoTune"];
|
||||
|
||||
int reqItemCount = NanoTunings[skill->iTuneID].reqItemCount;
|
||||
int reqItemID = NanoTunings[skill->iTuneID].reqItems;
|
||||
int i = 0;
|
||||
while (reqItemCount > 0 && i < 10) {
|
||||
|
||||
sItemBase& item = plr->Inven[skill->aiNeedItemSlotNum[i]];
|
||||
if (item.iType == 7 && item.iID == reqItemID) {
|
||||
if (item.iOpt > reqItemCount) {
|
||||
item.iOpt -= reqItemCount;
|
||||
reqItemCount = 0;
|
||||
}
|
||||
else {
|
||||
reqItemCount -= item.iOpt;
|
||||
item.iID = 0;
|
||||
item.iType = 0;
|
||||
item.iOpt = 0;
|
||||
}
|
||||
}
|
||||
i++; // next slot
|
||||
}
|
||||
|
||||
resp.iPC_FusionMatter = plr->fusionmatter; // update fusion matter in packet
|
||||
// update items clientside
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (skill->aiNeedItemSlotNum[i]) { // non-zero check
|
||||
resp.aItem[i] = plr->Inven[skill->aiNeedItemSlotNum[i]];
|
||||
resp.aiItemSlotNum[i] = skill->aiNeedItemSlotNum[i];
|
||||
}
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_NANO_TUNE_SUCC, sizeof(sP_FE2CL_REP_NANO_TUNE_SUCC));
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << U16toU8(plr->PCStyle.szFirstName) << U16toU8(plr->PCStyle.szLastName) << " set skill id " << skill->iTuneID << " for nano: " << skill->iNanoID << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
void NanoManager::resetNanoSkill(CNSocket* sock, int16_t nanoId) {
|
||||
if (nanoId > 36)
|
||||
return;
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
sNano nano = plr->Nanos[nanoId];
|
||||
|
||||
// 0 is reset
|
||||
nano.iSkillID = 0;
|
||||
plr->Nanos[nanoId] = nano;
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Active Powers
|
||||
namespace NanoManager {
|
||||
|
||||
bool doDebuff(CNSocket *sock, int32_t *pktdata, sSkillResult_Damage_N_Debuff *respdata, int i, int32_t iCBFlag, int32_t amount) {
|
||||
if (MobManager::Mobs.find(pktdata[i]) == MobManager::Mobs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] nanoDebuffEnemy: mob ID not found" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
Mob* mob = MobManager::Mobs[pktdata[i]];
|
||||
|
||||
int damage = MobManager::hitMob(sock, mob, 0); // using amount for something else
|
||||
|
||||
respdata[i].eCT = 4;
|
||||
respdata[i].iDamage = damage;
|
||||
respdata[i].iID = mob->appearanceData.iNPC_ID;
|
||||
respdata[i].iHP = mob->appearanceData.iHP;
|
||||
respdata[i].iConditionBitFlag = mob->appearanceData.iConditionBitFlag |= iCBFlag;
|
||||
mob->unbuffTimes[iCBFlag] = getTime() + amount;
|
||||
std::cout << (int)mob->appearanceData.iNPC_ID << " was debuffed" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool doBuff(CNSocket *sock, int32_t *pktdata, sSkillResult_Buff *respdata, int i, int32_t iCBFlag, int32_t amount) {
|
||||
if (MobManager::Mobs.find(pktdata[i]) == MobManager::Mobs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] nanoBuffEnemy: mob ID not found" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
Mob* mob = MobManager::Mobs[pktdata[i]];
|
||||
MobManager::hitMob(sock, mob, 0);
|
||||
|
||||
respdata[i].eCT = 4;
|
||||
respdata[i].iID = mob->appearanceData.iNPC_ID;
|
||||
respdata[i].iConditionBitFlag = mob->appearanceData.iConditionBitFlag |= iCBFlag;
|
||||
mob->unbuffTimes[iCBFlag] = getTime() + amount;
|
||||
|
||||
std::cout << (int)mob->appearanceData.iNPC_ID << " was debuffed" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool doHeal(CNSocket *sock, int32_t *pktdata, sSkillResult_Heal_HP *respdata, int i, int32_t iCBFlag, int32_t amount) {
|
||||
Player *plr = nullptr;
|
||||
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
if (pair.second.plr->iID == pktdata[i]) {
|
||||
plr = pair.second.plr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// player not found
|
||||
if (plr == nullptr)
|
||||
return false;
|
||||
|
||||
int healedAmount = PC_MAXHEALTH(plr->level) * amount / 100;
|
||||
|
||||
plr->HP += healedAmount;
|
||||
|
||||
if (plr->HP > PC_MAXHEALTH(plr->level))
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
|
||||
respdata[i].eCT = 1;
|
||||
respdata[i].iID = plr->iID;
|
||||
respdata[i].iHP = plr->HP;
|
||||
respdata[i].iHealHP = healedAmount;
|
||||
|
||||
std::cout << (int)plr->iID << " was healed" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool doGroupHeal(CNSocket *sock, int32_t *pktdata, sSkillResult_Heal_HP *respdata, int i, int32_t iCBFlag, int32_t amount) {
|
||||
Player *plr = nullptr;
|
||||
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
if (pair.second.plr->iID == pktdata[0]) {
|
||||
plr = pair.second.plr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// player not found
|
||||
if (plr == nullptr)
|
||||
return false;
|
||||
|
||||
Player *leader = PlayerManager::getPlayer(sock);
|
||||
|
||||
// player not found
|
||||
if (leader == nullptr)
|
||||
return false;
|
||||
|
||||
int healedAmount = PC_MAXHEALTH(plr->level) * amount / 100;
|
||||
|
||||
leader->HP += healedAmount;
|
||||
|
||||
if (leader->HP > PC_MAXHEALTH(leader->level))
|
||||
leader->HP = PC_MAXHEALTH(leader->level);
|
||||
|
||||
respdata[i].eCT = 1;
|
||||
respdata[i].iID = plr->iID;
|
||||
respdata[i].iHP = plr->HP;
|
||||
respdata[i].iHealHP = healedAmount;
|
||||
|
||||
std::cout << (int)plr->iID << " was healed" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool doDamage(CNSocket *sock, int32_t *pktdata, sSkillResult_Damage *respdata, int i, int32_t iCBFlag, int32_t amount) {
|
||||
if (MobManager::Mobs.find(pktdata[i]) == MobManager::Mobs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] nanoDebuffEnemy: mob ID not found" << std::endl;
|
||||
return false;
|
||||
}
|
||||
Mob* mob = MobManager::Mobs[pktdata[i]];
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return false;
|
||||
|
||||
int damage = MobManager::hitMob(sock, mob, PC_MAXHEALTH(plr->level) * amount / 100);
|
||||
|
||||
respdata[i].eCT = 4;
|
||||
respdata[i].iDamage = damage;
|
||||
respdata[i].iID = mob->appearanceData.iNPC_ID;
|
||||
respdata[i].iHP = mob->appearanceData.iHP;
|
||||
|
||||
std::cout << (int)mob->appearanceData.iNPC_ID << " was damaged" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: Leech is specially encoded.
|
||||
*
|
||||
* It manages to fit inside the activePower<>() mold with only a slight hack,
|
||||
* but it really is it's own thing. There is a hard assumption that players
|
||||
* will only every leech a single mob, and the sanity check that enforces that
|
||||
* assumption is critical.
|
||||
*/
|
||||
bool doLeech(CNSocket *sock, int32_t *pktdata, sSkillResult_Heal_HP *healdata, int i, int32_t iCBFlag, int32_t amount) {
|
||||
// this sanity check is VERY important
|
||||
if (i != 0) {
|
||||
std::cout << "[WARN] Player attempted to leech more than one mob!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
sSkillResult_Damage *damagedata = (sSkillResult_Damage*)(((uint8_t*)healdata) + sizeof(sSkillResult_Heal_HP));
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return false;
|
||||
|
||||
int healedAmount = PC_MAXHEALTH(plr->level) * amount / 100;
|
||||
|
||||
plr->HP += healedAmount;
|
||||
|
||||
if (plr->HP > PC_MAXHEALTH(plr->level))
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
|
||||
healdata->eCT = 1;
|
||||
healdata->iID = plr->iID;
|
||||
healdata->iHP = plr->HP;
|
||||
healdata->iHealHP = healedAmount;
|
||||
|
||||
if (MobManager::Mobs.find(pktdata[i]) == MobManager::Mobs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] doLeech: mob ID not found" << std::endl;
|
||||
return false;
|
||||
}
|
||||
Mob* mob = MobManager::Mobs[pktdata[i]];
|
||||
|
||||
int damage = MobManager::hitMob(sock, mob, PC_MAXHEALTH(plr->level) * amount / 100);
|
||||
|
||||
damagedata->eCT = 4;
|
||||
damagedata->iDamage = damage;
|
||||
damagedata->iID = mob->appearanceData.iNPC_ID;
|
||||
damagedata->iHP = mob->appearanceData.iHP;
|
||||
|
||||
std::cout << (int)mob->appearanceData.iNPC_ID << " was leeched" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// XXX: Special flags. This is still pretty dirty.
|
||||
enum {
|
||||
NONE,
|
||||
LEECH,
|
||||
GHEAL
|
||||
};
|
||||
|
||||
template<class sPAYLOAD,
|
||||
bool (*work)(CNSocket*,int32_t*,sPAYLOAD*,int,int32_t,int32_t),
|
||||
int specialCase=NONE>
|
||||
void activePower(CNSocket *sock, CNPacketData *data,
|
||||
int16_t nanoId, int16_t skillId, int16_t eSkillType,
|
||||
int32_t iCBFlag, int32_t amount) {
|
||||
|
||||
sP_CL2FE_REQ_NANO_SKILL_USE* pkt = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf;
|
||||
|
||||
// validate request check
|
||||
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_NANO_SKILL_USE), pkt->iTargetCnt, sizeof(int32_t), data->size)) {
|
||||
std::cout << "[WARN] bad sP_CL2FE_REQ_NANO_SKILL_USE packet size" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_NANO_SKILL_USE));
|
||||
|
||||
size_t resplen;
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
Player *otherPlr = plr;
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
// special case since leech is atypically encoded
|
||||
if constexpr (specialCase == LEECH)
|
||||
resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + sizeof(sSkillResult_Heal_HP) + sizeof(sSkillResult_Damage);
|
||||
else if constexpr (specialCase == GHEAL) {
|
||||
otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
pkt->iTargetCnt = otherPlr->groupCnt;
|
||||
resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + pkt->iTargetCnt * sizeof(sPAYLOAD);
|
||||
} else
|
||||
resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + pkt->iTargetCnt * sizeof(sPAYLOAD);
|
||||
|
||||
// validate response packet
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), pkt->iTargetCnt, sizeof(sPAYLOAD))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE packet size" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_NANO_SKILL_USE_SUCC *resp = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf;
|
||||
sPAYLOAD *respdata = (sPAYLOAD*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC));
|
||||
|
||||
plr->Nanos[plr->activeNano].iStamina -= 40;
|
||||
if (plr->Nanos[plr->activeNano].iStamina < 0)
|
||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||
|
||||
resp->iPC_ID = plr->iID;
|
||||
resp->iSkillID = skillId;
|
||||
resp->iNanoID = nanoId;
|
||||
resp->iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
|
||||
resp->eST = eSkillType;
|
||||
resp->iTargetCnt = pkt->iTargetCnt;
|
||||
|
||||
CNSocket *workSock = sock;
|
||||
|
||||
for (int i = 0; i < pkt->iTargetCnt; i++) {
|
||||
if constexpr (specialCase == GHEAL)
|
||||
workSock = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
|
||||
|
||||
if (!work(workSock, pktdata, respdata, i, iCBFlag, amount))
|
||||
return;
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen);
|
||||
}
|
||||
|
||||
// active nano power dispatch table
|
||||
std::vector<ActivePower> ActivePowers = {
|
||||
ActivePower(StunPowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_STUN, CSB_BIT_STUN, 2250),
|
||||
ActivePower(HealPowers, activePower<sSkillResult_Heal_HP, doHeal>, EST_HEAL_HP, CSB_BIT_NONE, 35),
|
||||
ActivePower(GroupHealPowers, activePower<sSkillResult_Heal_HP, doGroupHeal, GHEAL>,EST_HEAL_HP, CSB_BIT_NONE, 20),
|
||||
// TODO: Recall
|
||||
ActivePower(DrainPowers, activePower<sSkillResult_Buff, doBuff>, EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, 3000),
|
||||
ActivePower(SnarePowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_SNARE, CSB_BIT_DN_MOVE_SPEED, 4500),
|
||||
ActivePower(DamagePowers, activePower<sSkillResult_Damage, doDamage>, EST_DAMAGE, CSB_BIT_NONE, 12),
|
||||
ActivePower(LeechPowers, activePower<sSkillResult_Heal_HP, doLeech, LEECH>, EST_BLOODSUCKING, CSB_BIT_NONE, 18),
|
||||
ActivePower(SleepPowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_SLEEP, CSB_BIT_MEZ, 4500),
|
||||
};
|
||||
|
||||
}; // namespace
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Passive Powers
|
||||
void NanoManager::nanoBuff(CNSocket* sock, int16_t nanoId, int skillId, int16_t eSkillType, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue, bool groupPower) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
int pktCnt = 1;
|
||||
Player *leader = plr;
|
||||
plr->iConditionBitFlag |= iCBFlag;
|
||||
|
||||
if (groupPower) {
|
||||
plr->iGroupConditionBitFlag |= iCBFlag;
|
||||
|
||||
if (plr->iID != plr->iIDGroup)
|
||||
leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (leader == nullptr)
|
||||
return;
|
||||
|
||||
pktCnt = leader->groupCnt;
|
||||
}
|
||||
|
||||
if (leader == nullptr)
|
||||
return;
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE), pktCnt, sizeof(sSkillResult_Buff))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE) + pktCnt * sizeof(sSkillResult_Buff);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_NANO_SKILL_USE *resp = (sP_FE2CL_NANO_SKILL_USE*)respbuf;
|
||||
sSkillResult_Buff *respdata = (sSkillResult_Buff*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE));
|
||||
|
||||
resp->iPC_ID = plr->iID;
|
||||
resp->iSkillID = skillId;
|
||||
resp->iNanoID = nanoId;
|
||||
resp->iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
|
||||
resp->eST = eSkillType;
|
||||
resp->iTargetCnt = pktCnt;
|
||||
|
||||
int bitFlag = GroupManager::getGroupFlags(leader);
|
||||
|
||||
for (int i = 0; i < pktCnt; i++) {
|
||||
Player* varPlr;
|
||||
CNSocket* sockTo;
|
||||
|
||||
if (plr->iID == leader->groupIDs[i]) {
|
||||
varPlr = plr;
|
||||
sockTo = sock;
|
||||
} else {
|
||||
varPlr = PlayerManager::getPlayerFromID(leader->groupIDs[i]);
|
||||
sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]);
|
||||
}
|
||||
|
||||
if (varPlr == nullptr || sockTo == nullptr)
|
||||
continue;
|
||||
|
||||
respdata[i].eCT = 1;
|
||||
respdata[i].iID = varPlr->iID;
|
||||
respdata[i].iConditionBitFlag = iCBFlag;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt1);
|
||||
pkt1.eCSTB = eCharStatusTimeBuffID; // eCharStatusTimeBuffID
|
||||
pkt1.eTBU = 1; // eTimeBuffUpdate
|
||||
pkt1.eTBT = 1; // eTimeBuffType 1 means nano
|
||||
pkt1.iConditionBitFlag = bitFlag | varPlr->iConditionBitFlag;
|
||||
|
||||
if (iValue > 0)
|
||||
pkt1.TimeBuff.iValue = iValue;
|
||||
|
||||
sockTo->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen);
|
||||
}
|
||||
|
||||
void NanoManager::nanoUnbuff(CNSocket* sock, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue, bool groupPower) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
int pktCnt = 1;
|
||||
Player *leader = plr;
|
||||
plr->iConditionBitFlag &= ~iCBFlag;
|
||||
|
||||
if (groupPower) {
|
||||
plr->iGroupConditionBitFlag &= ~iCBFlag;
|
||||
|
||||
if (plr->iID != plr->iIDGroup)
|
||||
leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (leader == nullptr)
|
||||
return;
|
||||
|
||||
pktCnt = leader->groupCnt;
|
||||
}
|
||||
|
||||
int bitFlag = GroupManager::getGroupFlags(leader);
|
||||
|
||||
for (int i = 0; i < pktCnt; i++) {
|
||||
Player* varPlr;
|
||||
CNSocket* sockTo;
|
||||
|
||||
if (plr->iID == leader->groupIDs[i]) {
|
||||
varPlr = plr;
|
||||
sockTo = sock;
|
||||
} else {
|
||||
varPlr = PlayerManager::getPlayerFromID(leader->groupIDs[i]);
|
||||
sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]);
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp1);
|
||||
resp1.eCSTB = eCharStatusTimeBuffID; // eCharStatusTimeBuffID
|
||||
resp1.eTBU = 2; // eTimeBuffUpdate
|
||||
resp1.eTBT = 1; // eTimeBuffType 1 means nano
|
||||
resp1.iConditionBitFlag = bitFlag | varPlr->iConditionBitFlag;
|
||||
|
||||
if (iValue > 0)
|
||||
resp1.TimeBuff.iValue = iValue;
|
||||
|
||||
sockTo->sendPacket((void*)&resp1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
}
|
||||
}
|
||||
|
||||
// 0=A 1=B 2=C -1=Not found
|
||||
int NanoManager::nanoStyle(int nanoId) {
|
||||
if (nanoId < 1 || nanoId >= (int)NanoTable.size())
|
||||
return -1;
|
||||
return NanoTable[nanoId].style;
|
||||
}
|
||||
|
||||
namespace NanoManager {
|
||||
|
||||
std::vector<PassivePower> PassivePowers = {
|
||||
PassivePower(ScavengePowers, EST_REWARDBLOB, CSB_BIT_REWARD_BLOB, ECSB_REWARD_BLOB, 0, false),
|
||||
PassivePower(RunPowers, EST_RUN, CSB_BIT_UP_MOVE_SPEED, ECSB_UP_MOVE_SPEED, 200, false),
|
||||
PassivePower(GroupRunPowers, EST_RUN, CSB_BIT_UP_MOVE_SPEED, ECSB_UP_MOVE_SPEED, 200, true),
|
||||
PassivePower(BonusPowers, EST_REWARDCASH, CSB_BIT_REWARD_CASH, ECSB_REWARD_CASH, 0, false),
|
||||
PassivePower(GuardPowers, EST_PROTECTBATTERY, CSB_BIT_PROTECT_BATTERY, ECSB_PROTECT_BATTERY, 0, false),
|
||||
PassivePower(RadarPowers, EST_MINIMAPENEMY, CSB_BIT_MINIMAP_ENEMY, ECSB_MINIMAP_ENEMY, 0, false),
|
||||
PassivePower(AntidotePowers, EST_PROTECTINFECTION, CSB_BIT_PROTECT_INFECTION, ECSB_PROTECT_INFECTION, 0, false),
|
||||
PassivePower(FreedomPowers, EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, 0, false),
|
||||
PassivePower(GroupFreedomPowers, EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, 0, true),
|
||||
PassivePower(JumpPowers, EST_JUMP, CSB_BIT_UP_JUMP_HEIGHT, ECSB_UP_JUMP_HEIGHT, 400, false),
|
||||
PassivePower(GroupJumpPowers, EST_JUMP, CSB_BIT_UP_JUMP_HEIGHT, ECSB_UP_JUMP_HEIGHT, 400, true),
|
||||
PassivePower(SelfRevivePowers, EST_PHOENIX, CSB_BIT_PHOENIX, ECSB_PHOENIX, 0, false),
|
||||
PassivePower(SneakPowers, EST_STEALTH, CSB_BIT_UP_STEALTH, ECSB_UP_STEALTH, 0, false),
|
||||
PassivePower(GroupSneakPowers, EST_STEALTH, CSB_BIT_UP_STEALTH, ECSB_UP_STEALTH, 0, true),
|
||||
PassivePower(TreasureFinderPowers, EST_MINIMAPTRESURE, CSB_BIT_MINIMAP_TRESURE, ECSB_MINIMAP_TRESURE, 0, false),
|
||||
};
|
||||
|
||||
}; // namespace
|
||||
|
||||
void NanoManager::revivePlayer(Player* plr) {
|
||||
CNSocket* sock = PlayerManager::getSockFromID(plr->iID);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_REGEN_SUCC, response);
|
||||
INITSTRUCT(sP_FE2CL_PC_REGEN, resp2);
|
||||
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
|
||||
// Nanos
|
||||
int activeSlot = -1;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int nanoID = plr->equippedNanos[i];
|
||||
if (plr->activeNano == nanoID) {
|
||||
activeSlot = i;
|
||||
}
|
||||
response.PCRegenData.Nanos[i] = plr->Nanos[nanoID];
|
||||
}
|
||||
|
||||
// Response parameters
|
||||
response.PCRegenData.iActiveNanoSlotNum = activeSlot;
|
||||
response.PCRegenData.iX = plr->x;
|
||||
response.PCRegenData.iY = plr->y;
|
||||
response.PCRegenData.iZ = plr->z;
|
||||
response.PCRegenData.iHP = plr->HP;
|
||||
response.iFusionMatter = plr->fusionmatter;
|
||||
response.bMoveLocation = 0;
|
||||
response.PCRegenData.iMapNum = 0;
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_REGEN_SUCC, sizeof(sP_FE2CL_REP_PC_REGEN_SUCC));
|
||||
|
||||
// Update other players
|
||||
resp2.PCRegenDataForOtherPC.iPC_ID = plr->iID;
|
||||
resp2.PCRegenDataForOtherPC.iX = plr->x;
|
||||
resp2.PCRegenDataForOtherPC.iY = plr->y;
|
||||
resp2.PCRegenDataForOtherPC.iZ = plr->z;
|
||||
resp2.PCRegenDataForOtherPC.iHP = plr->HP;
|
||||
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
||||
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
|
||||
|
||||
PlayerManager::sendToViewable(sock, (void*)&resp2, P_FE2CL_PC_REGEN, sizeof(sP_FE2CL_PC_REGEN));
|
||||
}
|
||||
|
||||
void NanoManager::nanoChangeBuff(CNSocket* sock, Player* plr, int32_t cbFrom, int32_t cbTo) {
|
||||
bool sentPacket = false;
|
||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp);
|
||||
resp.eTBU = 3;
|
||||
resp.eTBT = 1;
|
||||
resp.iConditionBitFlag = cbTo;
|
||||
|
||||
if (!(cbFrom & CSB_BIT_UP_MOVE_SPEED) && (cbTo & CSB_BIT_UP_MOVE_SPEED)) {
|
||||
resp.eCSTB = ECSB_UP_MOVE_SPEED;
|
||||
resp.eTBU = 1;
|
||||
resp.TimeBuff.iValue = 200;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
sentPacket = true;
|
||||
} else if ((cbFrom & CSB_BIT_UP_MOVE_SPEED) && !(cbTo & CSB_BIT_UP_MOVE_SPEED)) {
|
||||
resp.eCSTB = ECSB_UP_MOVE_SPEED;
|
||||
resp.eTBU = 2;
|
||||
resp.TimeBuff.iValue = 200;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
sentPacket = true;
|
||||
}
|
||||
|
||||
if (!(cbFrom & CSB_BIT_UP_JUMP_HEIGHT) && (cbTo & CSB_BIT_UP_JUMP_HEIGHT)) {
|
||||
resp.eCSTB = ECSB_UP_JUMP_HEIGHT;
|
||||
resp.eTBU = 1;
|
||||
resp.TimeBuff.iValue = 400;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
sentPacket = true;
|
||||
} else if ((cbFrom & CSB_BIT_UP_JUMP_HEIGHT) && !(cbTo & CSB_BIT_UP_JUMP_HEIGHT)) {
|
||||
resp.eCSTB = ECSB_UP_JUMP_HEIGHT;
|
||||
resp.eTBU = 2;
|
||||
resp.TimeBuff.iValue = 400;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
sentPacket = true;
|
||||
}
|
||||
|
||||
if (!sentPacket)
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
}
|
||||
#pragma endregion
|
||||
@@ -1,76 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "CNShardServer.hpp"
|
||||
|
||||
typedef void (*ActivePowerHandler)(CNSocket*, CNPacketData*, int16_t, int16_t, int16_t, int32_t, int32_t);
|
||||
|
||||
struct ActivePower {
|
||||
std::set<int> powers;
|
||||
ActivePowerHandler handler;
|
||||
int16_t eSkillType;
|
||||
int32_t flag;
|
||||
int32_t amount;
|
||||
|
||||
ActivePower(std::set<int> p, ActivePowerHandler h, int16_t t, int32_t f, int32_t a) : powers(p), handler(h), eSkillType(t), flag(f), amount(a) {}
|
||||
|
||||
void handle(CNSocket *sock, CNPacketData *data, int16_t nanoId, int16_t skillId) {
|
||||
if (handler == nullptr)
|
||||
return;
|
||||
|
||||
handler(sock, data, nanoId, skillId, eSkillType, flag, amount);
|
||||
}
|
||||
};
|
||||
|
||||
struct PassivePower {
|
||||
std::set<int> powers;
|
||||
int16_t eSkillType;
|
||||
int32_t iCBFlag;
|
||||
int16_t eCharStatusTimeBuffID;
|
||||
int16_t iValue;
|
||||
bool groupPower;
|
||||
|
||||
PassivePower(std::set<int> p, int16_t t, int32_t f, int16_t b, int16_t a, bool g) : powers(p), eSkillType(t), iCBFlag(f), eCharStatusTimeBuffID(b), iValue(a), groupPower(g) {}
|
||||
};
|
||||
|
||||
struct NanoData {
|
||||
int style;
|
||||
};
|
||||
|
||||
struct NanoTuning {
|
||||
int reqItemCount;
|
||||
int reqItems;
|
||||
};
|
||||
|
||||
namespace NanoManager {
|
||||
extern std::vector<ActivePower> ActivePowers;
|
||||
extern std::vector<PassivePower> PassivePowers;
|
||||
extern std::map<int32_t, NanoData> NanoTable;
|
||||
extern std::map<int32_t, NanoTuning> NanoTunings;
|
||||
void init();
|
||||
|
||||
void nanoSummonHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoEquipHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoUnEquipHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoGMGiveHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoSkillSetHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoSkillSetGMHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoRecallHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoPotionHandler(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// Helper methods
|
||||
void addNano(CNSocket* sock, int16_t nanoId, int16_t slot, bool spendfm=false);
|
||||
void summonNano(CNSocket* sock, int slot);
|
||||
void setNanoSkill(CNSocket* sock, sP_CL2FE_REQ_NANO_TUNE* skill);
|
||||
void resetNanoSkill(CNSocket* sock, int16_t nanoId);
|
||||
|
||||
void nanoBuff(CNSocket* sock, int16_t nanoId, int skillId, int16_t eSkillType, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue = 0, bool groupPower = false);
|
||||
void nanoUnbuff(CNSocket* sock, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue = 0, bool groupPower = false);
|
||||
|
||||
int nanoStyle(int nanoId);
|
||||
void revivePlayer(Player* plr);
|
||||
void nanoChangeBuff(CNSocket* sock, Player* plr, int32_t cbFrom, int32_t cbTo);
|
||||
}
|
||||
373
src/Nanos.cpp
Normal file
373
src/Nanos.cpp
Normal file
@@ -0,0 +1,373 @@
|
||||
#include "Nanos.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using namespace Nanos;
|
||||
|
||||
std::map<int32_t, NanoData> Nanos::NanoTable;
|
||||
std::map<int32_t, NanoTuning> Nanos::NanoTunings;
|
||||
|
||||
#pragma region Helper methods
|
||||
void Nanos::addNano(CNSocket* sock, int16_t nanoID, int16_t slot, bool spendfm) {
|
||||
if (nanoID <= 0 || nanoID >= NANO_COUNT)
|
||||
return;
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
int level = plr->level;
|
||||
|
||||
#ifndef ACADEMY
|
||||
level = nanoID < plr->level ? plr->level : nanoID;
|
||||
|
||||
/*
|
||||
* Spend the necessary Fusion Matter.
|
||||
* Note the use of the not-yet-incremented plr->level as opposed to level.
|
||||
* Doing it the other way always leaves the FM at 0. Jade totally called it.
|
||||
*/
|
||||
plr->level = level;
|
||||
|
||||
if (spendfm)
|
||||
Missions::updateFusionMatter(sock, -(int)Missions::AvatarGrowth[plr->level-1]["m_iReqBlob_NanoCreate"]);
|
||||
#endif
|
||||
|
||||
// Send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_NANO_CREATE_SUCC, resp);
|
||||
resp.Nano.iID = nanoID;
|
||||
resp.Nano.iStamina = 150;
|
||||
resp.iQuestItemSlotNum = slot;
|
||||
resp.iPC_Level = level;
|
||||
resp.iPC_FusionMatter = plr->fusionmatter;
|
||||
|
||||
if (plr->activeNano > 0 && plr->activeNano == nanoID)
|
||||
summonNano(sock, -1); // just unsummon the nano to prevent infinite buffs
|
||||
|
||||
// Update player
|
||||
plr->Nanos[nanoID] = resp.Nano;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_NANO_CREATE_SUCC);
|
||||
|
||||
/*
|
||||
* iPC_Level in NANO_CREATE_SUCC sets the player's level.
|
||||
* Other players must be notified of the change as well. Both P_FE2CL_REP_PC_NANO_CREATE and
|
||||
* P_FE2CL_REP_PC_CHANGE_LEVEL appear to play the same animation, but only the latter affects
|
||||
* the other player's displayed level.
|
||||
*/
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_LEVEL, resp2);
|
||||
|
||||
resp2.iPC_ID = plr->iID;
|
||||
resp2.iPC_Level = level;
|
||||
|
||||
// Update other players' perception of the player's level
|
||||
PlayerManager::sendToViewable(sock, resp2, P_FE2CL_REP_PC_CHANGE_LEVEL);
|
||||
}
|
||||
|
||||
void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_ACTIVE_SUCC, resp);
|
||||
resp.iActiveNanoSlotNum = slot;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (slot > 2 || slot < -1)
|
||||
return; // sanity check
|
||||
|
||||
int16_t nanoID = slot == -1 ? 0 : plr->equippedNanos[slot];
|
||||
|
||||
if (slot != -1 && plr->Nanos[nanoID].iSkillID == 0)
|
||||
return; // prevent powerless nanos from summoning
|
||||
|
||||
if (nanoID >= NANO_COUNT || nanoID < 0)
|
||||
return; // sanity check
|
||||
|
||||
plr->activeNano = nanoID;
|
||||
sNano& nano = plr->Nanos[nanoID];
|
||||
|
||||
SkillData* skill = Abilities::SkillTable.count(nano.iSkillID) > 0
|
||||
? &Abilities::SkillTable[nano.iSkillID] : nullptr;
|
||||
if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) {
|
||||
// passive buff effect
|
||||
resp.eCSTB___Add = 1;
|
||||
ICombatant* src = dynamic_cast<ICombatant*>(plr);
|
||||
int32_t targets[] = { plr->iID };
|
||||
std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
|
||||
Abilities::useNanoSkill(sock, skill, nano, affectedCombatants);
|
||||
}
|
||||
|
||||
if (!silent) // silent nano death but only for the summoning player
|
||||
sock->sendPacket(resp, P_FE2CL_REP_NANO_ACTIVE_SUCC);
|
||||
|
||||
// Send to other players, these players can't handle silent nano deaths so this packet needs to be sent.
|
||||
INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1);
|
||||
pkt1.iPC_ID = plr->iID;
|
||||
pkt1.Nano = nano;
|
||||
PlayerManager::sendToViewable(sock, pkt1, P_FE2CL_NANO_ACTIVE);
|
||||
}
|
||||
|
||||
static void setNanoSkill(CNSocket* sock, sP_CL2FE_REQ_NANO_TUNE* skill) {
|
||||
if (skill->iNanoID >= NANO_COUNT)
|
||||
return;
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr->activeNano > 0 && plr->activeNano == skill->iNanoID)
|
||||
summonNano(sock, -1); // just unsummon the nano to prevent infinite buffs
|
||||
|
||||
sNano nano = plr->Nanos[skill->iNanoID];
|
||||
nano.iSkillID = skill->iTuneID;
|
||||
plr->Nanos[skill->iNanoID] = nano;
|
||||
|
||||
// Send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_TUNE_SUCC, resp);
|
||||
resp.iNanoID = skill->iNanoID;
|
||||
resp.iSkillID = skill->iTuneID;
|
||||
resp.iPC_FusionMatter = plr->fusionmatter;
|
||||
resp.aItem[9] = plr->Inven[0]; // quick fix to make sure item in slot 0 doesn't get yeeted by default
|
||||
|
||||
|
||||
// check if there's any garbage in the item slot array (this'll happen when a nano station isn't used)
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (skill->aiNeedItemSlotNum[i] < 0 || skill->aiNeedItemSlotNum[i] >= AINVEN_COUNT) {
|
||||
sock->sendPacket(resp, P_FE2CL_REP_NANO_TUNE_SUCC);
|
||||
return; // stop execution, don't run consumption logic
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ACADEMY
|
||||
if (plr->fusionmatter < (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoTune"]) // sanity check
|
||||
return;
|
||||
#endif
|
||||
|
||||
plr->fusionmatter -= (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoTune"];
|
||||
|
||||
int reqItemCount = NanoTunings[skill->iTuneID].reqItemCount;
|
||||
int reqItemID = NanoTunings[skill->iTuneID].reqItems;
|
||||
int i = 0;
|
||||
while (reqItemCount > 0 && i < 10) {
|
||||
|
||||
sItemBase& item = plr->Inven[skill->aiNeedItemSlotNum[i]];
|
||||
if (item.iType == 7 && item.iID == reqItemID) {
|
||||
if (item.iOpt > reqItemCount) {
|
||||
item.iOpt -= reqItemCount;
|
||||
reqItemCount = 0;
|
||||
}
|
||||
else {
|
||||
reqItemCount -= item.iOpt;
|
||||
item.iID = 0;
|
||||
item.iType = 0;
|
||||
item.iOpt = 0;
|
||||
}
|
||||
}
|
||||
i++; // next slot
|
||||
}
|
||||
|
||||
resp.iPC_FusionMatter = plr->fusionmatter; // update fusion matter in packet
|
||||
// update items clientside
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (skill->aiNeedItemSlotNum[i]) { // non-zero check
|
||||
resp.aItem[i] = plr->Inven[skill->aiNeedItemSlotNum[i]];
|
||||
resp.aiItemSlotNum[i] = skill->aiNeedItemSlotNum[i];
|
||||
}
|
||||
}
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_NANO_TUNE_SUCC);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << PlayerManager::getPlayerName(plr) << " set skill id " << skill->iTuneID << " for nano: " << skill->iNanoID << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
// 0=A 1=B 2=C -1=Not found
|
||||
int Nanos::nanoStyle(int nanoID) {
|
||||
if (nanoID < 1 || nanoID >= (int)NanoTable.size())
|
||||
return -1;
|
||||
return NanoTable[nanoID].style;
|
||||
}
|
||||
|
||||
bool Nanos::getNanoBoost(Player* plr) {
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (plr->equippedNanos[i] == plr->activeNano)
|
||||
if (plr->hasBuff(ECSB_STIMPAKSLOT1 + i))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto nano = (sP_CL2FE_REQ_NANO_EQUIP*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_EQUIP_SUCC, resp);
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// sanity checks
|
||||
if (nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
|
||||
return;
|
||||
|
||||
if (nano->iNanoID < 0 || nano->iNanoID >= NANO_COUNT)
|
||||
return;
|
||||
|
||||
resp.iNanoID = nano->iNanoID;
|
||||
resp.iNanoSlotNum = nano->iNanoSlotNum;
|
||||
|
||||
// Update player
|
||||
plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID;
|
||||
|
||||
// unsummon nano if replaced
|
||||
if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum])
|
||||
summonNano(sock, -1);
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_NANO_EQUIP_SUCC);
|
||||
}
|
||||
|
||||
static void nanoUnEquipHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto nano = (sP_CL2FE_REQ_NANO_UNEQUIP*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_UNEQUIP_SUCC, resp);
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// sanity check
|
||||
if (nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
|
||||
return;
|
||||
|
||||
resp.iNanoSlotNum = nano->iNanoSlotNum;
|
||||
|
||||
// unsummon nano if removed
|
||||
if (plr->equippedNanos[nano->iNanoSlotNum] == plr->activeNano)
|
||||
summonNano(sock, -1);
|
||||
|
||||
// update player
|
||||
plr->equippedNanos[nano->iNanoSlotNum] = 0;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_NANO_UNEQUIP_SUCC);
|
||||
}
|
||||
|
||||
static void nanoSummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_NANO_ACTIVE*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
summonNano(sock, pkt->iNanoSlotNum);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano slot: " << pkt->iNanoSlotNum << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// validate request check
|
||||
sP_CL2FE_REQ_NANO_SKILL_USE* pkt = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf;
|
||||
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_NANO_SKILL_USE), pkt->iTargetCnt, sizeof(int32_t), data->size)) {
|
||||
std::cout << "[WARN] bad sP_CL2FE_REQ_NANO_SKILL_USE packet size" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
sNano& nano = plr->Nanos[plr->activeNano];
|
||||
int16_t skillID = nano.iSkillID;
|
||||
SkillData* skillData = &Abilities::SkillTable[skillID];
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl;
|
||||
)
|
||||
|
||||
ICombatant* plrCombatant = dynamic_cast<ICombatant*>(plr);
|
||||
std::vector<ICombatant*> targetData = Abilities::matchTargets(plrCombatant, skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1));
|
||||
Abilities::useNanoSkill(sock, skillData, nano, targetData);
|
||||
|
||||
if (plr->Nanos[plr->activeNano].iStamina <= 0)
|
||||
summonNano(sock, -1);
|
||||
}
|
||||
|
||||
static void nanoSkillSetHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto skill = (sP_CL2FE_REQ_NANO_TUNE*)data->buf;
|
||||
setNanoSkill(sock, skill);
|
||||
}
|
||||
|
||||
static void nanoSkillSetGMHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto skillGM = (sP_CL2FE_REQ_NANO_TUNE*)data->buf;
|
||||
setNanoSkill(sock, skillGM);
|
||||
}
|
||||
|
||||
static void nanoRecallRegisterHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto recallData = (sP_CL2FE_REQ_REGIST_RXCOM*)data->buf;
|
||||
|
||||
if (NPCManager::NPCs.find(recallData->iNPCID) == NPCManager::NPCs.end())
|
||||
return;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
BaseNPC *npc = NPCManager::NPCs[recallData->iNPCID];
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_REGIST_RXCOM, response);
|
||||
response.iMapNum = plr->recallInstance = (int32_t)npc->instanceID; // Never going to recall into a Fusion Lair
|
||||
response.iX = plr->recallX = npc->x;
|
||||
response.iY = plr->recallY = npc->y;
|
||||
response.iZ = plr->recallZ = npc->z;
|
||||
sock->sendPacket(response, P_FE2CL_REP_REGIST_RXCOM);
|
||||
}
|
||||
|
||||
static void nanoRecallHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto recallData = (sP_CL2FE_REQ_WARP_USE_RECALL*)data->buf;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(recallData->iGroupMemberID);
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
// ensure the group member is still in the same IZ
|
||||
if (otherPlr->instanceID != plr->instanceID)
|
||||
return;
|
||||
|
||||
// do not allow hypothetical recall points in lairs to mess with the respawn logic
|
||||
if (PLAYERID(plr->instanceID) != 0)
|
||||
return;
|
||||
|
||||
if ((int32_t)plr->instanceID == otherPlr->recallInstance)
|
||||
PlayerManager::sendPlayerTo(sock, otherPlr->recallX, otherPlr->recallY, otherPlr->recallZ, otherPlr->recallInstance);
|
||||
else {
|
||||
INITSTRUCT(sP_FE2CL_REP_WARP_USE_RECALL_FAIL, response)
|
||||
sock->sendPacket(response, P_FE2CL_REP_WARP_USE_RECALL_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
static void nanoPotionHandler(CNSocket* sock, CNPacketData* data) {
|
||||
Player* player = PlayerManager::getPlayer(sock);
|
||||
|
||||
// sanity checks
|
||||
if (player->activeNano == -1 || player->batteryN == 0)
|
||||
return;
|
||||
|
||||
sNano nano = player->Nanos[player->activeNano];
|
||||
int difference = 150 - nano.iStamina;
|
||||
if (player->batteryN < difference)
|
||||
difference = player->batteryN;
|
||||
|
||||
if (difference == 0)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_CHARGE_NANO_STAMINA, response);
|
||||
response.iNanoID = nano.iID;
|
||||
response.iNanoStamina = nano.iStamina + difference;
|
||||
response.iBatteryN = player->batteryN - difference;
|
||||
|
||||
sock->sendPacket(response, P_FE2CL_REP_CHARGE_NANO_STAMINA);
|
||||
// now update serverside
|
||||
player->batteryN -= difference;
|
||||
player->Nanos[nano.iID].iStamina += difference;
|
||||
|
||||
}
|
||||
|
||||
void Nanos::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_ACTIVE, nanoSummonHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_EQUIP, nanoEquipHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_UNEQUIP, nanoUnEquipHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_TUNE, nanoSkillSetHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GIVE_NANO_SKILL, nanoSkillSetGMHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_SKILL_USE, nanoSkillUseHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REGIST_RXCOM, nanoRecallRegisterHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_WARP_USE_RECALL, nanoRecallHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_CHARGE_NANO_STAMINA, nanoPotionHandler);
|
||||
}
|
||||
29
src/Nanos.hpp
Normal file
29
src/Nanos.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "Abilities.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
struct NanoData {
|
||||
int style;
|
||||
};
|
||||
|
||||
struct NanoTuning {
|
||||
int reqItemCount;
|
||||
int reqItems;
|
||||
};
|
||||
|
||||
namespace Nanos {
|
||||
extern std::map<int32_t, NanoData> NanoTable;
|
||||
extern std::map<int32_t, NanoTuning> NanoTunings;
|
||||
void init();
|
||||
|
||||
// Helper methods
|
||||
void addNano(CNSocket* sock, int16_t nanoID, int16_t slot, bool spendfm=false);
|
||||
void summonNano(CNSocket* sock, int slot, bool silent = false);
|
||||
int nanoStyle(int nanoID);
|
||||
bool getNanoBoost(Player* plr);
|
||||
}
|
||||
153
src/Player.hpp
153
src/Player.hpp
@@ -1,71 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "Entities.hpp"
|
||||
#include "Groups.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
/* forward declaration(s) */
|
||||
class Buff;
|
||||
struct BuffStack;
|
||||
|
||||
#define ACTIVE_MISSION_COUNT 6
|
||||
|
||||
#define PC_MAXHEALTH(level) (925 + 75 * (level))
|
||||
|
||||
struct Player {
|
||||
int accountId;
|
||||
int accountLevel; // permission level (see CN_ACCOUNT_LEVEL enums)
|
||||
int64_t SerialKey;
|
||||
int32_t iID;
|
||||
uint64_t FEKey;
|
||||
time_t creationTime;
|
||||
struct Player : public Entity, public ICombatant {
|
||||
int accountId = 0;
|
||||
int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums)
|
||||
int32_t iID = 0;
|
||||
|
||||
int level;
|
||||
int HP;
|
||||
int slot; // player slot, not nano slot
|
||||
int16_t mentor;
|
||||
int32_t money;
|
||||
int32_t fusionmatter;
|
||||
int32_t batteryW;
|
||||
int32_t batteryN;
|
||||
sPCStyle PCStyle;
|
||||
sPCStyle2 PCStyle2;
|
||||
sNano Nanos[37]; // acquired nanos
|
||||
int equippedNanos[3];
|
||||
int activeNano; // active nano (index into Nanos)
|
||||
int8_t iPCState;
|
||||
int32_t iWarpLocationFlag;
|
||||
int64_t aSkywayLocationFlag[2];
|
||||
int32_t iConditionBitFlag;
|
||||
int8_t iSpecialState;
|
||||
int level = 0;
|
||||
int HP = 0;
|
||||
int slot = 0; // player slot, not nano slot
|
||||
int16_t mentor = 0;
|
||||
int32_t money = 0;
|
||||
int32_t fusionmatter = 0;
|
||||
int32_t batteryW = 0;
|
||||
int32_t batteryN = 0;
|
||||
sPCStyle PCStyle = {};
|
||||
sPCStyle2 PCStyle2 = {};
|
||||
sNano Nanos[NANO_COUNT] = {}; // acquired nanos
|
||||
int equippedNanos[3] = {};
|
||||
int activeNano = 0; // active nano (index into Nanos)
|
||||
int8_t iPCState = 0;
|
||||
int32_t iWarpLocationFlag = 0;
|
||||
int64_t aSkywayLocationFlag[2] = {};
|
||||
int8_t iSpecialState = 0;
|
||||
std::unordered_map<int, Buff*> buffs = {};
|
||||
|
||||
int x, y, z, angle;
|
||||
int lastX, lastY, lastZ, lastAngle;
|
||||
uint64_t instanceID;
|
||||
sItemBase Equip[AEQUIP_COUNT];
|
||||
sItemBase Inven[AINVEN_COUNT];
|
||||
sItemBase Bank[ABANK_COUNT];
|
||||
sItemTrade Trade[12];
|
||||
int32_t moneyInTrade;
|
||||
bool isTrading;
|
||||
bool isTradeConfirm;
|
||||
int angle = 0;
|
||||
int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0;
|
||||
int recallX = 0, recallY = 0, recallZ = 0, recallInstance = 0; // also Lair entrances
|
||||
sItemBase Equip[AEQUIP_COUNT] = {};
|
||||
sItemBase Inven[AINVEN_COUNT] = {};
|
||||
sItemBase Bank[ABANK_COUNT] = {};
|
||||
sItemTrade Trade[12] = {};
|
||||
int32_t moneyInTrade = 0;
|
||||
bool isTrading = false;
|
||||
bool isTradeConfirm = false;
|
||||
|
||||
bool inCombat;
|
||||
bool passiveNanoOut;
|
||||
int healCooldown;
|
||||
bool inCombat = false;
|
||||
bool onMonkey = false;
|
||||
int healCooldown = 0;
|
||||
|
||||
int pointDamage;
|
||||
int groupDamage;
|
||||
int defense;
|
||||
int pointDamage = 0;
|
||||
int groupDamage = 0;
|
||||
int fireRate = 0;
|
||||
int defense = 0;
|
||||
|
||||
int64_t aQuestFlag[16];
|
||||
int tasks[ACTIVE_MISSION_COUNT];
|
||||
int RemainingNPCCount[ACTIVE_MISSION_COUNT][3];
|
||||
sItemBase QInven[AQINVEN_COUNT];
|
||||
int32_t CurrentMissionID;
|
||||
int64_t aQuestFlag[16] = {};
|
||||
int tasks[ACTIVE_MISSION_COUNT] = {};
|
||||
int RemainingNPCCount[ACTIVE_MISSION_COUNT][3] = {};
|
||||
sItemBase QInven[AQINVEN_COUNT] = {};
|
||||
int32_t CurrentMissionID = 0;
|
||||
|
||||
sTimeLimitItemDeleteInfo2CL toRemoveVehicle;
|
||||
sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {};
|
||||
|
||||
int32_t iIDGroup;
|
||||
int groupCnt;
|
||||
int32_t groupIDs[4];
|
||||
int32_t iGroupConditionBitFlag;
|
||||
Group* group = nullptr;
|
||||
|
||||
bool notify = false;
|
||||
bool hidden = false;
|
||||
bool unwarpable = false;
|
||||
|
||||
bool buddiesSynced = false;
|
||||
int64_t buddyIDs[50] = {};
|
||||
bool isBuddyBlocked[50] = {};
|
||||
|
||||
uint64_t iFirstUseFlag[2] = {};
|
||||
time_t lastHeartbeat = 0;
|
||||
|
||||
int suspicionRating = 0;
|
||||
time_t lastShot = 0;
|
||||
std::vector<sItemBase> buyback = {};
|
||||
|
||||
Player() { kind = EntityKind::PLAYER; }
|
||||
|
||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||
|
||||
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
||||
virtual Buff* getBuff(int buffId) override;
|
||||
virtual void removeBuff(int buffId) override;
|
||||
virtual void removeBuff(int buffId, BuffClass buffClass) override;
|
||||
virtual void clearBuffs(bool force) override;
|
||||
virtual bool hasBuff(int buffId) override;
|
||||
virtual int getCompositeCondition() override;
|
||||
virtual int takeDamage(EntityRef src, int amt) override;
|
||||
virtual int heal(EntityRef src, int amt) override;
|
||||
virtual bool isAlive() override;
|
||||
virtual int getCurrentHP() override;
|
||||
virtual int getMaxHP() override;
|
||||
virtual int getLevel() override;
|
||||
virtual std::vector<EntityRef> getGroupMembers() override;
|
||||
virtual int32_t getCharType() override;
|
||||
virtual int32_t getID() override;
|
||||
virtual EntityRef getRef() override;
|
||||
|
||||
virtual void step(time_t currTime) override;
|
||||
|
||||
sNano* getActiveNano();
|
||||
sPCAppearanceData getAppearanceData();
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,74 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
#include "ChunkManager.hpp"
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "Chunking.hpp"
|
||||
#include "Transport.hpp"
|
||||
#include "Entities.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
struct WarpLocation;
|
||||
|
||||
struct PlayerView {
|
||||
std::tuple<int, int, uint64_t> chunkPos;
|
||||
std::vector<Chunk*> currentChunks;
|
||||
Player *plr;
|
||||
time_t lastHeartbeat;
|
||||
};
|
||||
|
||||
|
||||
namespace PlayerManager {
|
||||
extern std::map<CNSocket*, PlayerView> players;
|
||||
extern std::map<CNSocket*, Player*> players;
|
||||
void init();
|
||||
|
||||
void addPlayer(CNSocket* key, Player plr);
|
||||
void removePlayer(CNSocket* key);
|
||||
|
||||
bool removePlayerFromChunks(std::vector<Chunk*> chunks, CNSocket* sock);
|
||||
void addPlayerToChunks(std::vector<Chunk*> chunks, CNSocket* sock);
|
||||
|
||||
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z);
|
||||
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, int angle);
|
||||
void updatePlayerChunk(CNSocket* sock, int X, int Y, uint64_t instanceID);
|
||||
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, uint64_t I, int angle);
|
||||
void updatePlayerPositionForWarp(CNSocket* sock, int X, int Y, int Z, uint64_t inst);
|
||||
|
||||
void sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I);
|
||||
void sendPlayerTo(CNSocket* sock, int X, int Y, int Z);
|
||||
|
||||
void sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size);
|
||||
|
||||
void enterPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void loadPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void movePlayer(CNSocket* sock, CNPacketData* data);
|
||||
void stopPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void jumpPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void jumppadPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void launchPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void ziplinePlayer(CNSocket* sock, CNPacketData* data);
|
||||
void movePlatformPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void moveSliderPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void moveSlopePlayer(CNSocket* sock, CNPacketData* data);
|
||||
void gotoPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void setSpecialPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void heartbeatPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void revivePlayer(CNSocket* sock, CNPacketData* data);
|
||||
void exitGame(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
void setSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void setGMSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void changePlayerGuide(CNSocket *sock, CNPacketData *data);
|
||||
|
||||
void enterPlayerVehicle(CNSocket* sock, CNPacketData* data);
|
||||
void exitPlayerVehicle(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
Player *getPlayer(CNSocket* key);
|
||||
WarpLocation getRespawnPoint(Player *plr);
|
||||
std::string getPlayerName(Player *plr, bool id=true);
|
||||
|
||||
bool isAccountInUse(int accountId);
|
||||
void exitDuplicate(int accountId);
|
||||
void setSpecialState(CNSocket* sock, CNPacketData* data);
|
||||
Player *getPlayerFromID(int32_t iID);
|
||||
CNSocket *getSockFromID(int32_t iID);
|
||||
CNSocket *getSockFromName(std::string firstname, std::string lastname);
|
||||
CNSocket *getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname);
|
||||
WarpLocation *getRespawnPoint(Player *plr);
|
||||
|
||||
void sendToGroup(CNSocket *sock, void* buf, uint32_t type, size_t size);
|
||||
void sendToViewable(CNSocket *sock, void* buf, uint32_t type, size_t size);
|
||||
|
||||
// TODO: unify this under the new Entity system
|
||||
template<class T>
|
||||
void sendToViewable(CNSocket *sock, T& pkt, uint32_t type) {
|
||||
Player* plr = getPlayer(sock);
|
||||
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
||||
Chunk* chunk = *it;
|
||||
for (const EntityRef& ref : chunk->entities) {
|
||||
if (ref.kind != EntityKind::PLAYER || ref.sock == sock)
|
||||
continue;
|
||||
|
||||
ref.sock->sendPacket(pkt, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
290
src/PlayerMovement.cpp
Normal file
290
src/PlayerMovement.cpp
Normal file
@@ -0,0 +1,290 @@
|
||||
#include "PlayerMovement.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "TableData.hpp"
|
||||
#include "core/Core.hpp"
|
||||
|
||||
static void movePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
auto* moveData = (sP_CL2FE_REQ_PC_MOVE*)data->buf;
|
||||
PlayerManager::updatePlayerPosition(sock, moveData->iX, moveData->iY, moveData->iZ, plr->instanceID, moveData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_MOVE, moveResponse);
|
||||
|
||||
moveResponse.iID = plr->iID;
|
||||
moveResponse.cKeyValue = moveData->cKeyValue;
|
||||
|
||||
moveResponse.iX = moveData->iX;
|
||||
moveResponse.iY = moveData->iY;
|
||||
moveResponse.iZ = moveData->iZ;
|
||||
moveResponse.iAngle = moveData->iAngle;
|
||||
moveResponse.fVX = moveData->fVX;
|
||||
moveResponse.fVY = moveData->fVY;
|
||||
moveResponse.fVZ = moveData->fVZ;
|
||||
|
||||
moveResponse.iSpeed = moveData->iSpeed;
|
||||
moveResponse.iCliTime = moveData->iCliTime; // maybe don't send this??? seems unneeded...
|
||||
moveResponse.iSvrTime = tm;
|
||||
|
||||
PlayerManager::sendToViewable(sock, moveResponse, P_FE2CL_PC_MOVE);
|
||||
|
||||
// [gruntwork] check if player has a follower and move it
|
||||
if (TableData::RunningNPCPaths.find(plr->iID) != TableData::RunningNPCPaths.end()) {
|
||||
BaseNPC* follower = TableData::RunningNPCPaths[plr->iID].first;
|
||||
Transport::NPCQueues.erase(follower->id); // erase existing points
|
||||
std::queue<Vec3> queue;
|
||||
Vec3 from = { follower->x, follower->y, follower->z };
|
||||
float drag = 0.95f; // this ensures that they don't bump into the player
|
||||
Vec3 to = {
|
||||
(int)(follower->x + (moveData->iX - follower->x) * drag),
|
||||
(int)(follower->y + (moveData->iY - follower->y) * drag),
|
||||
(int)(follower->z + (moveData->iZ - follower->z) * drag)
|
||||
};
|
||||
|
||||
// add a route to the queue; to be processed in Transport::stepNPCPathing()
|
||||
Transport::lerp(&queue, from, to, NPC_DEFAULT_SPEED * 1.5); // little faster than typical
|
||||
Transport::NPCQueues[follower->id] = queue;
|
||||
}
|
||||
}
|
||||
|
||||
static void stopPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
auto stopData = (sP_CL2FE_REQ_PC_STOP*)data->buf;
|
||||
PlayerManager::updatePlayerPosition(sock, stopData->iX, stopData->iY, stopData->iZ, plr->instanceID, plr->angle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_STOP, stopResponse);
|
||||
|
||||
stopResponse.iID = plr->iID;
|
||||
|
||||
stopResponse.iX = stopData->iX;
|
||||
stopResponse.iY = stopData->iY;
|
||||
stopResponse.iZ = stopData->iZ;
|
||||
|
||||
stopResponse.iCliTime = stopData->iCliTime; // maybe don't send this??? seems unneeded...
|
||||
stopResponse.iSvrTime = tm;
|
||||
|
||||
PlayerManager::sendToViewable(sock, stopResponse, P_FE2CL_PC_STOP);
|
||||
}
|
||||
|
||||
static void jumpPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
auto jumpData = (sP_CL2FE_REQ_PC_JUMP*)data->buf;
|
||||
PlayerManager::updatePlayerPosition(sock, jumpData->iX, jumpData->iY, jumpData->iZ, plr->instanceID, jumpData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_JUMP, jumpResponse);
|
||||
|
||||
jumpResponse.iID = plr->iID;
|
||||
jumpResponse.cKeyValue = jumpData->cKeyValue;
|
||||
|
||||
jumpResponse.iX = jumpData->iX;
|
||||
jumpResponse.iY = jumpData->iY;
|
||||
jumpResponse.iZ = jumpData->iZ;
|
||||
jumpResponse.iAngle = jumpData->iAngle;
|
||||
jumpResponse.iVX = jumpData->iVX;
|
||||
jumpResponse.iVY = jumpData->iVY;
|
||||
jumpResponse.iVZ = jumpData->iVZ;
|
||||
|
||||
jumpResponse.iSpeed = jumpData->iSpeed;
|
||||
jumpResponse.iCliTime = jumpData->iCliTime; // maybe don't send this??? seems unneeded...
|
||||
jumpResponse.iSvrTime = tm;
|
||||
|
||||
PlayerManager::sendToViewable(sock, jumpResponse, P_FE2CL_PC_JUMP);
|
||||
}
|
||||
|
||||
static void jumppadPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
auto jumppadData = (sP_CL2FE_REQ_PC_JUMPPAD*)data->buf;
|
||||
PlayerManager::updatePlayerPosition(sock, jumppadData->iX, jumppadData->iY, jumppadData->iZ, plr->instanceID, jumppadData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_JUMPPAD, jumppadResponse);
|
||||
|
||||
jumppadResponse.iPC_ID = plr->iID;
|
||||
jumppadResponse.cKeyValue = jumppadData->cKeyValue;
|
||||
|
||||
jumppadResponse.iX = jumppadData->iX;
|
||||
jumppadResponse.iY = jumppadData->iY;
|
||||
jumppadResponse.iZ = jumppadData->iZ;
|
||||
jumppadResponse.iVX = jumppadData->iVX;
|
||||
jumppadResponse.iVY = jumppadData->iVY;
|
||||
jumppadResponse.iVZ = jumppadData->iVZ;
|
||||
|
||||
jumppadResponse.iCliTime = jumppadData->iCliTime;
|
||||
jumppadResponse.iSvrTime = tm;
|
||||
|
||||
PlayerManager::sendToViewable(sock, jumppadResponse, P_FE2CL_PC_JUMPPAD);
|
||||
}
|
||||
|
||||
static void launchPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
auto launchData = (sP_CL2FE_REQ_PC_LAUNCHER*)data->buf;
|
||||
PlayerManager::updatePlayerPosition(sock, launchData->iX, launchData->iY, launchData->iZ, plr->instanceID, launchData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_LAUNCHER, launchResponse);
|
||||
|
||||
launchResponse.iPC_ID = plr->iID;
|
||||
|
||||
launchResponse.iX = launchData->iX;
|
||||
launchResponse.iY = launchData->iY;
|
||||
launchResponse.iZ = launchData->iZ;
|
||||
launchResponse.iVX = launchData->iVX;
|
||||
launchResponse.iVY = launchData->iVY;
|
||||
launchResponse.iVZ = launchData->iVZ;
|
||||
launchResponse.iSpeed = launchData->iSpeed;
|
||||
launchResponse.iAngle = launchData->iAngle;
|
||||
|
||||
launchResponse.iCliTime = launchData->iCliTime;
|
||||
launchResponse.iSvrTime = tm;
|
||||
|
||||
PlayerManager::sendToViewable(sock, launchResponse, P_FE2CL_PC_LAUNCHER);
|
||||
}
|
||||
|
||||
static void ziplinePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
sP_CL2FE_REQ_PC_ZIPLINE* ziplineData = (sP_CL2FE_REQ_PC_ZIPLINE*)data->buf;
|
||||
PlayerManager::updatePlayerPosition(sock, ziplineData->iX, ziplineData->iY, ziplineData->iZ, plr->instanceID, ziplineData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_ZIPLINE, ziplineResponse);
|
||||
|
||||
ziplineResponse.iPC_ID = plr->iID;
|
||||
ziplineResponse.iCliTime = ziplineData->iCliTime;
|
||||
ziplineResponse.iSvrTime = tm;
|
||||
ziplineResponse.iX = ziplineData->iX;
|
||||
ziplineResponse.iY = ziplineData->iY;
|
||||
ziplineResponse.iZ = ziplineData->iZ;
|
||||
ziplineResponse.fVX = ziplineData->fVX;
|
||||
ziplineResponse.fVY = ziplineData->fVY;
|
||||
ziplineResponse.fVZ = ziplineData->fVZ;
|
||||
ziplineResponse.fMovDistance = ziplineData->fMovDistance;
|
||||
ziplineResponse.fMaxDistance = ziplineData->fMaxDistance;
|
||||
ziplineResponse.fDummy = ziplineData->fDummy; // wtf is this for?
|
||||
ziplineResponse.iStX = ziplineData->iStX;
|
||||
ziplineResponse.iStY = ziplineData->iStY;
|
||||
ziplineResponse.iStZ = ziplineData->iStZ;
|
||||
ziplineResponse.bDown = ziplineData->bDown;
|
||||
ziplineResponse.iSpeed = ziplineData->iSpeed;
|
||||
ziplineResponse.iAngle = ziplineData->iAngle;
|
||||
ziplineResponse.iRollMax = ziplineData->iRollMax;
|
||||
ziplineResponse.iRoll = ziplineData->iRoll;
|
||||
|
||||
PlayerManager::sendToViewable(sock, ziplineResponse, P_FE2CL_PC_ZIPLINE);
|
||||
}
|
||||
|
||||
static void movePlatformPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
auto platformData = (sP_CL2FE_REQ_PC_MOVEPLATFORM*)data->buf;
|
||||
PlayerManager::updatePlayerPosition(sock, platformData->iX, platformData->iY, platformData->iZ, plr->instanceID, platformData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_MOVEPLATFORM, platResponse);
|
||||
|
||||
platResponse.iPC_ID = plr->iID;
|
||||
platResponse.iCliTime = platformData->iCliTime;
|
||||
platResponse.iSvrTime = tm;
|
||||
platResponse.iX = platformData->iX;
|
||||
platResponse.iY = platformData->iY;
|
||||
platResponse.iZ = platformData->iZ;
|
||||
platResponse.iAngle = platformData->iAngle;
|
||||
platResponse.fVX = platformData->fVX;
|
||||
platResponse.fVY = platformData->fVY;
|
||||
platResponse.fVZ = platformData->fVZ;
|
||||
platResponse.iLcX = platformData->iLcX;
|
||||
platResponse.iLcY = platformData->iLcY;
|
||||
platResponse.iLcZ = platformData->iLcZ;
|
||||
platResponse.iSpeed = platformData->iSpeed;
|
||||
platResponse.bDown = platformData->bDown;
|
||||
platResponse.cKeyValue = platformData->cKeyValue;
|
||||
platResponse.iPlatformID = platformData->iPlatformID;
|
||||
|
||||
PlayerManager::sendToViewable(sock, platResponse, P_FE2CL_PC_MOVEPLATFORM);
|
||||
}
|
||||
|
||||
static void moveSliderPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
auto sliderData = (sP_CL2FE_REQ_PC_MOVETRANSPORTATION*)data->buf;
|
||||
PlayerManager::updatePlayerPosition(sock, sliderData->iX, sliderData->iY, sliderData->iZ, plr->instanceID, sliderData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_MOVETRANSPORTATION, sliderResponse);
|
||||
|
||||
sliderResponse.iPC_ID = plr->iID;
|
||||
sliderResponse.iCliTime = sliderData->iCliTime;
|
||||
sliderResponse.iSvrTime = tm;
|
||||
sliderResponse.iX = sliderData->iX;
|
||||
sliderResponse.iY = sliderData->iY;
|
||||
sliderResponse.iZ = sliderData->iZ;
|
||||
sliderResponse.iAngle = sliderData->iAngle;
|
||||
sliderResponse.fVX = sliderData->fVX;
|
||||
sliderResponse.fVY = sliderData->fVY;
|
||||
sliderResponse.fVZ = sliderData->fVZ;
|
||||
sliderResponse.iLcX = sliderData->iLcX;
|
||||
sliderResponse.iLcY = sliderData->iLcY;
|
||||
sliderResponse.iLcZ = sliderData->iLcZ;
|
||||
sliderResponse.iSpeed = sliderData->iSpeed;
|
||||
sliderResponse.cKeyValue = sliderData->cKeyValue;
|
||||
sliderResponse.iT_ID = sliderData->iT_ID;
|
||||
|
||||
PlayerManager::sendToViewable(sock, sliderResponse, P_FE2CL_PC_MOVETRANSPORTATION);
|
||||
}
|
||||
|
||||
static void moveSlopePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
sP_CL2FE_REQ_PC_SLOPE* slopeData = (sP_CL2FE_REQ_PC_SLOPE*)data->buf;
|
||||
PlayerManager::updatePlayerPosition(sock, slopeData->iX, slopeData->iY, slopeData->iZ, plr->instanceID, slopeData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_SLOPE, slopeResponse);
|
||||
|
||||
slopeResponse.iPC_ID = plr->iID;
|
||||
slopeResponse.iCliTime = slopeData->iCliTime;
|
||||
slopeResponse.iSvrTime = tm;
|
||||
slopeResponse.iX = slopeData->iX;
|
||||
slopeResponse.iY = slopeData->iY;
|
||||
slopeResponse.iZ = slopeData->iZ;
|
||||
slopeResponse.iAngle = slopeData->iAngle;
|
||||
slopeResponse.fVX = slopeData->fVX;
|
||||
slopeResponse.fVY = slopeData->fVY;
|
||||
slopeResponse.fVZ = slopeData->fVZ;
|
||||
slopeResponse.iSpeed = slopeData->iSpeed;
|
||||
slopeResponse.cKeyValue = slopeData->cKeyValue;
|
||||
slopeResponse.iSlopeID = slopeData->iSlopeID;
|
||||
|
||||
PlayerManager::sendToViewable(sock, slopeResponse, P_FE2CL_PC_SLOPE);
|
||||
}
|
||||
|
||||
void PlayerMovement::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_MOVE, movePlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_STOP, stopPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_JUMP, jumpPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_JUMPPAD, jumppadPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_LAUNCHER, launchPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ZIPLINE, ziplinePlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_MOVEPLATFORM, movePlatformPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_MOVETRANSPORTATION, moveSliderPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SLOPE, moveSlopePlayer);
|
||||
}
|
||||
5
src/PlayerMovement.hpp
Normal file
5
src/PlayerMovement.hpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace PlayerMovement {
|
||||
void init();
|
||||
};
|
||||
192
src/Racing.cpp
Normal file
192
src/Racing.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
#include "Racing.hpp"
|
||||
|
||||
#include "db/Database.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "NPCManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Items.hpp"
|
||||
|
||||
using namespace Racing;
|
||||
|
||||
std::map<int32_t, EPInfo> Racing::EPData;
|
||||
std::map<CNSocket*, EPRace> Racing::EPRaces;
|
||||
std::map<int32_t, std::pair<std::vector<int>, std::vector<int>>> Racing::EPRewards;
|
||||
|
||||
static void racingStart(CNSocket* sock, CNPacketData* data) {
|
||||
auto req = (sP_CL2FE_REQ_EP_RACE_START*)data->buf;
|
||||
|
||||
if (NPCManager::NPCs.find(req->iStartEcomID) == NPCManager::NPCs.end())
|
||||
return; // starting line agent not found
|
||||
|
||||
int mapNum = MAPNUM(NPCManager::NPCs[req->iStartEcomID]->instanceID);
|
||||
if (EPData.find(mapNum) == EPData.end() || EPData[mapNum].EPID == 0)
|
||||
return; // IZ not found
|
||||
|
||||
// make ongoing race entry
|
||||
EPRace race = { {}, req->iEPRaceMode, req->iEPTicketItemSlotNum, getTime() / 1000 };
|
||||
EPRaces[sock] = race;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_EP_RACE_START_SUCC, resp);
|
||||
resp.iStartTick = 0; // ignored
|
||||
resp.iLimitTime = EPData[mapNum].maxTime;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_EP_RACE_START_SUCC);
|
||||
}
|
||||
|
||||
static void racingGetPod(CNSocket* sock, CNPacketData* data) {
|
||||
if (EPRaces.find(sock) == EPRaces.end())
|
||||
return; // race not found
|
||||
|
||||
auto req = (sP_CL2FE_REQ_EP_GET_RING*)data->buf;
|
||||
|
||||
if (EPRaces[sock].collectedRings.count(req->iRingLID))
|
||||
return; // can't collect the same ring twice
|
||||
|
||||
// without an anticheat system, we really don't have a choice but to honor the request
|
||||
// TODO: proximity check so players can't cheat the race by replaying packets
|
||||
EPRaces[sock].collectedRings.insert(req->iRingLID);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_EP_GET_RING_SUCC, resp);
|
||||
|
||||
resp.iRingLID = req->iRingLID;
|
||||
resp.iRingCount_Get = EPRaces[sock].collectedRings.size();
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_EP_GET_RING_SUCC);
|
||||
}
|
||||
|
||||
static void racingCancel(CNSocket* sock, CNPacketData* data) {
|
||||
if (EPRaces.find(sock) == EPRaces.end())
|
||||
return; // race not found
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
EPRaces.erase(sock);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_EP_RACE_CANCEL_SUCC, resp);
|
||||
sock->sendPacket(resp, P_FE2CL_REP_EP_RACE_CANCEL_SUCC);
|
||||
|
||||
/*
|
||||
* This request packet is used for both cancelling the race via the
|
||||
* NPC at the start, *and* failing the race by running out of time.
|
||||
* If the latter is to happen, the client disables movement until it
|
||||
* receives a packet from the server that re-enables it.
|
||||
*
|
||||
* So, in order to prevent a potential softlock we respawn the player.
|
||||
*/
|
||||
|
||||
WarpLocation* respawnLoc = PlayerManager::getRespawnPoint(plr);
|
||||
|
||||
if (respawnLoc != nullptr) {
|
||||
PlayerManager::sendPlayerTo(sock, respawnLoc->x, respawnLoc->y, respawnLoc->z, respawnLoc->instanceID);
|
||||
} else {
|
||||
// fallback, just respawn the player in-place if no suitable point is found
|
||||
PlayerManager::sendPlayerTo(sock, plr->x, plr->y, plr->z, plr->instanceID);
|
||||
}
|
||||
}
|
||||
|
||||
static void racingEnd(CNSocket* sock, CNPacketData* data) {
|
||||
if (EPRaces.find(sock) == EPRaces.end())
|
||||
return; // race not found
|
||||
|
||||
auto req = (sP_CL2FE_REQ_EP_RACE_END*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (NPCManager::NPCs.find(req->iEndEcomID) == NPCManager::NPCs.end())
|
||||
return; // finish line agent not found
|
||||
|
||||
int mapNum = MAPNUM(NPCManager::NPCs[req->iEndEcomID]->instanceID);
|
||||
if (EPData.find(mapNum) == EPData.end() || EPData[mapNum].EPID == 0)
|
||||
return; // IZ not found
|
||||
|
||||
EPInfo& epInfo = EPData[mapNum];
|
||||
EPRace& epRace = EPRaces[sock];
|
||||
|
||||
uint64_t now = getTime() / 1000;
|
||||
int timeDiff = now - epRace.startTime;
|
||||
int podsCollected = epRace.collectedRings.size();
|
||||
int score = 0, fm = 0;
|
||||
|
||||
if (settings::OGRACINGSCORES) {
|
||||
score = std::min(epInfo.maxScore, (int)std::exp(
|
||||
(epInfo.podFactor * podsCollected) / epInfo.maxPods
|
||||
- (epInfo.timeFactor * timeDiff) / epInfo.maxTime
|
||||
+ epInfo.scaleFactor));
|
||||
fm = (1.0 + std::exp(epInfo.scaleFactor - 1.0) * epInfo.podFactor * podsCollected) / epInfo.maxPods;
|
||||
} else {
|
||||
score = std::max(0, 500 * podsCollected - 10 * timeDiff);
|
||||
fm = score * plr->level * (1.0f / 36) * 0.3f;
|
||||
}
|
||||
|
||||
// we submit the ranking first...
|
||||
Database::RaceRanking postRanking = {};
|
||||
postRanking.EPID = epInfo.EPID;
|
||||
postRanking.PlayerID = plr->iID;
|
||||
postRanking.RingCount = podsCollected;
|
||||
postRanking.Score = score;
|
||||
postRanking.Time = timeDiff;
|
||||
postRanking.Timestamp = getTimestamp();
|
||||
Database::postRaceRanking(postRanking);
|
||||
|
||||
// ...then we get the top ranking, which may or may not be what we just submitted
|
||||
Database::RaceRanking topRankingPlayer = Database::getTopRaceRanking(epInfo.EPID, plr->iID);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_EP_RACE_END_SUCC, resp);
|
||||
|
||||
// get rank scores and rewards
|
||||
std::vector<int>* rankScores = &EPRewards[epInfo.EPID].first;
|
||||
std::vector<int>* rankRewards = &EPRewards[epInfo.EPID].second;
|
||||
|
||||
// top ranking
|
||||
int topRank = 0;
|
||||
while (rankScores->at(topRank) > topRankingPlayer.Score)
|
||||
topRank++;
|
||||
|
||||
resp.iEPTopRank = topRank + 1;
|
||||
resp.iEPTopRingCount = topRankingPlayer.RingCount;
|
||||
resp.iEPTopScore = topRankingPlayer.Score;
|
||||
resp.iEPTopTime = topRankingPlayer.Time;
|
||||
|
||||
// this ranking
|
||||
int rank = 0;
|
||||
while (rankScores->at(rank) > postRanking.Score)
|
||||
rank++;
|
||||
|
||||
resp.iEPRank = rank + 1;
|
||||
resp.iEPRingCnt = postRanking.RingCount;
|
||||
resp.iEPScore = postRanking.Score;
|
||||
resp.iEPRaceTime = postRanking.Time;
|
||||
resp.iEPRaceMode = EPRaces[sock].mode;
|
||||
resp.iEPRewardFM = fm;
|
||||
|
||||
Missions::updateFusionMatter(sock, resp.iEPRewardFM);
|
||||
|
||||
resp.iFusionMatter = plr->fusionmatter;
|
||||
resp.iFatigue = 50;
|
||||
resp.iFatigue_Level = 1;
|
||||
|
||||
sItemReward reward;
|
||||
reward.iSlotNum = Items::findFreeSlot(plr);
|
||||
reward.eIL = 1;
|
||||
sItemBase item;
|
||||
item.iID = rankRewards->at(rank); // rank scores and rewards line up
|
||||
item.iType = 9;
|
||||
item.iOpt = 1;
|
||||
item.iTimeLimit = 0;
|
||||
reward.sItem = item;
|
||||
|
||||
if (reward.iSlotNum > -1 && reward.sItem.iID != 0) {
|
||||
resp.RewardItem = reward;
|
||||
plr->Inven[reward.iSlotNum] = item;
|
||||
}
|
||||
|
||||
EPRaces.erase(sock);
|
||||
sock->sendPacket(resp, P_FE2CL_REP_EP_RACE_END_SUCC);
|
||||
}
|
||||
|
||||
void Racing::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_EP_RACE_START, racingStart);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_EP_GET_RING, racingGetPod);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_EP_RACE_CANCEL, racingCancel);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_EP_RACE_END, racingEnd);
|
||||
}
|
||||
29
src/Racing.hpp
Normal file
29
src/Racing.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
struct EPInfo {
|
||||
// available through XDT (maxScore may be updated by drops)
|
||||
int zoneX, zoneY, EPID, maxScore;
|
||||
// (maybe) available through drops
|
||||
int maxTime = 0, maxPods = 0;
|
||||
double scaleFactor = 0.0, podFactor = 0.0, timeFactor = 0.0;
|
||||
};
|
||||
|
||||
struct EPRace {
|
||||
std::set<int> collectedRings;
|
||||
int mode, ticketSlot;
|
||||
time_t startTime;
|
||||
};
|
||||
|
||||
namespace Racing {
|
||||
extern std::map<int32_t, EPInfo> EPData;
|
||||
extern std::map<CNSocket*, EPRace> EPRaces;
|
||||
extern std::map<int32_t, std::pair<std::vector<int>, std::vector<int>>> EPRewards;
|
||||
|
||||
void init();
|
||||
}
|
||||
91
src/Rand.cpp
Normal file
91
src/Rand.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "Rand.hpp"
|
||||
#include "core/Core.hpp"
|
||||
|
||||
std::unique_ptr<std::mt19937> Rand::generator;
|
||||
|
||||
int32_t Rand::rand(int32_t startInclusive, int32_t endExclusive) {
|
||||
std::uniform_int_distribution<int32_t> dist(startInclusive, endExclusive - 1);
|
||||
return dist(*Rand::generator);
|
||||
}
|
||||
|
||||
int32_t Rand::rand(int32_t endExclusive) {
|
||||
return Rand::rand(0, endExclusive);
|
||||
}
|
||||
|
||||
int32_t Rand::rand() {
|
||||
return Rand::rand(0, INT32_MAX);
|
||||
}
|
||||
|
||||
int32_t Rand::randWeighted(const std::vector<int32_t>& weights) {
|
||||
std::discrete_distribution<int32_t> dist(weights.begin(), weights.end());
|
||||
return dist(*Rand::generator);
|
||||
}
|
||||
|
||||
float Rand::randFloat(float startInclusive, float endExclusive) {
|
||||
std::uniform_real_distribution<float> dist(startInclusive, endExclusive);
|
||||
return dist(*Rand::generator);
|
||||
}
|
||||
|
||||
float Rand::randFloat(float endExclusive) {
|
||||
return Rand::randFloat(0.0f, endExclusive);
|
||||
}
|
||||
|
||||
float Rand::randFloat() {
|
||||
return Rand::randFloat(0.0f, 1.0f);
|
||||
}
|
||||
|
||||
#define RANDBYTES 8
|
||||
|
||||
/*
|
||||
* Cryptographically secure RNG. Borrowed from bcrypt_gensalt().
|
||||
*/
|
||||
uint64_t Rand::cryptoRand() {
|
||||
uint8_t buf[RANDBYTES];
|
||||
|
||||
#ifdef _WIN32
|
||||
HCRYPTPROV p;
|
||||
|
||||
// Acquire a crypt context for generating random bytes.
|
||||
if (CryptAcquireContext(&p, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) == FALSE) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (CryptGenRandom(p, RANDBYTES, (BYTE*)buf) == FALSE) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (CryptReleaseContext(p, 0) == FALSE) {
|
||||
goto fail;
|
||||
}
|
||||
#else
|
||||
int fd;
|
||||
|
||||
// Get random bytes on Unix/Linux.
|
||||
fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
perror("open");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (read(fd, buf, RANDBYTES) < RANDBYTES) {
|
||||
perror("read");
|
||||
close(fd);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
#endif
|
||||
|
||||
return *(uint64_t*)buf;
|
||||
|
||||
fail:
|
||||
std::cout << "[FATAL] Failed to generate cryptographic random number" << std::endl;
|
||||
terminate(0);
|
||||
|
||||
/* not reached */
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Rand::init(uint64_t seed) {
|
||||
Rand::generator = std::make_unique<std::mt19937>(std::mt19937(seed));
|
||||
}
|
||||
23
src/Rand.hpp
Normal file
23
src/Rand.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <random>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Rand {
|
||||
extern std::unique_ptr<std::mt19937> generator;
|
||||
|
||||
void init(uint64_t seed);
|
||||
|
||||
int32_t rand(int32_t startInclusive, int32_t endExclusive);
|
||||
int32_t rand(int32_t endExclusive);
|
||||
int32_t rand();
|
||||
|
||||
int32_t randWeighted(const std::vector<int32_t>& weights);
|
||||
|
||||
uint64_t cryptoRand();
|
||||
|
||||
float randFloat(float startInclusive, float endExclusive);
|
||||
float randFloat(float endExclusive);
|
||||
float randFloat();
|
||||
};
|
||||
1484
src/TableData.cpp
1484
src/TableData.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,24 +1,32 @@
|
||||
#pragma once
|
||||
#include <map>
|
||||
|
||||
#include "contrib/JSON.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "JSON.hpp"
|
||||
|
||||
#include "Entities.hpp"
|
||||
#include "Transport.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// these are added to the NPC's static key to avoid collisions
|
||||
const int NPC_ID_OFFSET = 1;
|
||||
const int MOB_ID_OFFSET = 10000;
|
||||
const int MOB_GROUP_ID_OFFSET = 20000;
|
||||
|
||||
// typedef for JSON object because I don't want to type nlohmann::json every time
|
||||
typedef nlohmann::json json;
|
||||
|
||||
namespace TableData {
|
||||
extern std::map<int32_t, std::vector<WarpLocation>> RunningSkywayRoutes;
|
||||
extern std::map<int32_t, std::vector<Vec3>> RunningSkywayRoutes;
|
||||
extern std::map<int32_t, int> RunningNPCRotations;
|
||||
extern std::map<int32_t, int> RunningNPCMapNumbers;
|
||||
extern std::unordered_map<int32_t, std::pair<BaseNPC*, std::vector<BaseNPC*>>> RunningNPCPaths; // player ID -> following NPC
|
||||
extern std::vector<NPCPath> FinishedNPCPaths; // NPC ID -> path
|
||||
extern std::map<int32_t, BaseNPC*> RunningMobs;
|
||||
extern std::map<int32_t, BaseNPC*> RunningGroups;
|
||||
extern std::map<int32_t, BaseNPC*> RunningEggs;
|
||||
|
||||
void init();
|
||||
void cleanup();
|
||||
void loadGruntwork(int32_t*);
|
||||
void flush();
|
||||
|
||||
int getItemType(int);
|
||||
void loadPaths(int*);
|
||||
void loadDrops();
|
||||
void constructPathSkyway(nlohmann::json::iterator);
|
||||
void constructPathSlider(nlohmann::json, int, int);
|
||||
void constructPathNPC(nlohmann::json::iterator, int id=0);
|
||||
}
|
||||
|
||||
438
src/Trading.cpp
Normal file
438
src/Trading.cpp
Normal file
@@ -0,0 +1,438 @@
|
||||
#include "Trading.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "db/Database.hpp"
|
||||
|
||||
using namespace Trading;
|
||||
|
||||
static bool doTrade(Player* plr, Player* plr2) {
|
||||
// init dummy inventories
|
||||
sItemBase plrInven[AINVEN_COUNT];
|
||||
sItemBase plr2Inven[AINVEN_COUNT];
|
||||
memcpy(plrInven, plr->Inven, AINVEN_COUNT * sizeof(sItemBase));
|
||||
memcpy(plr2Inven, plr2->Inven, AINVEN_COUNT * sizeof(sItemBase));
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
// remove items offered by us
|
||||
if (plr->Trade[i].iID != 0) {
|
||||
if (plrInven[plr->Trade[i].iInvenNum].iID == 0
|
||||
|| plr->Trade[i].iID != plrInven[plr->Trade[i].iInvenNum].iID
|
||||
|| plr->Trade[i].iType != plrInven[plr->Trade[i].iInvenNum].iType) // pulling a fast one on us
|
||||
return false;
|
||||
|
||||
if (plr->Trade[i].iOpt < 1) {
|
||||
std::cout << "[WARN] Player tried trading an iOpt < 1 amount" << std::endl;
|
||||
plr->Trade[i].iOpt = 1;
|
||||
}
|
||||
|
||||
// for stacked items
|
||||
plrInven[plr->Trade[i].iInvenNum].iOpt -= plr->Trade[i].iOpt;
|
||||
|
||||
if (plrInven[plr->Trade[i].iInvenNum].iOpt == 0) {
|
||||
plrInven[plr->Trade[i].iInvenNum].iID = 0;
|
||||
plrInven[plr->Trade[i].iInvenNum].iType = 0;
|
||||
plrInven[plr->Trade[i].iInvenNum].iOpt = 0;
|
||||
} else if (plrInven[plr->Trade[i].iInvenNum].iOpt < 0) { // another dupe attempt
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (plr2->Trade[i].iID != 0) {
|
||||
if (plr2Inven[plr2->Trade[i].iInvenNum].iID == 0
|
||||
|| plr2->Trade[i].iID != plr2Inven[plr2->Trade[i].iInvenNum].iID
|
||||
|| plr2->Trade[i].iType != plr2Inven[plr2->Trade[i].iInvenNum].iType) // pulling a fast one on us
|
||||
return false;
|
||||
|
||||
if (plr2->Trade[i].iOpt < 1) {
|
||||
std::cout << "[WARN] Player tried trading an iOpt < 1 amount" << std::endl;
|
||||
plr2->Trade[i].iOpt = 1;
|
||||
}
|
||||
|
||||
// for stacked items
|
||||
plr2Inven[plr2->Trade[i].iInvenNum].iOpt -= plr2->Trade[i].iOpt;
|
||||
|
||||
if (plr2Inven[plr2->Trade[i].iInvenNum].iOpt == 0) {
|
||||
plr2Inven[plr2->Trade[i].iInvenNum].iID = 0;
|
||||
plr2Inven[plr2->Trade[i].iInvenNum].iType = 0;
|
||||
plr2Inven[plr2->Trade[i].iInvenNum].iOpt = 0;
|
||||
} else if (plr2Inven[plr2->Trade[i].iInvenNum].iOpt < 0) { // another dupe attempt
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// add items offered to us
|
||||
if (plr2->Trade[i].iID != 0) {
|
||||
for (int n = 0; n < AINVEN_COUNT; n++) {
|
||||
if (plrInven[n].iID == 0) {
|
||||
plrInven[n].iID = plr2->Trade[i].iID;
|
||||
plrInven[n].iType = plr2->Trade[i].iType;
|
||||
plrInven[n].iOpt = plr2->Trade[i].iOpt;
|
||||
plr2->Trade[i].iInvenNum = n;
|
||||
break;
|
||||
}
|
||||
|
||||
if (n >= AINVEN_COUNT - 1)
|
||||
return false; // not enough space
|
||||
}
|
||||
}
|
||||
|
||||
if (plr->Trade[i].iID != 0) {
|
||||
for (int n = 0; n < AINVEN_COUNT; n++) {
|
||||
if (plr2Inven[n].iID == 0) {
|
||||
plr2Inven[n].iID = plr->Trade[i].iID;
|
||||
plr2Inven[n].iType = plr->Trade[i].iType;
|
||||
plr2Inven[n].iOpt = plr->Trade[i].iOpt;
|
||||
plr->Trade[i].iInvenNum = n;
|
||||
break;
|
||||
}
|
||||
|
||||
if (n >= AINVEN_COUNT - 1)
|
||||
return false; // not enough space
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if everything went well, back into player inventory it goes
|
||||
memcpy(plr->Inven, plrInven, AINVEN_COUNT * sizeof(sItemBase));
|
||||
memcpy(plr2->Inven, plr2Inven, AINVEN_COUNT * sizeof(sItemBase));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void tradeOffer(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TRADE_OFFER* pacdat = (sP_CL2FE_REQ_PC_TRADE_OFFER*)data->buf;
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(pacdat->iID_To);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(otherSock);
|
||||
|
||||
if (plr->isTrading) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_OFFER_REFUSAL, resp);
|
||||
resp.iID_Request = pacdat->iID_To;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_OFFER_REFUSAL, sizeof(sP_FE2CL_REP_PC_TRADE_OFFER_REFUSAL));
|
||||
return; // prevent trading with a player already trading
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_OFFER, resp);
|
||||
resp.iID_Request = pacdat->iID_Request;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_OFFER, sizeof(sP_FE2CL_REP_PC_TRADE_OFFER));
|
||||
}
|
||||
|
||||
static void tradeOfferAccept(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TRADE_OFFER_ACCEPT* pacdat = (sP_CL2FE_REQ_PC_TRADE_OFFER_ACCEPT*)data->buf;
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(pacdat->iID_From);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* plr2 = PlayerManager::getPlayer(otherSock);
|
||||
|
||||
if (plr2->isTrading) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_OFFER_REFUSAL, resp);
|
||||
resp.iID_Request = pacdat->iID_From;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_OFFER_REFUSAL, sizeof(sP_FE2CL_REP_PC_TRADE_OFFER_REFUSAL));
|
||||
return; // prevent trading with a player already trading
|
||||
}
|
||||
|
||||
// clearing up trade slots
|
||||
plr->moneyInTrade = 0;
|
||||
plr2->moneyInTrade = 0;
|
||||
memset(&plr->Trade, 0, sizeof(plr->Trade));
|
||||
memset(&plr2->Trade, 0, sizeof(plr2->Trade));
|
||||
|
||||
// marking players as traders
|
||||
plr->isTrading = true;
|
||||
plr2->isTrading = true;
|
||||
|
||||
// marking players as unconfirmed
|
||||
plr->isTradeConfirm = false;
|
||||
plr2->isTradeConfirm = false;
|
||||
|
||||
// inform the other player that offer is accepted
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_OFFER, resp);
|
||||
resp.iID_Request = pacdat->iID_Request;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_OFFER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_OFFER_SUCC));
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_OFFER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_OFFER_SUCC));
|
||||
}
|
||||
|
||||
static void tradeOfferRefusal(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TRADE_OFFER_REFUSAL* pacdat = (sP_CL2FE_REQ_PC_TRADE_OFFER_REFUSAL*)data->buf;
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(pacdat->iID_From);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_OFFER_REFUSAL, resp);
|
||||
resp.iID_Request = pacdat->iID_Request;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_OFFER_REFUSAL, sizeof(sP_FE2CL_REP_PC_TRADE_OFFER_REFUSAL));
|
||||
}
|
||||
|
||||
static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TRADE_CONFIRM* pacdat = (sP_CL2FE_REQ_PC_TRADE_CONFIRM*)data->buf;
|
||||
|
||||
CNSocket* otherSock; // weird flip flop because we need to know who the other player is
|
||||
if (pacdat->iID_Request == pacdat->iID_From)
|
||||
otherSock = PlayerManager::getSockFromID(pacdat->iID_To);
|
||||
else
|
||||
otherSock = PlayerManager::getSockFromID(pacdat->iID_From);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* plr2 = PlayerManager::getPlayer(otherSock);
|
||||
|
||||
if (!(plr->isTrading && plr2->isTrading)) { // both players must be trading
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, resp);
|
||||
resp.iID_Request = plr2->iID;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT));
|
||||
resp.iID_Request = plr->iID;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT));
|
||||
|
||||
// both players are no longer trading
|
||||
plr->isTrading = false;
|
||||
plr2->isTrading = false;
|
||||
plr->isTradeConfirm = false;
|
||||
plr2->isTradeConfirm = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// send the confirm packet
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_CONFIRM, resp);
|
||||
resp.iID_Request = pacdat->iID_Request;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CONFIRM, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM));
|
||||
|
||||
if (!(plr2->isTradeConfirm)) {
|
||||
plr->isTradeConfirm = true;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CONFIRM, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM));
|
||||
return;
|
||||
}
|
||||
|
||||
// both players are no longer trading
|
||||
plr->isTrading = false;
|
||||
plr2->isTrading = false;
|
||||
plr->isTradeConfirm = false;
|
||||
plr2->isTradeConfirm = false;
|
||||
|
||||
if (doTrade(plr, plr2)) { // returns false if not enough slots
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_CONFIRM_SUCC, resp2);
|
||||
resp2.iID_Request = pacdat->iID_Request;
|
||||
resp2.iID_From = pacdat->iID_From;
|
||||
resp2.iID_To = pacdat->iID_To;
|
||||
plr->money = plr->money + plr2->moneyInTrade - plr->moneyInTrade;
|
||||
resp2.iCandy = plr->money;
|
||||
memcpy(resp2.Item, plr2->Trade, sizeof(plr2->Trade));
|
||||
memcpy(resp2.ItemStay, plr->Trade, sizeof(plr->Trade));
|
||||
|
||||
sock->sendPacket((void*)&resp2, P_FE2CL_REP_PC_TRADE_CONFIRM_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_SUCC));
|
||||
|
||||
plr2->money = plr2->money + plr->moneyInTrade - plr2->moneyInTrade;
|
||||
resp2.iCandy = plr2->money;
|
||||
memcpy(resp2.Item, plr->Trade, sizeof(plr->Trade));
|
||||
memcpy(resp2.ItemStay, plr2->Trade, sizeof(plr2->Trade));
|
||||
|
||||
otherSock->sendPacket((void*)&resp2, P_FE2CL_REP_PC_TRADE_CONFIRM_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_SUCC));
|
||||
} else {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, resp);
|
||||
resp.iID_Request = plr->iID;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT));
|
||||
resp.iID_Request = plr2->iID;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT));
|
||||
|
||||
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
|
||||
std::string text = "Trade Failed";
|
||||
U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg));
|
||||
msg.iDuringTime = 3;
|
||||
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||
otherSock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||
return;
|
||||
}
|
||||
|
||||
Database::commitTrade(plr, plr2);
|
||||
}
|
||||
|
||||
static void tradeConfirmCancel(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TRADE_CONFIRM_CANCEL* pacdat = (sP_CL2FE_REQ_PC_TRADE_CONFIRM_CANCEL*)data->buf;
|
||||
|
||||
CNSocket* otherSock; // weird flip flop because we need to know who the other player is
|
||||
if (pacdat->iID_Request == pacdat->iID_From)
|
||||
otherSock = PlayerManager::getSockFromID(pacdat->iID_To);
|
||||
else
|
||||
otherSock = PlayerManager::getSockFromID(pacdat->iID_From);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* plr2 = PlayerManager::getPlayer(otherSock);
|
||||
|
||||
// both players are not trading nor are in a confirmed state
|
||||
plr->isTrading = false;
|
||||
plr->isTradeConfirm = false;
|
||||
plr2->isTrading = false;
|
||||
plr2->isTradeConfirm = false;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_CONFIRM_CANCEL, resp);
|
||||
resp.iID_Request = pacdat->iID_Request;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CONFIRM_CANCEL, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_CANCEL));
|
||||
}
|
||||
|
||||
static void tradeRegisterItem(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TRADE_ITEM_REGISTER* pacdat = (sP_CL2FE_REQ_PC_TRADE_ITEM_REGISTER*)data->buf;
|
||||
|
||||
if (pacdat->Item.iSlotNum < 0 || pacdat->Item.iSlotNum > 4)
|
||||
return; // sanity check, there are only 5 trade slots
|
||||
|
||||
CNSocket* otherSock; // weird flip flop because we need to know who the other player is
|
||||
if (pacdat->iID_Request == pacdat->iID_From)
|
||||
otherSock = PlayerManager::getSockFromID(pacdat->iID_To);
|
||||
else
|
||||
otherSock = PlayerManager::getSockFromID(pacdat->iID_From);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* plr2 = PlayerManager::getPlayer(otherSock);
|
||||
plr->Trade[pacdat->Item.iSlotNum] = pacdat->Item;
|
||||
plr->isTradeConfirm = false;
|
||||
plr2->isTradeConfirm = false;
|
||||
|
||||
// since you can spread items like gumballs over multiple slots, we need to count them all
|
||||
// to make sure the inventory shows the right value during trade.
|
||||
int count = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (plr->Trade[i].iInvenNum == pacdat->Item.iInvenNum)
|
||||
count += plr->Trade[i].iOpt;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_ITEM_REGISTER_SUCC, resp);
|
||||
resp.iID_Request = pacdat->iID_Request;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
resp.TradeItem = pacdat->Item;
|
||||
resp.InvenItem = pacdat->Item;
|
||||
resp.InvenItem.iOpt = plr->Inven[pacdat->Item.iInvenNum].iOpt - count; // subtract this count
|
||||
|
||||
if (resp.InvenItem.iOpt < 0) // negative count items, doTrade() will block this later on
|
||||
std::cout << "[WARN] tradeRegisterItem: an item went negative count client side." << std::endl;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_ITEM_REGISTER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_ITEM_REGISTER_SUCC));
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_ITEM_REGISTER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_ITEM_REGISTER_SUCC));
|
||||
}
|
||||
|
||||
static void tradeUnregisterItem(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TRADE_ITEM_UNREGISTER* pacdat = (sP_CL2FE_REQ_PC_TRADE_ITEM_UNREGISTER*)data->buf;
|
||||
|
||||
if (pacdat->Item.iSlotNum < 0 || pacdat->Item.iSlotNum > 4)
|
||||
return; // sanity check, there are only 5 trade slots
|
||||
|
||||
CNSocket* otherSock; // weird flip flop because we need to know who the other player is
|
||||
if (pacdat->iID_Request == pacdat->iID_From)
|
||||
otherSock = PlayerManager::getSockFromID(pacdat->iID_To);
|
||||
else
|
||||
otherSock = PlayerManager::getSockFromID(pacdat->iID_From);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* plr2 = PlayerManager::getPlayer(otherSock);
|
||||
plr->isTradeConfirm = false;
|
||||
plr2->isTradeConfirm = false;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_ITEM_UNREGISTER_SUCC, resp);
|
||||
resp.iID_Request = pacdat->iID_Request;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
resp.TradeItem = pacdat->Item;
|
||||
resp.InvenItem = plr->Trade[pacdat->Item.iSlotNum];
|
||||
|
||||
memset(&plr->Trade[pacdat->Item.iSlotNum], 0, sizeof(plr->Trade[pacdat->Item.iSlotNum])); // clean up item slot
|
||||
|
||||
// since you can spread items like gumballs over multiple slots, we need to count them all
|
||||
// to make sure the inventory shows the right value during trade.
|
||||
int count = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (plr->Trade[i].iInvenNum == resp.InvenItem.iInvenNum)
|
||||
count += plr->Trade[i].iOpt;
|
||||
}
|
||||
|
||||
resp.InvenItem.iOpt = plr->Inven[resp.InvenItem.iInvenNum].iOpt - count; // subtract this count
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_ITEM_UNREGISTER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_ITEM_UNREGISTER_SUCC));
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_ITEM_UNREGISTER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_ITEM_UNREGISTER_SUCC));
|
||||
}
|
||||
|
||||
static void tradeRegisterCash(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TRADE_CASH_REGISTER* pacdat = (sP_CL2FE_REQ_PC_TRADE_CASH_REGISTER*)data->buf;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (pacdat->iCandy < 0 || pacdat->iCandy > plr->money)
|
||||
return; // famous glitch, begone
|
||||
|
||||
CNSocket* otherSock; // weird flip flop because we need to know who the other player is
|
||||
if (pacdat->iID_Request == pacdat->iID_From)
|
||||
otherSock = PlayerManager::getSockFromID(pacdat->iID_To);
|
||||
else
|
||||
otherSock = PlayerManager::getSockFromID(pacdat->iID_From);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
Player* plr2 = PlayerManager::getPlayer(otherSock);
|
||||
plr->isTradeConfirm = false;
|
||||
plr2->isTradeConfirm = false;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC, resp);
|
||||
resp.iID_Request = pacdat->iID_Request;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
resp.iCandy = pacdat->iCandy;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC));
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC));
|
||||
|
||||
plr->moneyInTrade = pacdat->iCandy;
|
||||
plr->isTradeConfirm = false;
|
||||
}
|
||||
|
||||
void Trading::init() {
|
||||
// Trade handlers
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_OFFER, tradeOffer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_OFFER_ACCEPT, tradeOfferAccept);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_OFFER_REFUSAL, tradeOfferRefusal);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_CONFIRM, tradeConfirm);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_CONFIRM_CANCEL, tradeConfirmCancel);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_ITEM_REGISTER, tradeRegisterItem);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_ITEM_UNREGISTER, tradeUnregisterItem);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_CASH_REGISTER, tradeRegisterCash);
|
||||
}
|
||||
5
src/Trading.hpp
Normal file
5
src/Trading.hpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace Trading {
|
||||
void init();
|
||||
}
|
||||
@@ -1,37 +1,30 @@
|
||||
#include "CNShardServer.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "Transport.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
#include "TransportManager.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "TableData.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "Entities.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "MobAI.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <cmath>
|
||||
|
||||
std::map<int32_t, TransportRoute> TransportManager::Routes;
|
||||
std::map<int32_t, TransportLocation> TransportManager::Locations;
|
||||
std::map<int32_t, std::queue<WarpLocation>> TransportManager::SkywayPaths;
|
||||
std::unordered_map<CNSocket*, std::queue<WarpLocation>> TransportManager::SkywayQueues;
|
||||
std::unordered_map<int32_t, std::queue<WarpLocation>> TransportManager::NPCQueues;
|
||||
using namespace Transport;
|
||||
|
||||
void TransportManager::init() {
|
||||
REGISTER_SHARD_TIMER(tickTransportationSystem, 1000);
|
||||
std::map<int32_t, TransportRoute> Transport::Routes;
|
||||
std::map<int32_t, TransportLocation> Transport::Locations;
|
||||
std::vector<NPCPath> Transport::NPCPaths;
|
||||
std::map<int32_t, std::queue<Vec3>> Transport::SkywayPaths;
|
||||
std::unordered_map<CNSocket*, std::queue<Vec3>> Transport::SkywayQueues;
|
||||
std::unordered_map<int32_t, std::queue<Vec3>> Transport::NPCQueues;
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION, transportRegisterLocationHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION, transportWarpHandler);
|
||||
}
|
||||
|
||||
void TransportManager::transportRegisterLocationHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION* transport = (sP_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION*)data->buf;
|
||||
static void transportRegisterLocationHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto transport = (sP_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
bool newReg = false; // this is a new registration
|
||||
//std::cout << "request to register transport, eTT " << transport->eTT << ", locID " << transport->iLocationID << ", npc " << transport->iNPC_ID << std::endl;
|
||||
if (transport->eTT == 1) { // S.C.A.M.P.E.R.
|
||||
@@ -43,12 +36,12 @@ void TransportManager::transportRegisterLocationHandler(CNSocket* sock, CNPacket
|
||||
failResp.iErrorCode = 0; // TODO: review what error code to use here
|
||||
failResp.iLocationID = transport->iLocationID;
|
||||
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, sizeof(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL));
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
// update registration bitfield using bitmask
|
||||
uint32_t newScamperFlag = plr->iWarpLocationFlag | (plr->accountLevel <= 40 ? INT32_MAX : (1UL << (transport->iLocationID - 1)));
|
||||
uint32_t newScamperFlag = plr->iWarpLocationFlag | (1UL << (transport->iLocationID - 1));
|
||||
if (newScamperFlag != plr->iWarpLocationFlag) {
|
||||
plr->iWarpLocationFlag = newScamperFlag;
|
||||
newReg = true;
|
||||
@@ -62,24 +55,18 @@ void TransportManager::transportRegisterLocationHandler(CNSocket* sock, CNPacket
|
||||
failResp.iErrorCode = 0; // TODO: review what error code to use here
|
||||
failResp.iLocationID = transport->iLocationID;
|
||||
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, sizeof(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL));
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* assuming the two bitfields are just stuck together to make a longer one, do a similar operation
|
||||
*/
|
||||
if (plr->accountLevel <= 40) {
|
||||
plr->aSkywayLocationFlag[0] = INT64_MAX;
|
||||
plr->aSkywayLocationFlag[1] = INT64_MAX;
|
||||
int index = transport->iLocationID > 64 ? 1 : 0;
|
||||
uint64_t newMonkeyFlag = plr->aSkywayLocationFlag[index] | (1ULL << (index ? transport->iLocationID - 65 : transport->iLocationID - 1));
|
||||
if (newMonkeyFlag != plr->aSkywayLocationFlag[index]) {
|
||||
plr->aSkywayLocationFlag[index] = newMonkeyFlag;
|
||||
newReg = true;
|
||||
} else {
|
||||
int index = transport->iLocationID > 64 ? 1 : 0;
|
||||
uint64_t newMonkeyFlag = plr->aSkywayLocationFlag[index] | (1ULL << (index ? transport->iLocationID - 65 : transport->iLocationID - 1));
|
||||
if (newMonkeyFlag != plr->aSkywayLocationFlag[index]) {
|
||||
plr->aSkywayLocationFlag[index] = newMonkeyFlag;
|
||||
newReg = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::cout << "[WARN] Unknown mode of transport; eTT = " << transport->eTT << std::endl;
|
||||
@@ -89,7 +76,7 @@ void TransportManager::transportRegisterLocationHandler(CNSocket* sock, CNPacket
|
||||
failResp.iErrorCode = 0; // TODO: review what error code to use here
|
||||
failResp.iLocationID = transport->iLocationID;
|
||||
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, sizeof(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL));
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,19 +91,13 @@ void TransportManager::transportRegisterLocationHandler(CNSocket* sock, CNPacket
|
||||
resp.aWyvernLocationFlag[0] = plr->aSkywayLocationFlag[0];
|
||||
resp.aWyvernLocationFlag[1] = plr->aSkywayLocationFlag[1];
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_SUCC, sizeof(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_SUCC));
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_SUCC);
|
||||
}
|
||||
|
||||
void TransportManager::transportWarpHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION* req = (sP_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION*)data->buf;
|
||||
static void transportWarpHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto req = (sP_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
/*
|
||||
* req:
|
||||
* eIL -- inventory type
|
||||
@@ -131,39 +112,33 @@ void TransportManager::transportWarpHandler(CNSocket* sock, CNPacketData* data)
|
||||
failResp.iErrorCode = 0; // TODO: error code
|
||||
failResp.iTransportationID = req->iTransporationID;
|
||||
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_FAIL, sizeof(sP_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_FAIL));
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
TransportRoute route = Routes[req->iTransporationID];
|
||||
plr->money -= route.cost;
|
||||
|
||||
TransportLocation target;
|
||||
PlayerView& plrv = PlayerManager::players[sock];
|
||||
TransportLocation* target = nullptr;
|
||||
switch (route.type) {
|
||||
case 1: // S.C.A.M.P.E.R.
|
||||
target = Locations[route.end];
|
||||
plr->x = target.x;
|
||||
plr->y = target.y;
|
||||
plr->z = target.z;
|
||||
/*
|
||||
* Not strictly necessary since there isn't a valid SCAMPER that puts you in the
|
||||
* same map tile you were already in, but we might as well force an NPC reload.
|
||||
*/
|
||||
PlayerManager::removePlayerFromChunks(plrv.currentChunks, sock);
|
||||
plrv.currentChunks.clear();
|
||||
plrv.chunkPos = std::make_tuple(0, 0, plrv.plr->instanceID);
|
||||
target = &Locations[route.end];
|
||||
break;
|
||||
case 2: // Monkey Skyway
|
||||
// set last safe coords
|
||||
plr->lastX = plr->x;
|
||||
plr->lastY = plr->y;
|
||||
plr->lastZ = plr->z;
|
||||
if (SkywayPaths.find(route.mssRouteNum) != SkywayPaths.end()) { // check if route exists
|
||||
NanoManager::summonNano(sock, -1); // make sure that no nano is active during the ride
|
||||
Nanos::summonNano(sock, -1); // make sure that no nano is active during the ride
|
||||
SkywayQueues[sock] = SkywayPaths[route.mssRouteNum]; // set socket point queue to route
|
||||
plr->onMonkey = true;
|
||||
break;
|
||||
} else if (TableData::RunningSkywayRoutes.find(route.mssRouteNum) != TableData::RunningSkywayRoutes.end()) {
|
||||
std::vector<WarpLocation>* _route = &TableData::RunningSkywayRoutes[route.mssRouteNum];
|
||||
|
||||
NanoManager::summonNano(sock, -1);
|
||||
std::vector<Vec3>* _route = &TableData::RunningSkywayRoutes[route.mssRouteNum];
|
||||
Nanos::summonNano(sock, -1);
|
||||
testMssRoute(sock, _route);
|
||||
plr->onMonkey = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -173,7 +148,7 @@ void TransportManager::transportWarpHandler(CNSocket* sock, CNPacketData* data)
|
||||
alert.iAnnounceType = 0; // don't think this lets us make a confirm dialog
|
||||
alert.iDuringTime = 3;
|
||||
U8toU16("Skyway route " + std::to_string(route.mssRouteNum) + " isn't pathed yet. You will not be charged any taros.", (char16_t*)alert.szAnnounceMsg, sizeof(alert.szAnnounceMsg));
|
||||
sock->sendPacket((void*)&alert, P_FE2CL_ANNOUNCE_MSG, sizeof(sP_FE2CL_ANNOUNCE_MSG));
|
||||
sock->sendPacket(alert, P_FE2CL_ANNOUNCE_MSG);
|
||||
|
||||
std::cout << "[WARN] MSS route " << route.mssRouteNum << " not pathed" << std::endl;
|
||||
break;
|
||||
@@ -186,20 +161,26 @@ void TransportManager::transportWarpHandler(CNSocket* sock, CNPacketData* data)
|
||||
// response parameters
|
||||
resp.eTT = route.type;
|
||||
resp.iCandy = plr->money;
|
||||
resp.iX = plr->x;
|
||||
resp.iY = plr->y;
|
||||
resp.iZ = plr->z;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_SUCC, sizeof(sP_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_SUCC));
|
||||
resp.iX = (target == nullptr) ? plr->x : target->x;
|
||||
resp.iY = (target == nullptr) ? plr->y : target->y;
|
||||
resp.iZ = (target == nullptr) ? plr->z : target->z;
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_SUCC);
|
||||
|
||||
if (target == nullptr)
|
||||
return;
|
||||
|
||||
// we warped; update position and chunks
|
||||
PlayerManager::updatePlayerPositionForWarp(sock, target->x, target->y, target->z, INSTANCE_OVERWORLD);
|
||||
}
|
||||
|
||||
void TransportManager::testMssRoute(CNSocket *sock, std::vector<WarpLocation>* route) {
|
||||
void Transport::testMssRoute(CNSocket *sock, std::vector<Vec3>* route) {
|
||||
int speed = 1500; // TODO: make this adjustable
|
||||
std::queue<WarpLocation> path;
|
||||
WarpLocation last = route->front(); // start pos
|
||||
std::queue<Vec3> path;
|
||||
Vec3 last = route->front(); // start pos
|
||||
|
||||
for (int i = 1; i < route->size(); i++) {
|
||||
WarpLocation coords = route->at(i);
|
||||
TransportManager::lerp(&path, last, coords, speed);
|
||||
Vec3 coords = route->at(i);
|
||||
Transport::lerp(&path, last, coords, speed);
|
||||
path.push(coords); // add keyframe to the queue
|
||||
last = coords; // update start pos
|
||||
}
|
||||
@@ -207,31 +188,26 @@ void TransportManager::testMssRoute(CNSocket *sock, std::vector<WarpLocation>* r
|
||||
SkywayQueues[sock] = path;
|
||||
}
|
||||
|
||||
void TransportManager::tickTransportationSystem(CNServer* serv, time_t currTime) {
|
||||
stepNPCPathing();
|
||||
stepSkywaySystem();
|
||||
}
|
||||
|
||||
/*
|
||||
* Go through every socket that has broomstick points queued up, and advance to the next point.
|
||||
* If the player has disconnected or finished the route, clean up and remove them from the queue.
|
||||
*/
|
||||
void TransportManager::stepSkywaySystem() {
|
||||
static void stepSkywaySystem() {
|
||||
|
||||
// using an unordered map so we can remove finished players in one iteration
|
||||
std::unordered_map<CNSocket*, std::queue<WarpLocation>>::iterator it = SkywayQueues.begin();
|
||||
std::unordered_map<CNSocket*, std::queue<Vec3>>::iterator it = SkywayQueues.begin();
|
||||
while (it != SkywayQueues.end()) {
|
||||
|
||||
std::queue<WarpLocation>* queue = &it->second;
|
||||
std::queue<Vec3>* queue = &it->second;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(it->first);
|
||||
|
||||
if (plr == nullptr) {
|
||||
if (PlayerManager::players.find(it->first) == PlayerManager::players.end()) {
|
||||
// pluck out dead socket + update iterator
|
||||
it = SkywayQueues.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(it->first);
|
||||
|
||||
if (queue->empty()) {
|
||||
// send dismount packet
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_RIDING_SUCC, rideSucc);
|
||||
@@ -240,12 +216,13 @@ void TransportManager::stepSkywaySystem() {
|
||||
rideSucc.eRT = 0;
|
||||
rideBroadcast.iPC_ID = plr->iID;
|
||||
rideBroadcast.eRT = 0;
|
||||
it->first->sendPacket((void*)&rideSucc, P_FE2CL_REP_PC_RIDING_SUCC, sizeof(sP_FE2CL_REP_PC_RIDING_SUCC));
|
||||
it->first->sendPacket(rideSucc, P_FE2CL_REP_PC_RIDING_SUCC);
|
||||
// send packet to players in view
|
||||
PlayerManager::sendToViewable(it->first, (void*)&rideBroadcast, P_FE2CL_PC_RIDING, sizeof(sP_FE2CL_PC_RIDING));
|
||||
PlayerManager::sendToViewable(it->first, rideBroadcast, P_FE2CL_PC_RIDING);
|
||||
it = SkywayQueues.erase(it); // remove player from tracking map + update iterator
|
||||
plr->onMonkey = false;
|
||||
} else {
|
||||
WarpLocation point = queue->front(); // get point
|
||||
Vec3 point = queue->front(); // get point
|
||||
queue->pop(); // remove point from front of queue
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_BROOMSTICK_MOVE, bmstk);
|
||||
@@ -253,24 +230,24 @@ void TransportManager::stepSkywaySystem() {
|
||||
bmstk.iToX = point.x;
|
||||
bmstk.iToY = point.y;
|
||||
bmstk.iToZ = point.z;
|
||||
it->first->sendPacket((void*)&bmstk, P_FE2CL_PC_BROOMSTICK_MOVE, sizeof(sP_FE2CL_PC_BROOMSTICK_MOVE));
|
||||
it->first->sendPacket(bmstk, P_FE2CL_PC_BROOMSTICK_MOVE);
|
||||
// set player location to point to update viewables
|
||||
PlayerManager::updatePlayerChunk(it->first, point.x, point.y, plr->instanceID);
|
||||
PlayerManager::updatePlayerPosition(it->first, point.x, point.y, point.z, plr->instanceID, plr->angle);
|
||||
// send packet to players in view
|
||||
PlayerManager::sendToViewable(it->first, (void*)&bmstk, P_FE2CL_PC_BROOMSTICK_MOVE, sizeof(sP_FE2CL_PC_BROOMSTICK_MOVE));
|
||||
PlayerManager::sendToViewable(it->first, bmstk, P_FE2CL_PC_BROOMSTICK_MOVE);
|
||||
|
||||
it++; // go to next entry in map
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TransportManager::stepNPCPathing() {
|
||||
static void stepNPCPathing() {
|
||||
|
||||
// all NPC pathing queues
|
||||
std::unordered_map<int32_t, std::queue<WarpLocation>>::iterator it = NPCQueues.begin();
|
||||
std::unordered_map<int32_t, std::queue<Vec3>>::iterator it = NPCQueues.begin();
|
||||
while (it != NPCQueues.end()) {
|
||||
|
||||
std::queue<WarpLocation>* queue = &it->second;
|
||||
std::queue<Vec3>* queue = &it->second;
|
||||
|
||||
BaseNPC* npc = nullptr;
|
||||
if (NPCManager::NPCs.find(it->first) != NPCManager::NPCs.end())
|
||||
@@ -283,32 +260,34 @@ void TransportManager::stepNPCPathing() {
|
||||
}
|
||||
|
||||
// skip if not simulating mobs
|
||||
if (npc->npcClass == NPC_MOB && !MobManager::simulateMobs) {
|
||||
if (npc->kind == EntityKind::MOB && !MobAI::simulateMobs) {
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// do not roam if not roaming
|
||||
if (npc->npcClass == NPC_MOB && ((Mob*)npc)->state != MobState::ROAMING) {
|
||||
if (npc->kind == EntityKind::MOB && ((Mob*)npc)->state != AIState::ROAMING) {
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
|
||||
WarpLocation point = queue->front(); // get point
|
||||
Vec3 point = queue->front(); // get point
|
||||
queue->pop(); // remove point from front of queue
|
||||
|
||||
// calculate displacement
|
||||
int dXY = hypot(point.x - npc->appearanceData.iX, point.y - npc->appearanceData.iY); // XY plane distance
|
||||
int distanceBetween = hypot(dXY, point.z - npc->appearanceData.iZ); // total distance
|
||||
int dXY = hypot(point.x - npc->x, point.y - npc->y); // XY plane distance
|
||||
int distanceBetween = hypot(dXY, point.z - npc->z); // total distance
|
||||
|
||||
// update NPC location to update viewables
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, point.x, point.y, point.z);
|
||||
NPCManager::updateNPCPosition(npc->id, point.x, point.y, point.z, npc->instanceID, npc->angle);
|
||||
|
||||
switch (npc->npcClass) {
|
||||
case NPC_BUS:
|
||||
// TODO: move walking logic into Entity stack
|
||||
switch (npc->kind) {
|
||||
case EntityKind::BUS:
|
||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove);
|
||||
|
||||
busMove.eTT = 3;
|
||||
busMove.iT_ID = npc->appearanceData.iNPC_ID;
|
||||
busMove.iT_ID = npc->id;
|
||||
busMove.iMoveStyle = 0; // ???
|
||||
busMove.iToX = point.x;
|
||||
busMove.iToY = point.y;
|
||||
@@ -317,12 +296,12 @@ void TransportManager::stepNPCPathing() {
|
||||
|
||||
NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
|
||||
break;
|
||||
case NPC_MOB:
|
||||
MobManager::incNextMovement((Mob*)npc);
|
||||
case EntityKind::MOB:
|
||||
MobAI::incNextMovement((Mob*)npc);
|
||||
/* fallthrough */
|
||||
default:
|
||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, move);
|
||||
move.iNPC_ID = npc->appearanceData.iNPC_ID;
|
||||
move.iNPC_ID = npc->id;
|
||||
move.iMoveStyle = 0; // ???
|
||||
move.iToX = point.x;
|
||||
move.iToY = point.y;
|
||||
@@ -334,25 +313,29 @@ void TransportManager::stepNPCPathing() {
|
||||
}
|
||||
|
||||
/*
|
||||
* Move processed point to the back to maintain cycle, unless this is a
|
||||
* dynamically calculated mob route.
|
||||
* If this path should be repeated, move processed point to the back to maintain cycle.
|
||||
*/
|
||||
if (!(npc->npcClass == NPC_MOB && !((Mob*)npc)->staticPath))
|
||||
if (npc->loopingPath)
|
||||
queue->push(point);
|
||||
|
||||
it++; // go to next entry in map
|
||||
}
|
||||
}
|
||||
|
||||
static void tickTransportationSystem(CNServer* serv, time_t currTime) {
|
||||
stepNPCPathing();
|
||||
stepSkywaySystem();
|
||||
}
|
||||
|
||||
/*
|
||||
* Linearly interpolate between two points and insert the results into a queue.
|
||||
*/
|
||||
void TransportManager::lerp(std::queue<WarpLocation>* queue, WarpLocation start, WarpLocation end, int gapSize, float curve) {
|
||||
void Transport::lerp(std::queue<Vec3>* queue, Vec3 start, Vec3 end, int gapSize, float curve) {
|
||||
int dXY = hypot(end.x - start.x, end.y - start.y); // XY plane distance
|
||||
int distanceBetween = hypot(dXY, end.z - start.z); // total distance
|
||||
int lerps = distanceBetween / gapSize; // number of intermediate points to add
|
||||
for (int i = 1; i <= lerps; i++) {
|
||||
WarpLocation lerp;
|
||||
Vec3 lerp;
|
||||
// lerp math
|
||||
//float frac = i / (lerps + 1);
|
||||
float frac = powf(i, curve) / powf(lerps + 1, curve);
|
||||
@@ -362,6 +345,84 @@ void TransportManager::lerp(std::queue<WarpLocation>* queue, WarpLocation start,
|
||||
queue->push(lerp); // add lerp'd point
|
||||
}
|
||||
}
|
||||
void TransportManager::lerp(std::queue<WarpLocation>* queue, WarpLocation start, WarpLocation end, int gapSize) {
|
||||
void Transport::lerp(std::queue<Vec3>* queue, Vec3 start, Vec3 end, int gapSize) {
|
||||
lerp(queue, start, end, gapSize, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find and return the first path that targets either the type or the ID.
|
||||
* If no matches are found, return nullptr
|
||||
*/
|
||||
NPCPath* Transport::findApplicablePath(int32_t id, int32_t type, int taskID) {
|
||||
NPCPath* match = nullptr;
|
||||
for (auto _path = Transport::NPCPaths.begin(); _path != Transport::NPCPaths.end(); _path++) {
|
||||
|
||||
// task ID for the path must match so escorts don't start early
|
||||
if (_path->escortTaskID != taskID)
|
||||
continue;
|
||||
|
||||
// search target IDs
|
||||
for (int32_t pID : _path->targetIDs) {
|
||||
if (id == pID) {
|
||||
match = &(*_path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match != nullptr)
|
||||
break; // early break for ID matches, since ID has higher priority than type
|
||||
|
||||
// search target types
|
||||
for (int32_t pType : _path->targetTypes) {
|
||||
if (type == pType) {
|
||||
match = &(*_path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match != nullptr)
|
||||
break;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
void Transport::constructPathNPC(int32_t id, NPCPath* path) {
|
||||
BaseNPC* npc = NPCManager::NPCs[id];
|
||||
if (npc->kind == EntityKind::MOB)
|
||||
((Mob*)(npc))->staticPath = true;
|
||||
npc->loopingPath = path->isLoop;
|
||||
|
||||
// Interpolate
|
||||
std::vector<Vec3> pathPoints = path->points;
|
||||
std::queue<Vec3> points;
|
||||
|
||||
auto _point = pathPoints.begin();
|
||||
Vec3 from = *_point; // point A coords
|
||||
for (_point++; _point != pathPoints.end(); _point++) { // loop through all point Bs
|
||||
Vec3 to = *_point; // point B coords
|
||||
// add point A to the queue
|
||||
if (path->isRelative) {
|
||||
// relative; the NPCs current position is assumed to be its spawn point
|
||||
Vec3 fromReal = { from.x + npc->x, from.y + npc->y, from.z + npc->z };
|
||||
Vec3 toReal = { to.x + npc->x, to.y + npc->y, to.z + npc->z };
|
||||
points.push(fromReal);
|
||||
Transport::lerp(&points, fromReal, toReal, path->speed); // lerp from A to B
|
||||
}
|
||||
else {
|
||||
// absolute
|
||||
points.push(from);
|
||||
Transport::lerp(&points, from, to, path->speed); // lerp from A to B
|
||||
}
|
||||
|
||||
from = to; // update point A
|
||||
}
|
||||
|
||||
Transport::NPCQueues[id] = points;
|
||||
}
|
||||
|
||||
void Transport::init() {
|
||||
REGISTER_SHARD_TIMER(tickTransportationSystem, 1000);
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION, transportRegisterLocationHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION, transportWarpHandler);
|
||||
}
|
||||
59
src/Transport.hpp
Normal file
59
src/Transport.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
|
||||
const int SLIDER_SPEED = 1200;
|
||||
const int SLIDER_STOP_TICKS = 16;
|
||||
const int SLIDER_GAP_SIZE = 45000;
|
||||
|
||||
const int NPC_DEFAULT_SPEED = 300;
|
||||
|
||||
struct Vec3 {
|
||||
int x, y, z;
|
||||
};
|
||||
|
||||
struct WarpLocation {
|
||||
int x, y, z, instanceID, isInstance, limitTaskID, npcID;
|
||||
};
|
||||
|
||||
struct TransportRoute {
|
||||
int type, start, end, cost, mssSpeed, mssRouteNum;
|
||||
};
|
||||
|
||||
struct TransportLocation {
|
||||
int npcID, x, y, z;
|
||||
};
|
||||
|
||||
struct NPCPath {
|
||||
std::vector<Vec3> points;
|
||||
std::vector<int32_t> targetIDs;
|
||||
std::vector<int32_t> targetTypes;
|
||||
int speed;
|
||||
int escortTaskID;
|
||||
bool isRelative;
|
||||
bool isLoop;
|
||||
};
|
||||
|
||||
namespace Transport {
|
||||
extern std::map<int32_t, TransportRoute> Routes;
|
||||
extern std::map<int32_t, TransportLocation> Locations;
|
||||
extern std::vector<NPCPath> NPCPaths; // predefined NPC paths
|
||||
extern std::map<int32_t, std::queue<Vec3>> SkywayPaths; // predefined skyway paths with points
|
||||
extern std::unordered_map<CNSocket*, std::queue<Vec3>> SkywayQueues; // player sockets with queued broomstick points
|
||||
extern std::unordered_map<int32_t, std::queue<Vec3>> NPCQueues; // NPC ids with queued pathing points
|
||||
|
||||
void init();
|
||||
|
||||
void testMssRoute(CNSocket *sock, std::vector<Vec3>* route);
|
||||
|
||||
void lerp(std::queue<Vec3>*, Vec3, Vec3, int, float);
|
||||
void lerp(std::queue<Vec3>*, Vec3, Vec3, int);
|
||||
|
||||
NPCPath* findApplicablePath(int32_t, int32_t, int = -1);
|
||||
void constructPathNPC(int32_t, NPCPath*);
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNShardServer.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
const int SLIDER_SPEED = 1200;
|
||||
const int SLIDER_STOP_TICKS = 16;
|
||||
|
||||
struct WarpLocation;
|
||||
|
||||
struct TransportRoute {
|
||||
int type, start, end, cost, mssSpeed, mssRouteNum;
|
||||
};
|
||||
|
||||
struct TransportLocation {
|
||||
int npcID, x, y, z;
|
||||
};
|
||||
|
||||
namespace TransportManager {
|
||||
extern std::map<int32_t, TransportRoute> Routes;
|
||||
extern std::map<int32_t, TransportLocation> Locations;
|
||||
extern std::map<int32_t, std::queue<WarpLocation>> SkywayPaths; // predefined skyway paths with points
|
||||
extern std::unordered_map<CNSocket*, std::queue<WarpLocation>> SkywayQueues; // player sockets with queued broomstick points
|
||||
extern std::unordered_map<int32_t, std::queue<WarpLocation>> NPCQueues; // NPC ids with queued pathing points
|
||||
|
||||
void init();
|
||||
|
||||
void transportRegisterLocationHandler(CNSocket*, CNPacketData*);
|
||||
void transportWarpHandler(CNSocket*, CNPacketData*);
|
||||
|
||||
void testMssRoute(CNSocket *sock, std::vector<WarpLocation>* route);
|
||||
|
||||
void tickTransportationSystem(CNServer*, time_t);
|
||||
void stepNPCPathing();
|
||||
void stepSkywaySystem();
|
||||
|
||||
void lerp(std::queue<WarpLocation>*, WarpLocation, WarpLocation, int, float);
|
||||
void lerp(std::queue<WarpLocation>*, WarpLocation, WarpLocation, int);
|
||||
}
|
||||
405
src/Vendors.cpp
Normal file
405
src/Vendors.cpp
Normal file
@@ -0,0 +1,405 @@
|
||||
#include "Vendors.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Rand.hpp"
|
||||
|
||||
// 7 days
|
||||
#define VEHICLE_EXPIRY_DURATION 604800
|
||||
|
||||
using namespace Vendors;
|
||||
|
||||
std::map<int32_t, std::vector<VendorListing>> Vendors::VendorTables;
|
||||
|
||||
static void vendorBuy(CNSocket* sock, CNPacketData* data) {
|
||||
auto req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// prepare fail packet
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
|
||||
if (req->iVendorID != req->iNPC_ID || Vendors::VendorTables.find(req->iVendorID) == Vendors::VendorTables.end()) {
|
||||
std::cout << "[WARN] Vendor with ID " << req->iVendorID << " mismatched or not found (buy)" << std::endl;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<VendorListing>* listings = &Vendors::VendorTables[req->iVendorID];
|
||||
VendorListing reqItem;
|
||||
reqItem.id = req->Item.iID;
|
||||
reqItem.type = req->Item.iType;
|
||||
reqItem.sort = 0; // just to be safe
|
||||
|
||||
if (std::find(listings->begin(), listings->end(), reqItem) == listings->end()) { // item not found in listing
|
||||
std::cout << "[WARN] Player " << PlayerManager::getPlayerName(plr) << " tried to buy an item that wasn't on sale" << std::endl;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
Items::Item* itemDat = Items::getItemData(req->Item.iID, req->Item.iType);
|
||||
if (itemDat == nullptr) {
|
||||
std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (buy)" << std::endl;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
int itemCost = itemDat->buyPrice * (itemDat->stackSize > 1 ? req->Item.iOpt : 1);
|
||||
int slot = Items::findFreeSlot(plr);
|
||||
if (itemCost > plr->money || slot == -1) {
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
// crates don't have a stack size in TableData, so we can't check those
|
||||
if (itemDat->stackSize != 0 && req->Item.iOpt > itemDat->stackSize) {
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
// if vehicle
|
||||
if (req->Item.iType == 10) {
|
||||
// set time limit: current time + expiry duration
|
||||
req->Item.iTimeLimit = getTimestamp() + VEHICLE_EXPIRY_DURATION;
|
||||
}
|
||||
|
||||
if (slot != req->iInvenSlotNum) {
|
||||
// possible item stacking?
|
||||
std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, resp);
|
||||
|
||||
plr->money = plr->money - itemCost;
|
||||
plr->Inven[slot] = req->Item;
|
||||
|
||||
resp.iCandy = plr->money;
|
||||
resp.iInvenSlotNum = slot;
|
||||
resp.Item = req->Item;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC);
|
||||
}
|
||||
|
||||
static void vendorSell(CNSocket* sock, CNPacketData* data) {
|
||||
auto req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// prepare a fail packet
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
|
||||
if (req->iInvenSlotNum < 0 || req->iInvenSlotNum >= AINVEN_COUNT || req->iItemCnt < 0) {
|
||||
std::cout << "[WARN] Client failed to sell item in slot " << req->iInvenSlotNum << std::endl;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
sItemBase* item = &plr->Inven[req->iInvenSlotNum];
|
||||
Items::Item* itemData = Items::getItemData(item->iID, item->iType);
|
||||
|
||||
if (itemData == nullptr || !itemData->sellable || item->iOpt < req->iItemCnt) { // sanity + sellable check
|
||||
std::cout << "[WARN] Item id " << item->iID << " with type " << item->iType << " not found (sell)" << std::endl;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
// fail to sell croc-potted items
|
||||
if (item->iOpt >= 1 << 16) {
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
sItemBase original;
|
||||
memcpy(&original, item, sizeof(sItemBase));
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, resp);
|
||||
|
||||
// increment taros
|
||||
plr->money += itemData->sellPrice * req->iItemCnt;
|
||||
|
||||
// modify item
|
||||
if (item->iOpt - req->iItemCnt > 0) { // selling part of a stack
|
||||
item->iOpt -= req->iItemCnt;
|
||||
original.iOpt = req->iItemCnt;
|
||||
}
|
||||
else { // selling entire slot
|
||||
// make sure it's fully zeroed, even the padding and non-104 members
|
||||
memset(item, 0, sizeof(*item));
|
||||
}
|
||||
|
||||
// add to buyback list
|
||||
plr->buyback.push_back(original);
|
||||
// forget oldest member if there's more than 5
|
||||
if (plr->buyback.size() > 5)
|
||||
plr->buyback.erase(plr->buyback.begin());
|
||||
//std::cout << (int)plr->buyback.size() << " items in buyback\n";
|
||||
|
||||
// response parameters
|
||||
resp.iInvenSlotNum = req->iInvenSlotNum;
|
||||
resp.iCandy = plr->money;
|
||||
resp.Item = original; // the item that gets sent to buyback
|
||||
resp.ItemStay = *item; // the void item that gets put in the slot
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC);
|
||||
}
|
||||
|
||||
static void vendorBuyback(CNSocket* sock, CNPacketData* data) {
|
||||
auto req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// prepare fail packet
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
|
||||
//std::cout << "buying back from index " << (int)req->iListID << " into " << (int)req->iInvenSlotNum <<
|
||||
// " from " << (int)req->iNPC_ID << " (vendor = " << (int)req->iVendorID << ")\n";
|
||||
|
||||
int idx = req->iListID - 1;
|
||||
|
||||
// sanity check
|
||||
if (idx < 0 || idx >= plr->buyback.size()) {
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
// get the item out of the buyback list
|
||||
sItemBase item = plr->buyback[idx];
|
||||
/*
|
||||
* NOTE: The client sends the index of the exact item the user clicked on.
|
||||
* We then operate on that item, but we remove the *first* identical item
|
||||
* from the buyback list, instead of the one at the supplied index.
|
||||
*
|
||||
* This was originally a mistake on my part, but it turns out the client
|
||||
* does the exact same thing, so this *is* the correct thing to do to keep
|
||||
* them in sync.
|
||||
*/
|
||||
for (auto it = plr->buyback.begin(); it != plr->buyback.end(); it++) {
|
||||
/*
|
||||
* XXX: we really need a standard item comparison function that
|
||||
* will work properly across all builds (ex. with iSerial)
|
||||
*/
|
||||
if (it->iType == item.iType && it->iID == item.iID && it->iOpt == item.iOpt
|
||||
&& it->iTimeLimit == item.iTimeLimit) {
|
||||
plr->buyback.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
//std::cout << (int)plr->buyback.size() << " items in buyback\n";
|
||||
|
||||
Items::Item* itemDat = Items::getItemData(item.iID, item.iType);
|
||||
|
||||
if (itemDat == nullptr) {
|
||||
std::cout << "[WARN] Item id " << item.iID << " with type " << item.iType << " not found (rebuy)" << std::endl;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
// sell price is used on rebuy. ternary identifies stacked items
|
||||
int itemCost = itemDat->sellPrice * (itemDat->stackSize > 1 ? item.iOpt : 1);
|
||||
int slot = Items::findFreeSlot(plr);
|
||||
if (itemCost > plr->money || slot == -1) {
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (slot != req->iInvenSlotNum) {
|
||||
// possible item stacking?
|
||||
std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl;
|
||||
}
|
||||
|
||||
plr->money = plr->money - itemCost;
|
||||
plr->Inven[slot] = item;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, resp);
|
||||
// response parameters
|
||||
resp.iCandy = plr->money;
|
||||
resp.iInvenSlotNum = slot;
|
||||
resp.Item = item;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC);
|
||||
}
|
||||
|
||||
static void vendorTable(CNSocket* sock, CNPacketData* data) {
|
||||
auto req = (sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE*)data->buf;
|
||||
|
||||
if (req->iVendorID != req->iNPC_ID || Vendors::VendorTables.find(req->iVendorID) == Vendors::VendorTables.end())
|
||||
return;
|
||||
|
||||
std::vector<VendorListing>& listings = Vendors::VendorTables[req->iVendorID];
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp);
|
||||
|
||||
for (int i = 0; i < (int)listings.size() && i < 20; i++) { // 20 is the max
|
||||
sItemBase base = {};
|
||||
base.iID = listings[i].id;
|
||||
base.iType = listings[i].type;
|
||||
|
||||
/*
|
||||
* Set vehicle expiry value.
|
||||
*
|
||||
* Note: sItemBase.iTimeLimit in the context of vendor listings contains
|
||||
* a duration, unlike in most other contexts where it contains the
|
||||
* expiration timestamp.
|
||||
*/
|
||||
if (listings[i].type == 10)
|
||||
base.iTimeLimit = VEHICLE_EXPIRY_DURATION;
|
||||
|
||||
sItemVendor vItem;
|
||||
vItem.item = base;
|
||||
vItem.iSortNum = listings[i].sort;
|
||||
vItem.iVendorID = req->iVendorID;
|
||||
//vItem.fBuyCost = listings[i].price; // this value is not actually the one that is used
|
||||
|
||||
resp.item[i] = vItem;
|
||||
}
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC);
|
||||
}
|
||||
|
||||
static void vendorStart(CNSocket* sock, CNPacketData* data) {
|
||||
auto req = (sP_CL2FE_REQ_PC_VENDOR_START*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_START_SUCC, resp);
|
||||
|
||||
resp.iNPC_ID = req->iNPC_ID;
|
||||
resp.iVendorID = req->iVendorID;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_VENDOR_START_SUCC);
|
||||
}
|
||||
|
||||
static void vendorBuyBattery(CNSocket* sock, CNPacketData* data) {
|
||||
auto req = (sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
int cost = req->Item.iOpt * 100;
|
||||
if ((req->Item.iID == 3 ? (plr->batteryW >= 9999) : (plr->batteryN >= 9999)) || plr->money < cost || req->Item.iOpt < 0) { // sanity check
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
cost = plr->batteryW + plr->batteryN;
|
||||
plr->batteryW += req->Item.iID == 3 ? req->Item.iOpt * 100 : 0;
|
||||
plr->batteryN += req->Item.iID == 4 ? req->Item.iOpt * 100 : 0;
|
||||
|
||||
// caps
|
||||
if (plr->batteryW > 9999)
|
||||
plr->batteryW = 9999;
|
||||
if (plr->batteryN > 9999)
|
||||
plr->batteryN = 9999;
|
||||
|
||||
cost = plr->batteryW + plr->batteryN - cost;
|
||||
plr->money -= cost;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, resp);
|
||||
|
||||
resp.iCandy = plr->money;
|
||||
resp.iBatteryW = plr->batteryW;
|
||||
resp.iBatteryN = plr->batteryN;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC);
|
||||
}
|
||||
|
||||
static void vendorCombineItems(CNSocket* sock, CNPacketData* data) {
|
||||
auto req = (sP_CL2FE_REQ_PC_ITEM_COMBINATION*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// prepare fail packet
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp);
|
||||
failResp.iCostumeItemSlot = req->iCostumeItemSlot;
|
||||
failResp.iStatItemSlot = req->iStatItemSlot;
|
||||
failResp.iErrorCode = 0;
|
||||
|
||||
// sanity check slot indices
|
||||
if (req->iCostumeItemSlot < 0 || req->iCostumeItemSlot >= AINVEN_COUNT || req->iStatItemSlot < 0 || req->iStatItemSlot >= AINVEN_COUNT) {
|
||||
std::cout << "[WARN] Inventory slot(s) out of range (" << req->iStatItemSlot << " and " << req->iCostumeItemSlot << ")" << std::endl;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
sItemBase* itemStats = &plr->Inven[req->iStatItemSlot];
|
||||
sItemBase* itemLooks = &plr->Inven[req->iCostumeItemSlot];
|
||||
Items::Item* itemStatsDat = Items::getItemData(itemStats->iID, itemStats->iType);
|
||||
Items::Item* itemLooksDat = Items::getItemData(itemLooks->iID, itemLooks->iType);
|
||||
|
||||
// sanity check item and combination entry existence
|
||||
if (itemStatsDat == nullptr || itemLooksDat == nullptr
|
||||
|| Items::CrocPotTable.find(abs(itemStatsDat->level - itemLooksDat->level)) == Items::CrocPotTable.end()) {
|
||||
std::cout << "[WARN] Either item ids or croc pot value set not found" << std::endl;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
// sanity check matching item types
|
||||
if (itemStats->iType != itemLooks->iType
|
||||
|| (itemStats->iType == 0 && itemStatsDat->weaponType != itemLooksDat->weaponType)) {
|
||||
std::cout << "[WARN] Player attempted to combine mismatched items" << std::endl;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
CrocPotEntry* recipe = &Items::CrocPotTable[abs(itemStatsDat->level - itemLooksDat->level)];
|
||||
int cost = itemStatsDat->buyPrice * recipe->multStats + itemLooksDat->buyPrice * recipe->multLooks;
|
||||
float successChance = recipe->base / 100.0f; // base success chance
|
||||
|
||||
// rarity gap multiplier
|
||||
switch (abs(itemStatsDat->rarity - itemLooksDat->rarity)) {
|
||||
case 0:
|
||||
successChance *= recipe->rd0;
|
||||
break;
|
||||
case 1:
|
||||
successChance *= recipe->rd1;
|
||||
break;
|
||||
case 2:
|
||||
successChance *= recipe->rd2;
|
||||
break;
|
||||
case 3:
|
||||
successChance *= recipe->rd3;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
float rolled = Rand::randFloat(100.0f); // success chance out of 100
|
||||
//std::cout << rolled << " vs " << successChance << std::endl;
|
||||
plr->money -= cost;
|
||||
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, resp);
|
||||
if (rolled < successChance) {
|
||||
// success
|
||||
resp.iSuccessFlag = 1;
|
||||
|
||||
// modify the looks item with the new stats and set the appearance through iOpt
|
||||
itemLooks->iOpt = (int32_t)((itemLooks->iOpt) >> 16 > 0 ? (itemLooks->iOpt >> 16) : itemLooks->iID) << 16;
|
||||
itemLooks->iID = itemStats->iID;
|
||||
|
||||
// delete stats item
|
||||
itemStats->iID = 0;
|
||||
itemStats->iOpt = 0;
|
||||
itemStats->iTimeLimit = 0;
|
||||
itemStats->iType = 0;
|
||||
}
|
||||
else {
|
||||
// failure; don't do anything?
|
||||
resp.iSuccessFlag = 0;
|
||||
}
|
||||
resp.iCandy = plr->money;
|
||||
resp.iNewItemSlot = req->iCostumeItemSlot;
|
||||
resp.iStatItemSlot = req->iStatItemSlot;
|
||||
resp.sNewItem = *itemLooks;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_ITEM_COMBINATION_SUCC);
|
||||
}
|
||||
|
||||
void Vendors::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_START, vendorStart);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE, vendorTable);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_BUY, vendorBuy);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_SELL, vendorSell);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY, vendorBuyback);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_BATTERY_BUY, vendorBuyBattery);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_COMBINATION, vendorCombineItems);
|
||||
}
|
||||
21
src/Vendors.hpp
Normal file
21
src/Vendors.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
struct VendorListing {
|
||||
int sort, type, id;
|
||||
|
||||
// when validating a listing, we don't really care about the sorting index
|
||||
bool operator==(const VendorListing& other) const {
|
||||
return type == other.type && id == other.id;
|
||||
}
|
||||
};
|
||||
|
||||
namespace Vendors {
|
||||
extern std::map<int32_t, std::vector<VendorListing>> VendorTables;
|
||||
|
||||
void init();
|
||||
}
|
||||
230517
src/contrib/sqlite/sqlite3.c
230517
src/contrib/sqlite/sqlite3.c
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
567
src/core/CNProtocol.cpp
Normal file
567
src/core/CNProtocol.cpp
Normal file
@@ -0,0 +1,567 @@
|
||||
#include "core/CNProtocol.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
// ========================================================[[ CNSocketEncryption ]]========================================================
|
||||
|
||||
// literally C/P from the client and converted to C++ (does some byte swapping /shrug)
|
||||
int CNSocketEncryption::Encrypt_byte_change_A(int ERSize, uint8_t* data, int size) {
|
||||
int num = 0;
|
||||
int num2 = 0;
|
||||
int num3 = 0;
|
||||
|
||||
while (num + ERSize <= size) {
|
||||
int num4 = num + num3;
|
||||
int num5 = num + (ERSize - 1 - num3);
|
||||
|
||||
uint8_t b = data[num4];
|
||||
data[num4] = data[num5];
|
||||
data[num5] = b;
|
||||
num += ERSize;
|
||||
num3++;
|
||||
if (num3 > ERSize / 2) {
|
||||
num3 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
num2 = ERSize - (num + ERSize - size);
|
||||
return num + num2;
|
||||
}
|
||||
|
||||
int CNSocketEncryption::xorData(uint8_t* buffer, uint8_t* key, int size) {
|
||||
// xor every 8 bytes with 8 byte key
|
||||
for (int i = 0; i < size; i++) {
|
||||
buffer[i] ^= key[i % keyLength];
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) {
|
||||
uint64_t num = (uint64_t)(iv1 + 1);
|
||||
uint64_t num2 = (uint64_t)(iv2 + 1);
|
||||
uint64_t dEKey;
|
||||
memcpy(&dEKey, defaultKey, sizeof(dEKey));
|
||||
return dEKey * (uTime * num * num2);
|
||||
}
|
||||
|
||||
int CNSocketEncryption::encryptData(uint8_t* buffer, uint8_t* key, int size) {
|
||||
int eRSize = size % (keyLength / 2 + 1) * 2 + keyLength; // C/P from client
|
||||
int size2 = xorData(buffer, key, size);
|
||||
return Encrypt_byte_change_A(eRSize, buffer, size2);
|
||||
}
|
||||
|
||||
int CNSocketEncryption::decryptData(uint8_t* buffer, uint8_t* key, int size) {
|
||||
int eRSize = size % (keyLength / 2 + 1) * 2 + keyLength; // size % of 18????
|
||||
int size2 = Encrypt_byte_change_A(eRSize, buffer, size);
|
||||
return xorData(buffer, key, size2);
|
||||
}
|
||||
|
||||
// ========================================================[[ CNPacketData ]]========================================================
|
||||
|
||||
CNPacketData::CNPacketData(void *b, uint32_t t, int l, int trnum, void *trs):
|
||||
buf(b), size(l), type(t), trCnt(trnum), trailers(trs) {}
|
||||
|
||||
// ========================================================[[ CNSocket ]]========================================================
|
||||
|
||||
CNSocket::CNSocket(SOCKET s, struct sockaddr_in &addr, PacketHandler ph): sock(s), sockaddr(addr), pHandler(ph) {
|
||||
memcpy(&EKey, CNSocketEncryption::defaultKey, sizeof(EKey));
|
||||
}
|
||||
|
||||
bool CNSocket::sendData(uint8_t* data, int size) {
|
||||
int sentBytes = 0;
|
||||
int maxTries = 10;
|
||||
|
||||
while (sentBytes < size) {
|
||||
int sent = send(sock, (buffer_t*)(data + sentBytes), size - sentBytes, 0);
|
||||
if (SOCKETERROR(sent)) {
|
||||
if (OF_ERRNO == OF_EWOULD && maxTries > 0) {
|
||||
maxTries--;
|
||||
continue; // try again
|
||||
}
|
||||
printSocketError("send");
|
||||
return false; // error occured while sending bytes
|
||||
}
|
||||
sentBytes += sent;
|
||||
}
|
||||
|
||||
return true; // it worked!
|
||||
}
|
||||
|
||||
void CNSocket::setEKey(uint64_t k) {
|
||||
EKey = k;
|
||||
}
|
||||
|
||||
void CNSocket::setFEKey(uint64_t k) {
|
||||
FEKey = k;
|
||||
}
|
||||
|
||||
uint64_t CNSocket::getEKey() {
|
||||
return EKey;
|
||||
}
|
||||
|
||||
uint64_t CNSocket::getFEKey() {
|
||||
return FEKey;
|
||||
}
|
||||
|
||||
bool CNSocket::isAlive() {
|
||||
return alive;
|
||||
}
|
||||
|
||||
void CNSocket::kill() {
|
||||
if (!alive)
|
||||
return;
|
||||
|
||||
alive = false;
|
||||
|
||||
#ifdef _WIN32
|
||||
shutdown(sock, SD_BOTH);
|
||||
closesocket(sock);
|
||||
#else
|
||||
shutdown(sock, SHUT_RDWR);
|
||||
close(sock);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CNSocket::validatingSendPacket(void *pkt, uint32_t packetType) {
|
||||
assert(isOutboundPacketID(packetType));
|
||||
assert(Packets::packets.find(packetType) != Packets::packets.end());
|
||||
|
||||
PacketDesc& desc = Packets::packets[packetType];
|
||||
size_t resplen = desc.size;
|
||||
|
||||
/*
|
||||
* Note that this validation doesn't happen on time to prevent a buffer
|
||||
* overflow if it would have taken place, but we do it anyway so the
|
||||
* assertion failure at least makes it clear that something isn't being
|
||||
* validated properly.
|
||||
*/
|
||||
if (desc.variadic) {
|
||||
int32_t ntrailers = *(int32_t*)(((uint8_t*)pkt) + desc.cntMembOfs);
|
||||
assert(validOutVarPacket(desc.size, ntrailers, desc.trailerSize));
|
||||
resplen = desc.size + ntrailers * desc.trailerSize;
|
||||
}
|
||||
|
||||
sendPacket(pkt, packetType, resplen);
|
||||
}
|
||||
|
||||
void CNSocket::sendPacket(void* buf, uint32_t type, size_t size) {
|
||||
if (!alive)
|
||||
return;
|
||||
|
||||
uint8_t fullpkt[CN_PACKET_BUFFER_SIZE]; // length, type, body
|
||||
uint8_t* body = fullpkt + 4; // packet without length (type, body)
|
||||
size_t bodysize = size + 4;
|
||||
|
||||
// set packet length
|
||||
memcpy(fullpkt, (void*)&bodysize, 4);
|
||||
|
||||
// copy packet type to the front of the buffer & then the actual buffer
|
||||
memcpy(body, (void*)&type, 4);
|
||||
memcpy(body+4, buf, size);
|
||||
|
||||
// encrypt the packet
|
||||
switch (activeKey) {
|
||||
case SOCKETKEY_E:
|
||||
CNSocketEncryption::encryptData((uint8_t*)body, (uint8_t*)(&EKey), bodysize);
|
||||
break;
|
||||
case SOCKETKEY_FE:
|
||||
CNSocketEncryption::encryptData((uint8_t*)body, (uint8_t*)(&FEKey), bodysize);
|
||||
break;
|
||||
default:
|
||||
DEBUGLOG(
|
||||
std::cout << "[WARN]: UNSET KEYTYPE FOR SOCKET!! ABORTING SEND" << std::endl;
|
||||
)
|
||||
return;
|
||||
}
|
||||
|
||||
// send packet data!
|
||||
if (alive && !sendData(fullpkt, bodysize+4))
|
||||
kill();
|
||||
}
|
||||
|
||||
void CNSocket::setActiveKey(ACTIVEKEY key) {
|
||||
activeKey = key;
|
||||
}
|
||||
|
||||
inline void CNSocket::parsePacket(uint8_t *buf, size_t size) {
|
||||
uint32_t type = *((uint32_t*)buf);
|
||||
uint8_t *body = buf + 4;
|
||||
size_t pktSize = size - 4;
|
||||
|
||||
if (Packets::packets.find(type) == Packets::packets.end()) {
|
||||
std::cerr << "OpenFusion: UNKNOWN PACKET: " << (int)type << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isInboundPacketID(type)) {
|
||||
std::cerr << "OpenFusion: UNEXPECTED PACKET: " << (int)type << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
PacketDesc& desc = Packets::packets[type];
|
||||
|
||||
/*
|
||||
* Some packet structs with no meaningful contents have length 1, but
|
||||
* the client doesn't transmit that byte at all, so we special-case that.
|
||||
* It's important that we do that by zeroing that byte, as the server could
|
||||
* hypothetically try and read from it and get a byte of the previous
|
||||
* packet's contents.
|
||||
*
|
||||
* Assigning a zero byte to the body like this is safe, since there's a
|
||||
* huge empty buffer behind that pointer.
|
||||
*/
|
||||
if (!desc.variadic && desc.size == 1 && pktSize == 0) {
|
||||
pktSize = 1;
|
||||
*body = 0;
|
||||
}
|
||||
|
||||
int32_t ntrailers = 0;
|
||||
if (desc.variadic) {
|
||||
ntrailers = *(int32_t*)(body + desc.cntMembOfs);
|
||||
if (!validInVarPacket(desc.size, ntrailers, desc.trailerSize, pktSize)) {
|
||||
std::cerr << "[WARN] Received invalid variadic packet: " << desc.name << " (" << type << ")" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (!desc.variadic && pktSize != desc.size) {
|
||||
std::cerr << "[WARN] Received " << desc.name << " (" << type << ") of wrong size ("
|
||||
<< (int)pktSize << " vs " << desc.size << ")" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
void *trailers = nullptr;
|
||||
if (desc.variadic)
|
||||
trailers = body + desc.size;
|
||||
|
||||
CNPacketData pkt(body, type, pktSize, ntrailers, trailers);
|
||||
pHandler(this, &pkt);
|
||||
}
|
||||
|
||||
void CNSocket::step() {
|
||||
// read step
|
||||
|
||||
// XXX NOTE: we must not recv() twice without a poll() inbetween
|
||||
if (readSize <= 0) {
|
||||
// we aren't reading a packet yet, try to start looking for one
|
||||
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
|
||||
if (recved >= 0 && recved < sizeof(int32_t)) {
|
||||
// too little data for readSize or the socket was closed normally (when 0 bytes were read)
|
||||
kill();
|
||||
return;
|
||||
} else if (!SOCKETERROR(recved)) {
|
||||
// we got our packet size!!!!
|
||||
readSize = *((int32_t*)readBuffer);
|
||||
// sanity check
|
||||
if (readSize > CN_PACKET_BUFFER_SIZE) {
|
||||
kill();
|
||||
return;
|
||||
}
|
||||
|
||||
// we'll just leave bufferIndex at 0 since we already have the packet size, it's safe to overwrite those bytes
|
||||
activelyReading = true;
|
||||
} else if (OF_ERRNO != OF_EWOULD) {
|
||||
// serious socket issue, disconnect connection
|
||||
printSocketError("recv");
|
||||
kill();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (readSize > 0 && readBufferIndex < readSize) {
|
||||
// read until the end of the packet (or at least try to)
|
||||
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
|
||||
if (recved == 0) {
|
||||
// the socket was closed normally
|
||||
kill();
|
||||
return;
|
||||
} else if (!SOCKETERROR(recved))
|
||||
readBufferIndex += recved;
|
||||
else if (OF_ERRNO != OF_EWOULD) {
|
||||
// serious socket issue, disconnect connection
|
||||
printSocketError("recv");
|
||||
kill();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (activelyReading && readBufferIndex >= readSize) {
|
||||
// decrypt readBuffer and copy to CNPacketData
|
||||
CNSocketEncryption::decryptData((uint8_t*)&readBuffer, (uint8_t*)(&EKey), readSize);
|
||||
|
||||
parsePacket(readBuffer, readSize);
|
||||
|
||||
// reset vars :)
|
||||
readSize = 0;
|
||||
readBufferIndex = 0;
|
||||
activelyReading = false;
|
||||
}
|
||||
}
|
||||
|
||||
void printSocketError(const char *call) {
|
||||
#ifdef _WIN32
|
||||
std::cerr << call << ": ";
|
||||
|
||||
LPSTR lpMsgBuf = nullptr; // string buffer
|
||||
DWORD errCode = WSAGetLastError(); // error code
|
||||
|
||||
if (errCode == 0) {
|
||||
std::cerr << "no error code" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t bufSize = FormatMessageA( // actually get the error message
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL,
|
||||
errCode, // in
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPTSTR)&lpMsgBuf, // out
|
||||
0, NULL);
|
||||
|
||||
// convert buffer to string and output message to terminal
|
||||
std::string msg(lpMsgBuf, bufSize);
|
||||
std::cerr << msg; // newline included
|
||||
|
||||
LocalFree(lpMsgBuf); // free the buffer
|
||||
#else
|
||||
perror(call);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool setSockNonblocking(SOCKET listener, SOCKET newSock) {
|
||||
#ifdef _WIN32
|
||||
unsigned long mode = 1;
|
||||
if (ioctlsocket(newSock, FIONBIO, &mode) != 0) {
|
||||
#else
|
||||
if (fcntl(newSock, F_SETFL, (fcntl(newSock, F_GETFL, 0) | O_NONBLOCK)) != 0) {
|
||||
#endif
|
||||
printSocketError("fcntl");
|
||||
std::cerr << "[WARN] OpenFusion: fcntl failed on new connection" << std::endl;
|
||||
#ifdef _WIN32
|
||||
shutdown(newSock, SD_BOTH);
|
||||
closesocket(newSock);
|
||||
#else
|
||||
shutdown(newSock, SHUT_RDWR);
|
||||
close(newSock);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================================================[[ CNServer ]]========================================================
|
||||
|
||||
void CNServer::init() {
|
||||
// create socket file descriptor
|
||||
sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (SOCKETINVALID(sock)) {
|
||||
printSocketError("socket");
|
||||
std::cerr << "[FATAL] OpenFusion: socket failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// attach socket to the port
|
||||
int opt = 1;
|
||||
#ifdef _WIN32
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)) != 0) {
|
||||
#else
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
|
||||
#endif
|
||||
std::cerr << "[FATAL] OpenFusion: setsockopt failed" << std::endl;
|
||||
printSocketError("setsockopt");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = INADDR_ANY;
|
||||
address.sin_port = htons(port);
|
||||
|
||||
addressSize = sizeof(address);
|
||||
|
||||
// Bind to the port
|
||||
if (SOCKETERROR(bind(sock, (struct sockaddr *)&address, addressSize))) {
|
||||
std::cerr << "[FATAL] OpenFusion: bind failed" << std::endl;
|
||||
printSocketError("bind");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (SOCKETERROR(listen(sock, SOMAXCONN))) {
|
||||
std::cerr << "[FATAL] OpenFusion: listen failed" << std::endl;
|
||||
printSocketError("listen");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// set server listener to non-blocking
|
||||
#ifdef _WIN32
|
||||
unsigned long mode = 1;
|
||||
if (ioctlsocket(sock, FIONBIO, &mode) != 0) {
|
||||
#else
|
||||
if (fcntl(sock, F_SETFL, (fcntl(sock, F_GETFL, 0) | O_NONBLOCK)) != 0) {
|
||||
#endif
|
||||
printSocketError("fcntl");
|
||||
std::cerr << "[FATAL] OpenFusion: fcntl failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// poll() configuration
|
||||
fds.reserve(STARTFDSCOUNT);
|
||||
fds.push_back({sock, POLLIN});
|
||||
}
|
||||
|
||||
CNServer::CNServer() {};
|
||||
CNServer::CNServer(uint16_t p): port(p) {}
|
||||
|
||||
void CNServer::addPollFD(SOCKET s) {
|
||||
fds.push_back({s, POLLIN});
|
||||
}
|
||||
|
||||
void CNServer::removePollFD(int fd) {
|
||||
auto it = fds.begin();
|
||||
while (it != fds.end() && it->fd != fd)
|
||||
it++;
|
||||
assert(it != fds.end());
|
||||
|
||||
fds.erase(it);
|
||||
}
|
||||
|
||||
void CNServer::start() {
|
||||
std::cout << "Starting server at *:" << port << std::endl;
|
||||
while (active) {
|
||||
// the timeout is to ensure shard timers are ticking
|
||||
int n = poll(fds.data(), fds.size(), 50);
|
||||
if (SOCKETERROR(n)) {
|
||||
#ifndef _WIN32
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
#endif
|
||||
std::cout << "[FATAL] poll() returned error" << std::endl;
|
||||
printSocketError("poll");
|
||||
terminate(0);
|
||||
}
|
||||
|
||||
for (int i = 0; i < fds.size() && n > 0; i++) {
|
||||
if (fds[i].revents == 0)
|
||||
continue; // nothing in this one; don't decrement n
|
||||
|
||||
n--;
|
||||
|
||||
// is it the listener?
|
||||
if (fds[i].fd == sock) {
|
||||
// any sort of error on the listener
|
||||
if (fds[i].revents & ~POLLIN) {
|
||||
std::cout << "[FATAL] Error on listener socket" << std::endl;
|
||||
terminate(0);
|
||||
}
|
||||
|
||||
SOCKET newConnectionSocket = accept(sock, (struct sockaddr *)&address, (socklen_t*)&addressSize);
|
||||
if (SOCKETINVALID(newConnectionSocket)) {
|
||||
printSocketError("accept");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!setSockNonblocking(sock, newConnectionSocket))
|
||||
continue;
|
||||
|
||||
std::cout << "New " << serverType << " connection! " << inet_ntoa(address.sin_addr) << std::endl;
|
||||
|
||||
addPollFD(newConnectionSocket);
|
||||
|
||||
// add connection to list!
|
||||
CNSocket* tmp = new CNSocket(newConnectionSocket, address, pHandler);
|
||||
connections[newConnectionSocket] = tmp;
|
||||
newConnection(tmp);
|
||||
|
||||
} else if (checkExtraSockets(i)) {
|
||||
// no-op. handled in checkExtraSockets().
|
||||
|
||||
} else {
|
||||
std::lock_guard<std::mutex> lock(activeCrit); // protect operations on connections
|
||||
|
||||
// halt packet handling if server is shutting down
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
// player sockets
|
||||
if (connections.find(fds[i].fd) == connections.end()) {
|
||||
std::cout << "[FATAL] Event on non-existent socket: " << fds[i].fd << std::endl;
|
||||
assert(0);
|
||||
/* not reached */
|
||||
}
|
||||
|
||||
CNSocket* cSock = connections[fds[i].fd];
|
||||
|
||||
// kill the socket on hangup/error
|
||||
if (fds[i].revents & ~POLLIN)
|
||||
cSock->kill();
|
||||
|
||||
if (cSock->isAlive())
|
||||
cSock->step();
|
||||
}
|
||||
}
|
||||
|
||||
onStep();
|
||||
|
||||
// clean up dead connection sockets
|
||||
auto it = connections.begin();
|
||||
while (it != connections.end()) {
|
||||
CNSocket *cSock = it->second;
|
||||
|
||||
if (!cSock->isAlive()) {
|
||||
killConnection(cSock);
|
||||
it = connections.erase(it);
|
||||
|
||||
removePollFD(cSock->sock);
|
||||
|
||||
delete cSock;
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CNServer::kill() {
|
||||
std::lock_guard<std::mutex> lock(activeCrit); // the lock will be removed when the function ends
|
||||
active = false;
|
||||
|
||||
// kill all connections
|
||||
for (auto& pair : connections) {
|
||||
CNSocket *cSock = pair.second;
|
||||
if (cSock->isAlive())
|
||||
cSock->kill();
|
||||
|
||||
delete cSock;
|
||||
}
|
||||
|
||||
connections.clear();
|
||||
}
|
||||
|
||||
void CNServer::printPacket(CNPacketData *data) {
|
||||
if (settings::VERBOSITY < 2)
|
||||
return;
|
||||
|
||||
if (settings::VERBOSITY < 3) switch (data->type) {
|
||||
case P_CL2LS_REP_LIVE_CHECK:
|
||||
case P_CL2FE_REP_LIVE_CHECK:
|
||||
case P_CL2FE_REQ_PC_MOVE:
|
||||
case P_CL2FE_REQ_PC_JUMP:
|
||||
case P_CL2FE_REQ_PC_SLOPE:
|
||||
case P_CL2FE_REQ_PC_MOVEPLATFORM:
|
||||
case P_CL2FE_REQ_PC_MOVETRANSPORTATION:
|
||||
case P_CL2FE_REQ_PC_ZIPLINE:
|
||||
case P_CL2FE_REQ_PC_JUMPPAD:
|
||||
case P_CL2FE_REQ_PC_LAUNCHER:
|
||||
case P_CL2FE_REQ_PC_STOP:
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "OpenFusion: received " << Packets::p2str(data->type) << " (" << data->type << ")" << std::endl;
|
||||
}
|
||||
|
||||
bool CNServer::checkExtraSockets(int i) { return false; } // stubbed
|
||||
void CNServer::newConnection(CNSocket* cns) {} // stubbed
|
||||
void CNServer::killConnection(CNSocket* cns) {} // stubbed
|
||||
void CNServer::onStep() {} // stubbed
|
||||
@@ -10,6 +10,7 @@
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#define M_PI 3.14159265358979323846
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
@@ -17,6 +18,8 @@
|
||||
#pragma comment(lib, "Ws2_32.lib")
|
||||
|
||||
typedef char buffer_t;
|
||||
#define PollFD WSAPOLLFD
|
||||
#define poll WSAPoll
|
||||
#define OF_ERRNO WSAGetLastError()
|
||||
#define OF_EWOULD WSAEWOULDBLOCK
|
||||
#define SOCKETINVALID(x) (x == INVALID_SOCKET)
|
||||
@@ -26,11 +29,13 @@
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
typedef int SOCKET;
|
||||
typedef void buffer_t;
|
||||
#define PollFD struct pollfd
|
||||
#define OF_ERRNO errno
|
||||
#define OF_EWOULD EWOULDBLOCK
|
||||
#define SOCKETINVALID(x) (x < 0)
|
||||
@@ -43,8 +48,12 @@
|
||||
#include <csignal>
|
||||
#include <list>
|
||||
#include <queue>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Defines.hpp"
|
||||
#include "Packets.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
|
||||
@@ -67,16 +76,24 @@ inline void* xmalloc(size_t sz) {
|
||||
void* res = calloc(1, sz);
|
||||
|
||||
if (res == NULL) {
|
||||
std::cerr << "[FATAL] OpenFusion: calloc failed to allocate memory!" << std::endl;
|
||||
std::cerr << "[FATAL] OpenFusion: out of memory!" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
inline constexpr bool isInboundPacketID(uint32_t id) {
|
||||
return ((id & CL2LS) == CL2LS) || ((id & CL2FE) == CL2FE);
|
||||
}
|
||||
|
||||
inline constexpr bool isOutboundPacketID(uint32_t id) {
|
||||
return ((id & LS2CL) == LS2CL) || ((id & FE2CL) == FE2CL);
|
||||
}
|
||||
|
||||
// overflow-safe validation of variable-length packets
|
||||
// for outbound packets
|
||||
inline bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) {
|
||||
inline constexpr bool validOutVarPacket(size_t base, size_t npayloads, size_t plsize) {
|
||||
// check for multiplication overflow
|
||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||
return false;
|
||||
@@ -93,7 +110,7 @@ inline bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) {
|
||||
}
|
||||
|
||||
// for inbound packets
|
||||
inline bool validInVarPacket(size_t base, int32_t npayloads, size_t plsize, size_t datasize) {
|
||||
inline constexpr bool validInVarPacket(size_t base, size_t npayloads, size_t plsize, size_t datasize) {
|
||||
// check for multiplication overflow
|
||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||
return false;
|
||||
@@ -110,6 +127,9 @@ inline bool validInVarPacket(size_t base, int32_t npayloads, size_t plsize, size
|
||||
return true;
|
||||
}
|
||||
|
||||
void printSocketError(const char *func);
|
||||
bool setSockNonblocking(SOCKET listener, SOCKET newSock);
|
||||
|
||||
namespace CNSocketEncryption {
|
||||
// you won't believe how complicated they made it in the client :facepalm:
|
||||
static constexpr const char* defaultKey = "m@rQn~W#";
|
||||
@@ -123,11 +143,13 @@ namespace CNSocketEncryption {
|
||||
}
|
||||
|
||||
struct CNPacketData {
|
||||
void* buf;
|
||||
void *buf;
|
||||
int size;
|
||||
uint32_t type;
|
||||
int trCnt;
|
||||
void *trailers;
|
||||
|
||||
CNPacketData(void* b, uint32_t t, int l);
|
||||
CNPacketData(void* b, uint32_t t, int l, int trnum, void *trs);
|
||||
};
|
||||
|
||||
enum ACTIVEKEY {
|
||||
@@ -135,8 +157,6 @@ enum ACTIVEKEY {
|
||||
SOCKETKEY_FE
|
||||
};
|
||||
|
||||
struct Player;
|
||||
|
||||
class CNSocket;
|
||||
typedef void (*PacketHandler)(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
@@ -155,12 +175,15 @@ private:
|
||||
bool sendData(uint8_t* data, int size);
|
||||
int recvData(buffer_t* data, int size);
|
||||
|
||||
inline void parsePacket(uint8_t *buf, size_t size);
|
||||
void validatingSendPacket(void *buf, uint32_t packetType);
|
||||
|
||||
public:
|
||||
SOCKET sock;
|
||||
sockaddr_in sockaddr;
|
||||
PacketHandler pHandler;
|
||||
Player *plr = nullptr;
|
||||
|
||||
CNSocket(SOCKET s, PacketHandler ph);
|
||||
CNSocket(SOCKET s, struct sockaddr_in &addr, PacketHandler ph);
|
||||
|
||||
void setEKey(uint64_t k);
|
||||
void setFEKey(uint64_t k);
|
||||
@@ -172,6 +195,16 @@ public:
|
||||
void sendPacket(void* buf, uint32_t packetType, size_t size);
|
||||
void step();
|
||||
bool isAlive();
|
||||
|
||||
// generic, validating wrapper for sendPacket()
|
||||
template<class T>
|
||||
inline void sendPacket(T& pkt, uint32_t packetType) {
|
||||
/*
|
||||
* We do most of the logic in a helper, to lower the amount of code
|
||||
* that gets generated multiple times with each template instantiation.
|
||||
*/
|
||||
validatingSendPacket((void*)&pkt, packetType);
|
||||
}
|
||||
};
|
||||
|
||||
class CNServer;
|
||||
@@ -191,9 +224,13 @@ struct TimerEvent {
|
||||
// in charge of accepting new connections and making sure each connection is kept alive
|
||||
class CNServer {
|
||||
protected:
|
||||
std::list<CNSocket*> connections;
|
||||
std::unordered_map<SOCKET, CNSocket*> connections;
|
||||
std::mutex activeCrit;
|
||||
|
||||
const size_t STARTFDSCOUNT = 8; // number of initial PollFD slots
|
||||
std::vector<PollFD> fds;
|
||||
|
||||
std::string serverType = "invalid";
|
||||
SOCKET sock;
|
||||
uint16_t port;
|
||||
socklen_t addressSize;
|
||||
@@ -202,6 +239,9 @@ protected:
|
||||
|
||||
bool active = true;
|
||||
|
||||
void addPollFD(SOCKET s);
|
||||
void removePollFD(int i);
|
||||
|
||||
public:
|
||||
PacketHandler pHandler;
|
||||
|
||||
@@ -210,7 +250,8 @@ public:
|
||||
|
||||
void start();
|
||||
void kill();
|
||||
static void printPacket(CNPacketData *data, int type);
|
||||
static void printPacket(CNPacketData *data);
|
||||
virtual bool checkExtraSockets(int i);
|
||||
virtual void newConnection(CNSocket* cns);
|
||||
virtual void killConnection(CNSocket* cns);
|
||||
virtual void onStep();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user